use HTML::Entities;
use POSIX 'setsid';
use XML::LibXML;
+use File::Basename;
use SL::Iconv;
use SL::Template::OpenDocument::Styles;
-use SL::DB::BankAccount;
use SL::Helper::QrBill;
-use SL::Helper::ISO3166;
+use SL::Helper::QrBillFunctions qw(
+ get_ref_number_formatted get_iban_formatted get_amount_formatted
+ get_street_name_from_address_line get_building_number_from_address_line
+);
use Cwd;
# use File::Copy;
# use File::Spec;
# use File::Temp qw(:mktemp);
use IO::File;
-use List::Util qw(first);
use strict;
} elsif ((substr($text, $pos + 4, 4) eq 'else') && (1 == $depth)) {
if (!$var) {
- $self->{"error"} = '<%else%> outside of <%if%> / <%ifnot%>.';
+ $self->{error} = '<%else%> outside of <%if%> / <%ifnot%>.';
return undef;
}
($table_row, $contents) = $self->find_end($contents, length($1));
if (!$table_row) {
- $self->{"error"} = "Unclosed <\%foreachrow\%>." unless ($self->{"error"});
+ $self->{error} = "Unclosed <\%foreachrow\%>." unless ($self->{"error"});
$main::lxdebug->leave_sub();
return undef;
}
substr($contents, 0, $pos_foreach) = "";
if ($contents !~ m|^(\<\%foreach (.*?)\%\>)|) {
- $self->{"error"} = "Malformed <\%foreach\%>.";
+ $self->{error} = "Malformed <\%foreach\%>.";
$main::lxdebug->leave_sub();
return undef;
}
my $block;
($block, $contents) = $self->find_end($contents);
if (!$block) {
- $self->{"error"} = "Unclosed <\%foreach\%>." unless ($self->{"error"});
+ $self->{error} = "Unclosed <\%foreach\%>." unless ($self->{error});
$main::lxdebug->leave_sub();
return undef;
}
my $self = $_[0];
local *OUT = $_[1];
- my $form = $self->{"form"};
+ my $form = $self->{form};
close(OUT);
+ my $is_qr_bill = $::instance_conf->get_create_qrbill_invoices &&
+ ($form->{formname} eq 'invoice' ||
+ $form->{formname} eq 'invoice_for_advance_payment') &&
+ $form->{template_meta}->{printer}->{template_code} =~ m/qr/ ?
+ 1 : 0;
+
my $qr_image_path;
- if ($::instance_conf->get_create_qrbill_invoices && $form->{formname} eq 'invoice') {
+ if ($is_qr_bill) {
# the biller account information, biller address and the reference number,
# are needed in the template aswell as in the qr-code generation, therefore
# assemble these and add to $::form
}
my $file_name;
- if ($form->{"IN"} =~ m|^/|) {
- $file_name = $form->{"IN"};
+ if ($form->{IN} =~ m|^/|) {
+ $file_name = $form->{IN};
} else {
- $file_name = $form->{"templates"} . "/" . $form->{"IN"};
+ $file_name = $form->{templates} . "/" . $form->{IN};
}
my $zip = Archive::Zip->new();
if (Archive::Zip->AZ_OK != $zip->read($file_name)) {
- $self->{"error"} = "File not found/is not a OpenDocument file.";
+ $self->{error} = "File not found/is not a OpenDocument file.";
$main::lxdebug->leave_sub();
return 0;
}
my $contents = Encode::decode('utf-8-strict', $zip->contents("content.xml"));
if (!$contents) {
- $self->{"error"} = "File is not a OpenDocument file.";
+ $self->{error} = "File is not a OpenDocument file.";
$main::lxdebug->leave_sub();
return 0;
}
$zip->contents("styles.xml", Encode::encode('utf-8-strict', $new_styles));
}
- if ($::instance_conf->get_create_qrbill_invoices && $form->{formname} eq 'invoice') {
+ if ($is_qr_bill) {
# get placeholder path from odt XML
my $qr_placeholder_path;
my $dom = XML::LibXML->load_xml(string => $contents);
);
}
- $zip->writeToFileNamed($form->{"tmpfile"}, 1);
+ $zip->writeToFileNamed($form->{tmpfile}, 1);
my $res = 1;
- if ($form->{"format"} =~ /pdf/) {
+ if ($form->{format} =~ /pdf/) {
$res = $self->convert_to_pdf();
}
return $res;
}
-sub get_qrbill_account {
- $main::lxdebug->enter_sub();
- my ($self) = @_;
-
- my $qr_account;
-
- my $bank_accounts = SL::DB::Manager::BankAccount->get_all;
- $qr_account = scalar(@{ $bank_accounts }) == 1 ?
- $bank_accounts->[0] :
- first { $_->use_for_qrbill } @{ $bank_accounts };
-
- if (!$qr_account) {
- $::form->error($::locale->text('No bank account flagged for QRBill usage was found.'));
- }
-
- $main::lxdebug->leave_sub();
- return $qr_account;
-}
-
-sub remove_letters_prefix {
- my $s = $_[0];
- $s =~ s/^[a-zA-Z]+//;
- return $s;
-}
-
-sub check_digits_and_max_length {
- my $s = $_[0];
- my $length = $_[1];
-
- return 0 if (!($s =~ /^\d*$/) || length($s) > $length);
- return 1;
-}
-
-sub calculate_check_digit {
- # calculate ESR check digit using algorithm: "modulo 10, recursive"
- my $ref_number_str = $_[0];
-
- my @m = (0, 9, 4, 6, 8, 2, 7, 1, 3, 5);
- my $carry = 0;
-
- my @ref_number_split = map int($_), split(//, $ref_number_str);
-
- for my $v (@ref_number_split) {
- $carry = @m[($carry + $v) % 10];
- }
-
- return (10 - $carry) % 10;
-}
-
-sub assemble_ref_number {
- $main::lxdebug->enter_sub();
-
- my $bank_id = $_[0];
- my $customer_number = $_[1];
- my $order_number = $_[2] // "0";
- my $invoice_number = $_[3] // "0";
-
- # check values (analog to checks in makro)
- # - bank_id
- # input: 6 digits, only numbers
- # output: 6 digits, only numbers
- if (!($bank_id =~ /^\d*$/) || length($bank_id) != 6) {
- $::form->error($::locale->text('Bank account id number invalid. Must be 6 digits.'));
- }
-
- # - customer_number
- # input: prefix (letters) + up to 6 digits (numbers)
- # output: prefix removed, 6 digits, filled with leading zeros
- $customer_number = remove_letters_prefix($customer_number);
- if (!check_digits_and_max_length($customer_number, 6)) {
- $::form->error($::locale->text('Customer number invalid. Must be less then or equal to 6 digits after prefix.'));
- }
- # fill with zeros
- $customer_number = sprintf "%06d", $customer_number;
-
- # - order_number
- # input: prefix (letters) + up to 7 digits, may be zero
- # output: prefix removed, 7 digits, filled with leading zeros
- $order_number = remove_letters_prefix($order_number);
- if (!check_digits_and_max_length($order_number, 7)) {
- $::form->error($::locale->text('Order number invalid. Must be less then or equal to 7 digits after prefix.'));
- }
- # fill with zeros
- $order_number = sprintf "%07d", $order_number;
-
- # - invoice_number
- # input: prefix (letters) + up to 7 digits, may be zero
- # output: prefix removed, 7 digits, filled with leading zeros
- $invoice_number = remove_letters_prefix($invoice_number);
- if (!check_digits_and_max_length($invoice_number, 7)) {
- $::form->error($::locale->text('Invoice number invalid. Must be less then or equal to 7 digits after prefix.'));
- }
- # fill with zeros
- $invoice_number = sprintf "%07d", $invoice_number;
-
- # assemble ref. number
- my $ref_number = $bank_id . $customer_number . $order_number . $invoice_number;
-
- # calculate check digit
- my $ref_number_cpl = $ref_number . calculate_check_digit($ref_number);
-
- $main::lxdebug->leave_sub();
- return $ref_number_cpl;
-}
-
-sub get_ref_number_formatted {
- $main::lxdebug->enter_sub();
-
- my $ref_number = $_[0];
-
- # create ref. number in format:
- # 'XX XXXXX XXXXX XXXXX XXXXX XXXXX' (2 digits + 5 x 5 digits)
- my $ref_number_spaced = substr($ref_number, 0, 2) . ' ' .
- substr($ref_number, 2, 5) . ' ' .
- substr($ref_number, 7, 5) . ' ' .
- substr($ref_number, 12, 5) . ' ' .
- substr($ref_number, 17, 5) . ' ' .
- substr($ref_number, 22, 5);
-
- $main::lxdebug->leave_sub();
- return $ref_number_spaced;
-}
-
-sub get_iban_formatted {
- $main::lxdebug->enter_sub();
-
- my $iban = $_[0];
-
- # create iban number in format:
- # 'XXXX XXXX XXXX XXXX XXXX X' (5 x 4 + 1digits)
- my $iban_spaced = substr($iban, 0, 4) . ' ' .
- substr($iban, 4, 4) . ' ' .
- substr($iban, 8, 4) . ' ' .
- substr($iban, 12, 4) . ' ' .
- substr($iban, 16, 4) . ' ' .
- substr($iban, 20, 1);
-
- $main::lxdebug->leave_sub();
- return $iban_spaced;
-}
-
-sub get_amount_formatted {
- $main::lxdebug->enter_sub();
-
- unless ($_[0] =~ /^\d+\.\d{2}$/) {
- $::form->error($::locale->text('Amount has wrong format.'));
- }
-
- local $_ = shift;
- $_ = reverse split //;
- m/^\d{2}\./g;
- s/\G(\d{3})(?=\d)/$1 /g;
-
- $main::lxdebug->leave_sub();
- return scalar reverse split //;
-}
-
sub generate_qr_code {
$main::lxdebug->enter_sub();
my $self = $_[0];
- my $form = $self->{"form"};
+ my $form = $self->{form};
# assemble data for QR-Code
- # get qr-account data
- my $qr_account = $self->get_qrbill_account();
+ if (!$form->{qrbill_iban}) {
+ $::form->error($::locale->text('No bank account flagged for QRBill usage was found.'));
+ }
my %biller_information = (
- 'iban' => $qr_account->{'iban'}
+ iban => $form->{qrbill_iban}
);
- my $biller_countrycode = SL::Helper::ISO3166::map_name_to_alpha_2_code(
- $::instance_conf->get_address_country()
- );
- if (!$biller_countrycode) {
+ if (!$form->{qrbill_biller_countrycode}) {
$::form->error($::locale->text('Error mapping biller countrycode.'));
}
my %biller_data = (
- 'address_type' => 'K',
- 'company' => $::instance_conf->get_company(),
- 'address_row1' => $::instance_conf->get_address_street1(),
- 'address_row2' => $::instance_conf->get_address_zipcode() . ' ' . $::instance_conf->get_address_city(),
- 'countrycode' => $biller_countrycode,
+ address_type => 'S',
+ company => $::instance_conf->get_company(),
+ street => get_street_name_from_address_line($::instance_conf->get_address_street1()),
+ street_no => get_building_number_from_address_line($::instance_conf->get_address_street1()),
+ postalcode => $::instance_conf->get_address_zipcode(),
+ city => $::instance_conf->get_address_city(),
+ countrycode => $form->{qrbill_biller_countrycode},
);
- my $amount;
- if ($form->{'qrbill_without_amount'}) {
+ my ($amount, $amount_formatted);
+ if ($form->{qrbill_without_amount}) {
$amount = '';
+ $amount_formatted = '';
} else {
- $amount = sprintf("%.2f", $form->parse_amount(\%::myconfig, $form->{'total'}));
+ $amount = $form->{qrbill_amount};
+
+ # format amount for template
+ $amount_formatted = get_amount_formatted($amount);
+ if (!$amount_formatted) {
+ $::form->error($::locale->text('Amount has wrong format.'));
+ }
}
my %payment_information = (
- 'amount' => $amount,
- 'currency' => $form->{'currency'},
+ amount => $amount,
+ currency => $form->{currency},
);
- my $customer_countrycode = SL::Helper::ISO3166::map_name_to_alpha_2_code($form->{'country'});
- if (!$customer_countrycode) {
+ # get address data from billing address if given, otherwise from invoice
+ my $street_name = get_street_name_from_address_line($form->{billing_address_id} ?
+ $form->{billing_address_street} :
+ $form->{street});
+ my $building_number = get_building_number_from_address_line($form->{billing_address_id} ?
+ $form->{billing_address_street} :
+ $form->{street});
+ my $postalcode = $form->{billing_address_id} ?
+ $form->{billing_address_zipcode} :
+ $form->{zipcode};
+ my $city = $form->{billing_address_id} ?
+ $form->{billing_address_city} :
+ $form->{city};
+
+ # validate address data
+ if ($postalcode !~ m/^\d{4,}$/) {
+ $::form->error($::locale->text('Zipcode missing or wrong format.'));
+ }
+ if (!$city) {
+ $::form->error($::locale->text('No city given.'));
+ }
+ if (!$form->{qrbill_customer_countrycode}) {
$::form->error($::locale->text('Error mapping customer countrycode.'));
}
+
my %invoice_recipient_data = (
- 'address_type' => 'K',
- 'name' => $form->{'name'},
- 'address_row1' => $form->{'street'},
- 'address_row2' => $form->{'zipcode'} . ' ' . $form->{'city'},
- 'countrycode' => $customer_countrycode,
+ address_type => 'S',
+ name => $form->{billing_address_id} ?
+ $form->{billing_address_name} :
+ $form->{name},
+ street => $street_name,
+ street_no => $building_number,
+ postalcode => $postalcode,
+ city => $city,
+ countrycode => $form->{qrbill_customer_countrycode},
);
my %ref_nr_data;
if ($::instance_conf->get_create_qrbill_invoices == 1) {
- # generate ref.-no. with check digit
- my $ref_number = assemble_ref_number(
- $qr_account->{'bank_account_id'},
- $form->{'customernumber'},
- $form->{'ordnumber'},
- $form->{'invnumber'},
- );
+ # fill reference number with zeros when printing preview (before booking)
+ my $reference_number = $form->{id} ? $form->{qr_reference} : '0' x 27;
+
%ref_nr_data = (
- 'type' => 'QRR',
- 'ref_number' => $ref_number,
+ type => 'QRR',
+ ref_number => $reference_number,
);
# get ref. number/iban formatted with spaces and set into form for template
# processing
- $form->{'ref_number'} = $ref_number;
- $form->{'ref_number_formatted'} = get_ref_number_formatted($ref_number);
+ $form->{ref_number} = $reference_number;
+ $form->{ref_number_formatted} = get_ref_number_formatted($reference_number);
} elsif ($::instance_conf->get_create_qrbill_invoices == 2) {
%ref_nr_data = (
- 'type' => 'NON',
- 'ref_number' => '',
+ type => 'NON',
+ ref_number => '',
);
} else {
$::form->error($::locale->text('Error getting QR-Bill type.'));
}
- # set into form for template processing
- $form->{'biller_information'} = \%biller_information;
- $form->{'biller_data'} = \%biller_data;
- $form->{'iban_formatted'} = get_iban_formatted($qr_account->{'iban'});
-
- # format amount for template
- $form->{'amount_formatted'} = get_amount_formatted(
- sprintf(
- "%.2f",
- $form->parse_amount(\%::myconfig, $form->{'total'})
- )
+ my %additional_information = (
+ unstructured_message => $form->{qr_unstructured_message}
);
+ # set into form for template processing
+ $form->{biller_information} = \%biller_information;
+ $form->{biller_data} = \%biller_data;
+ $form->{iban_formatted} = get_iban_formatted($form->{qrbill_iban});
+ $form->{amount_formatted} = $amount_formatted;
+ $form->{unstructured_message} = $form->{qr_unstructured_message};
+
# set outfile
- my $outfile = $form->{"tmpdir"} . '/' . 'qr-code.png';
+ my $outfile = $form->{tmpdir} . '/' . 'qr-code.png';
# generate QR-Code Image
eval {
\%payment_information,
\%invoice_recipient_data,
\%ref_nr_data,
+ \%additional_information
);
$qr_image->generate($outfile);
} or do {
return $outfile;
}
-sub is_xvfb_running {
- $main::lxdebug->enter_sub();
-
- my ($self) = @_;
-
- local *IN;
- my $dfname = $self->{"userspath"} . "/xvfb_display";
- my $display;
-
- $main::lxdebug->message(LXDebug->DEBUG2(), " Looking for $dfname\n");
- if ((-f $dfname) && open(IN, $dfname)) {
- my $pid = <IN>;
- chomp($pid);
- $display = <IN>;
- chomp($display);
- my $xauthority = <IN>;
- chomp($xauthority);
- close(IN);
-
- $main::lxdebug->message(LXDebug->DEBUG2(), " found with $pid and $display\n");
-
- if ((! -d "/proc/$pid") || !open(IN, "/proc/$pid/cmdline")) {
- $main::lxdebug->message(LXDebug->DEBUG2(), " no/wrong process #1\n");
- unlink($dfname, $xauthority);
- $main::lxdebug->leave_sub();
- return undef;
- }
- my $line = <IN>;
- close(IN);
- if ($line !~ /xvfb/i) {
- $main::lxdebug->message(LXDebug->DEBUG2(), " no/wrong process #2\n");
- unlink($dfname, $xauthority);
- $main::lxdebug->leave_sub();
- return undef;
- }
-
- $ENV{"XAUTHORITY"} = $xauthority;
- $ENV{"DISPLAY"} = $display;
- } else {
- $main::lxdebug->message(LXDebug->DEBUG2(), " not found\n");
- }
-
- $main::lxdebug->leave_sub();
-
- return $display;
-}
-
-sub spawn_xvfb {
- $main::lxdebug->enter_sub();
-
- my ($self) = @_;
-
- $main::lxdebug->message(LXDebug->DEBUG2, "spawn_xvfb()\n");
-
- my $display = $self->is_xvfb_running();
-
- if ($display) {
- $main::lxdebug->leave_sub();
- return $display;
- }
-
- $display = 99;
- while ( -f "/tmp/.X${display}-lock") {
- $display++;
- }
- $display = ":${display}";
- $main::lxdebug->message(LXDebug->DEBUG2(), " display $display\n");
-
- my $mcookie = `mcookie`;
- die("Installation error: mcookie not found.") if ($? != 0);
- chomp($mcookie);
-
- $main::lxdebug->message(LXDebug->DEBUG2(), " mcookie $mcookie\n");
-
- my $xauthority = "/tmp/.Xauthority-" . $$ . "-" . time() . "-" . int(rand(9999999));
- $ENV{"XAUTHORITY"} = $xauthority;
-
- $main::lxdebug->message(LXDebug->DEBUG2(), " xauthority $xauthority\n");
-
- if (system("xauth add \"${display}\" . \"${mcookie}\"") == -1) {
- die "system call to xauth failed: $!";
- }
- if ($? != 0) {
- $self->{"error"} = "Conversion to PDF failed because OpenOffice could not be started (xauth: $!)";
- $main::lxdebug->leave_sub();
- return undef;
- }
-
- $main::lxdebug->message(LXDebug->DEBUG2(), " about to fork()\n");
-
- my $pid = fork();
- if (0 == $pid) {
- $main::lxdebug->message(LXDebug->DEBUG2(), " Child execing\n");
- exec($::lx_office_conf{applications}->{xvfb}, $display, "-screen", "0", "640x480x8", "-nolisten", "tcp");
- }
- sleep(3);
- $main::lxdebug->message(LXDebug->DEBUG2(), " parent dont sleeping\n");
-
- local *OUT;
- my $dfname = $self->{"userspath"} . "/xvfb_display";
- if (!open(OUT, ">", $dfname)) {
- $self->{"error"} = "Conversion to PDF failed because OpenOffice could not be started ($dfname: $!)";
- unlink($xauthority);
- kill($pid);
- $main::lxdebug->leave_sub();
- return undef;
- }
- print(OUT "$pid\n$display\n$xauthority\n");
- close(OUT);
-
- $main::lxdebug->message(LXDebug->DEBUG2(), " parent re-testing\n");
-
- if (!$self->is_xvfb_running()) {
- $self->{"error"} = "Conversion to PDF failed because OpenOffice could not be started.";
- unlink($xauthority, $dfname);
- kill($pid);
- $main::lxdebug->leave_sub();
- return undef;
- }
-
- $main::lxdebug->message(LXDebug->DEBUG2(), " spawn OK\n");
-
- $main::lxdebug->leave_sub();
-
- return $display;
-}
-
sub _run_python_uno {
my ($self, @args) = @_;
my $ssres = setsid();
$main::lxdebug->message(LXDebug->DEBUG2(), " Child execing\n");
my @cmdline = ($::lx_office_conf{applications}->{openofficeorg_writer},
- "-minimized", "-norestore", "-nologo", "-nolockcheck",
- "-headless",
- "-accept=socket,host=localhost,port=" .
+ "--minimized", "--norestore", "--nologo", "--nolockcheck",
+ "--headless",
+ "--accept=socket,host=localhost,port=" .
$::lx_office_conf{print_templates}->{openofficeorg_daemon_port} . ";urp;");
exec(@cmdline);
} else {
}
if (!$res) {
- $self->{"error"} = "Conversion from OpenDocument to PDF failed because " .
+ $self->{error} = "Conversion from OpenDocument to PDF failed because " .
"OpenOffice could not be started.";
}
my ($self) = @_;
- my $form = $self->{"form"};
+ my $form = $self->{form};
- my $filename = $form->{"tmpfile"};
+ my $filename = $form->{tmpfile};
$filename =~ s/.odt$//;
if (substr($filename, 0, 1) ne "/") {
$filename = getcwd() . "/${filename}";
}
- if (substr($self->{"userspath"}, 0, 1) eq "/") {
- $ENV{'HOME'} = $self->{"userspath"};
+ if (substr($self->{userspath}, 0, 1) eq "/") {
+ $ENV{HOME} = $self->{userspath};
} else {
- $ENV{'HOME'} = getcwd() . "/" . $self->{"userspath"};
+ $ENV{HOME} = getcwd() . "/" . $self->{userspath};
}
- if (!$self->spawn_xvfb()) {
- $main::lxdebug->leave_sub();
- return 0;
- }
+ my $outdir = dirname($filename);
if (!$::lx_office_conf{print_templates}->{openofficeorg_daemon}) {
if (system($::lx_office_conf{applications}->{openofficeorg_writer},
- "-minimized", "-norestore", "-nologo", "-nolockcheck", "-headless",
- "file:${filename}.odt",
- "macro://" . (split('/', $filename))[-1] . "/Standard.Conversion.ConvertSelfToPDF()") == -1) {
+ "--minimized", "--norestore", "--nologo", "--nolockcheck", "--headless",
+ "--convert-to", "pdf", "--outdir", $outdir,
+ "file:${filename}.odt") == -1) {
die "system call to $::lx_office_conf{applications}->{openofficeorg_writer} failed: $!";
}
} else {
my $res = $?;
if ((0 == $?) || (-f "${filename}.pdf" && -s "${filename}.pdf")) {
- $form->{"tmpfile"} =~ s/odt$/pdf/;
+ $form->{tmpfile} =~ s/odt$/pdf/;
unlink($filename . ".odt");
}
unlink($filename . ".odt", $filename . ".pdf");
- $self->{"error"} = "Conversion from OpenDocument to PDF failed. " .
+ $self->{error} = "Conversion from OpenDocument to PDF failed. " .
"Exit code: $res";
$main::lxdebug->leave_sub();
sub get_mime_type() {
my ($self) = @_;
- if ($self->{"form"}->{"format"} =~ /pdf/) {
+ if ($self->{form}->{format} =~ /pdf/) {
return "application/pdf";
} else {
return "application/vnd.oasis.opendocument.text";