Revision 7e6e1bcf
Von Cem Aydin vor mehr als 2 Jahren hinzugefügt
SL/Template/OpenDocument.pm | ||
---|---|---|
6 | 6 |
use Encode; |
7 | 7 |
use HTML::Entities; |
8 | 8 |
use POSIX 'setsid'; |
9 |
use XML::LibXML; |
|
9 | 10 |
|
10 | 11 |
use SL::Iconv; |
11 | 12 |
use SL::Template::OpenDocument::Styles; |
12 | 13 |
|
14 |
use SL::DB::BankAccount; |
|
15 |
use SL::Helper::QrBill; |
|
16 |
use SL::Helper::ISO3166; |
|
17 |
|
|
13 | 18 |
use Cwd; |
14 | 19 |
# use File::Copy; |
15 | 20 |
# use File::Spec; |
16 | 21 |
# use File::Temp qw(:mktemp); |
17 | 22 |
use IO::File; |
23 |
use List::Util qw(first); |
|
18 | 24 |
|
19 | 25 |
use strict; |
20 | 26 |
|
... | ... | |
346 | 352 |
sub parse { |
347 | 353 |
$main::lxdebug->enter_sub(); |
348 | 354 |
my $self = $_[0]; |
355 |
|
|
349 | 356 |
local *OUT = $_[1]; |
350 | 357 |
my $form = $self->{"form"}; |
351 | 358 |
|
352 | 359 |
close(OUT); |
353 | 360 |
|
361 |
my $qr_image_path; |
|
362 |
if ($::instance_conf->get_create_qrbill_invoices) { |
|
363 |
# the biller account information, biller address and the reference number, |
|
364 |
# are needed in the template aswell as in the qr-code generation, therefore |
|
365 |
# assemble these and add to $::form |
|
366 |
$qr_image_path = $self->generate_qr_code; |
|
367 |
} |
|
368 |
|
|
354 | 369 |
my $file_name; |
355 | 370 |
if ($form->{"IN"} =~ m|^/|) { |
356 | 371 |
$file_name = $form->{"IN"}; |
... | ... | |
420 | 435 |
$zip->contents("styles.xml", Encode::encode('utf-8-strict', $new_styles)); |
421 | 436 |
} |
422 | 437 |
|
438 |
if ($::instance_conf->get_create_qrbill_invoices) { |
|
439 |
# get placeholder path from odt XML |
|
440 |
my $qr_placeholder_path; |
|
441 |
my $dom = XML::LibXML->load_xml(string => $contents); |
|
442 |
my @nodelist = $dom->getElementsByTagName("draw:frame"); |
|
443 |
for my $node (@nodelist) { |
|
444 |
my $attr = $node->getAttribute('draw:name'); |
|
445 |
if ($attr eq 'QRCodePlaceholder') { |
|
446 |
my @children = $node->getChildrenByTagName('draw:image'); |
|
447 |
$qr_placeholder_path = $children[0]->getAttribute('xlink:href'); |
|
448 |
} |
|
449 |
} |
|
450 |
if (!defined($qr_placeholder_path)) { |
|
451 |
$::form->error($::locale->text('QR-Code placeholder image: QRCodePlaceholder not found in template.')); |
|
452 |
} |
|
453 |
# replace QR-Code Placeholder Image in zip file (odt) with generated one |
|
454 |
$zip->updateMember( |
|
455 |
$qr_placeholder_path, |
|
456 |
$qr_image_path |
|
457 |
); |
|
458 |
} |
|
459 |
|
|
423 | 460 |
$zip->writeToFileNamed($form->{"tmpfile"}, 1); |
424 | 461 |
|
425 | 462 |
my $res = 1; |
... | ... | |
431 | 468 |
return $res; |
432 | 469 |
} |
433 | 470 |
|
471 |
sub get_qrbill_account { |
|
472 |
$main::lxdebug->enter_sub(); |
|
473 |
my ($self) = @_; |
|
474 |
|
|
475 |
my $qr_account; |
|
476 |
|
|
477 |
my $bank_accounts = SL::DB::Manager::BankAccount->get_all; |
|
478 |
$qr_account = scalar(@{ $bank_accounts }) == 1 ? |
|
479 |
$bank_accounts->[0] : |
|
480 |
first { $_->use_for_qrbill } @{ $bank_accounts }; |
|
481 |
|
|
482 |
if (!$qr_account) { |
|
483 |
$::form->error($::locale->text('No bank account flagged for QRBill usage was found.')); |
|
484 |
} |
|
485 |
|
|
486 |
$main::lxdebug->leave_sub(); |
|
487 |
return $qr_account; |
|
488 |
} |
|
489 |
|
|
490 |
sub remove_letters_prefix { |
|
491 |
my $s = $_[0]; |
|
492 |
$s =~ s/^[a-zA-Z]+//; |
|
493 |
return $s; |
|
494 |
} |
|
495 |
|
|
496 |
sub check_digits_and_max_length { |
|
497 |
my $s = $_[0]; |
|
498 |
my $length = $_[1]; |
|
499 |
|
|
500 |
return 0 if (!($s =~ /^\d*$/) || length($s) > $length); |
|
501 |
return 1; |
|
502 |
} |
|
503 |
|
|
504 |
sub calculate_check_digit { |
|
505 |
# calculate ESR check digit using algorithm: "modulo 10, recursive" |
|
506 |
my $ref_number_str = $_[0]; |
|
507 |
|
|
508 |
my @m = (0, 9, 4, 6, 8, 2, 7, 1, 3, 5); |
|
509 |
my $carry = 0; |
|
510 |
|
|
511 |
my @ref_number_split = map int($_), split(//, $ref_number_str); |
|
512 |
|
|
513 |
for my $v (@ref_number_split) { |
|
514 |
$carry = @m[($carry + $v) % 10]; |
|
515 |
} |
|
516 |
|
|
517 |
return (10 - $carry) % 10; |
|
518 |
} |
|
519 |
|
|
520 |
sub assemble_ref_number { |
|
521 |
$main::lxdebug->enter_sub(); |
|
522 |
|
|
523 |
my $bank_id = $_[0]; |
|
524 |
my $customer_number = $_[1]; |
|
525 |
my $order_number = $_[2] // "0"; |
|
526 |
my $invoice_number = $_[3] // "0"; |
|
527 |
|
|
528 |
# check values (analog to checks in makro) |
|
529 |
# - bank_id |
|
530 |
# input: 6 digits, only numbers |
|
531 |
# output: 6 digits, only numbers |
|
532 |
if (!($bank_id =~ /^\d*$/) || length($bank_id) != 6) { |
|
533 |
$::form->error($::locale->text('Bank account id number invalid. Must be 6 digits.')); |
|
534 |
} |
|
535 |
|
|
536 |
# - customer_number |
|
537 |
# input: prefix (letters) + up to 6 digits (numbers) |
|
538 |
# output: prefix removed, 6 digits, filled with leading zeros |
|
539 |
$customer_number = remove_letters_prefix($customer_number); |
|
540 |
if (!check_digits_and_max_length($customer_number, 6)) { |
|
541 |
$::form->error($::locale->text('Customer number invalid. Must be less then or equal to 6 digits after prefix.')); |
|
542 |
} |
|
543 |
# fill with zeros |
|
544 |
$customer_number = sprintf "%06d", $customer_number; |
|
545 |
|
|
546 |
# - order_number |
|
547 |
# input: prefix (letters) + up to 7 digits, may be zero |
|
548 |
# output: prefix removed, 7 digits, filled with leading zeros |
|
549 |
$order_number = remove_letters_prefix($order_number); |
|
550 |
if (!check_digits_and_max_length($order_number, 7)) { |
|
551 |
$::form->error($::locale->text('Order number invalid. Must be less then or equal to 7 digits after prefix.')); |
|
552 |
} |
|
553 |
# fill with zeros |
|
554 |
$order_number = sprintf "%07d", $order_number; |
|
555 |
|
|
556 |
# - invoice_number |
|
557 |
# input: prefix (letters) + up to 7 digits, may be zero |
|
558 |
# output: prefix removed, 7 digits, filled with leading zeros |
|
559 |
$invoice_number = remove_letters_prefix($invoice_number); |
|
560 |
if (!check_digits_and_max_length($invoice_number, 7)) { |
|
561 |
$::form->error($::locale->text('Invoice number invalid. Must be less then or equal to 7 digits after prefix.')); |
|
562 |
} |
|
563 |
# fill with zeros |
|
564 |
$invoice_number = sprintf "%07d", $invoice_number; |
|
565 |
|
|
566 |
# assemble ref. number |
|
567 |
my $ref_number = $bank_id . $customer_number . $order_number . $invoice_number; |
|
568 |
|
|
569 |
# calculate check digit |
|
570 |
my $ref_number_cpl = $ref_number . calculate_check_digit($ref_number); |
|
571 |
|
|
572 |
$main::lxdebug->leave_sub(); |
|
573 |
return $ref_number_cpl; |
|
574 |
} |
|
575 |
|
|
576 |
sub get_ref_number_formatted { |
|
577 |
$main::lxdebug->enter_sub(); |
|
578 |
|
|
579 |
my $ref_number = $_[0]; |
|
580 |
|
|
581 |
# create ref. number in format: |
|
582 |
# 'XX XXXXX XXXXX XXXXX XXXXX XXXXX' (2 digits + 5 x 5 digits) |
|
583 |
my $ref_number_spaced = substr($ref_number, 0, 2) . ' ' . |
|
584 |
substr($ref_number, 2, 5) . ' ' . |
|
585 |
substr($ref_number, 7, 5) . ' ' . |
|
586 |
substr($ref_number, 12, 5) . ' ' . |
|
587 |
substr($ref_number, 17, 5) . ' ' . |
|
588 |
substr($ref_number, 22, 5); |
|
589 |
|
|
590 |
$main::lxdebug->leave_sub(); |
|
591 |
return $ref_number_spaced; |
|
592 |
} |
|
593 |
|
|
594 |
sub get_iban_formatted { |
|
595 |
$main::lxdebug->enter_sub(); |
|
596 |
|
|
597 |
my $iban = $_[0]; |
|
598 |
|
|
599 |
# create iban number in format: |
|
600 |
# 'XXXX XXXX XXXX XXXX XXXX X' (5 x 4 + 1digits) |
|
601 |
my $iban_spaced = substr($iban, 0, 4) . ' ' . |
|
602 |
substr($iban, 4, 4) . ' ' . |
|
603 |
substr($iban, 8, 4) . ' ' . |
|
604 |
substr($iban, 12, 4) . ' ' . |
|
605 |
substr($iban, 16, 4) . ' ' . |
|
606 |
substr($iban, 20, 1); |
|
607 |
|
|
608 |
$main::lxdebug->leave_sub(); |
|
609 |
return $iban_spaced; |
|
610 |
} |
|
611 |
|
|
612 |
sub get_amount_formatted { |
|
613 |
$main::lxdebug->enter_sub(); |
|
614 |
|
|
615 |
unless ($_[0] =~ /^\d+\.\d{2}$/) { |
|
616 |
$::form->error($::locale->text('Amount has wrong format.')); |
|
617 |
} |
|
618 |
|
|
619 |
local $_ = shift; |
|
620 |
$_ = reverse split //; |
|
621 |
m/^\d{2}\./g; |
|
622 |
s/\G(\d{3})(?=\d)/$1 /g; |
|
623 |
|
|
624 |
$main::lxdebug->leave_sub(); |
|
625 |
return scalar reverse split //; |
|
626 |
} |
|
627 |
|
|
628 |
sub generate_qr_code { |
|
629 |
$main::lxdebug->enter_sub(); |
|
630 |
my $self = $_[0]; |
|
631 |
my $form = $self->{"form"}; |
|
632 |
|
|
633 |
# assemble data for QR-Code |
|
634 |
|
|
635 |
# get qr-account data |
|
636 |
my $qr_account = $self->get_qrbill_account(); |
|
637 |
|
|
638 |
my %biller_information = ( |
|
639 |
'iban' => $qr_account->{'iban'} |
|
640 |
); |
|
641 |
|
|
642 |
my $biller_countrycode = SL::Helper::ISO3166::map_name_to_alpha_2_code( |
|
643 |
$::instance_conf->get_address_country() |
|
644 |
); |
|
645 |
if (!$biller_countrycode) { |
|
646 |
$::form->error($::locale->text('Error mapping biller countrycode.')); |
|
647 |
} |
|
648 |
my %biller_data = ( |
|
649 |
'address_type' => 'K', |
|
650 |
'company' => $::instance_conf->get_company(), |
|
651 |
'address_row1' => $::instance_conf->get_address_street1(), |
|
652 |
'address_row2' => $::instance_conf->get_address_zipcode() . ' ' . $::instance_conf->get_address_city(), |
|
653 |
'countrycode' => $biller_countrycode, |
|
654 |
); |
|
655 |
|
|
656 |
my %payment_information = ( |
|
657 |
'amount' => sprintf("%.2f", $form->parse_amount(\%::myconfig, $form->{'total'})), |
|
658 |
'currency' => $form->{'currency'}, |
|
659 |
); |
|
660 |
|
|
661 |
my $customer_countrycode = SL::Helper::ISO3166::map_name_to_alpha_2_code($form->{'country'}); |
|
662 |
if (!$customer_countrycode) { |
|
663 |
$::form->error($::locale->text('Error mapping customer countrycode.')); |
|
664 |
} |
|
665 |
my %invoice_recipient_data = ( |
|
666 |
'address_type' => 'K', |
|
667 |
'name' => $form->{'name'}, |
|
668 |
'address_row1' => $form->{'street'}, |
|
669 |
'address_row2' => $form->{'zipcode'} . ' ' . $form->{'city'}, |
|
670 |
'countrycode' => $customer_countrycode, |
|
671 |
); |
|
672 |
|
|
673 |
# generate ref.-no. with check digit |
|
674 |
my $ref_number = assemble_ref_number( |
|
675 |
$qr_account->{'bank_account_id'}, |
|
676 |
$form->{'customernumber'}, |
|
677 |
$form->{'ordnumber'}, |
|
678 |
$form->{'invnumber'}, |
|
679 |
); |
|
680 |
|
|
681 |
my %ref_nr_data = ( |
|
682 |
'type' => 'QRR', |
|
683 |
'ref_number' => $ref_number, |
|
684 |
); |
|
685 |
|
|
686 |
# set into form for template processing |
|
687 |
$form->{'biller_information'} = \%biller_information; |
|
688 |
$form->{'biller_data'} = \%biller_data; |
|
689 |
$form->{'ref_number'} = $ref_number; |
|
690 |
|
|
691 |
# get ref. number/iban formatted with spaces |
|
692 |
$form->{'ref_number_formatted'} = get_ref_number_formatted($ref_number); |
|
693 |
$form->{'iban_formatted'} = get_iban_formatted($qr_account->{'iban'}); |
|
694 |
|
|
695 |
# format amount for template |
|
696 |
$form->{'amount_formatted'} = get_amount_formatted( |
|
697 |
sprintf( |
|
698 |
"%.2f", |
|
699 |
$form->parse_amount(\%::myconfig, $form->{'total'}) |
|
700 |
) |
|
701 |
); |
|
702 |
|
|
703 |
# set outfile |
|
704 |
my $outfile = $form->{"tmpdir"} . '/' . 'qr-code.png'; |
|
705 |
|
|
706 |
# generate QR-Code Image |
|
707 |
eval { |
|
708 |
my $qr_image = SL::Helper::QrBill->new( |
|
709 |
\%biller_information, |
|
710 |
\%biller_data, |
|
711 |
\%payment_information, |
|
712 |
\%invoice_recipient_data, |
|
713 |
\%ref_nr_data, |
|
714 |
); |
|
715 |
$qr_image->generate($outfile); |
|
716 |
} or do { |
|
717 |
local $_ = $@; chomp; my $error = $_; |
|
718 |
$::form->error($::locale->text('QR-Image generation failed: ' . $error)); |
|
719 |
}; |
|
720 |
|
|
721 |
$main::lxdebug->leave_sub(); |
|
722 |
return $outfile; |
|
723 |
} |
|
724 |
|
|
434 | 725 |
sub is_xvfb_running { |
435 | 726 |
$main::lxdebug->enter_sub(); |
436 | 727 |
|
Auch abrufbar als: Unified diff
Swiss QR-Bill: In Druckablauf OpenDocument/OASIS integrieren
- Feature in Mandantenkonfiguration einschaltbar
- Aufruf zum Erzeugen von QR-Code PNG (Steven Schubiger)
- Vorlage hinzugefügt (rev-odt/invoice_qr.odt)
- PNG Bild CH-Kreuz hinzugefügt
- Übersetzungen hinzugefügt, locales Script ausgeführt de/en
- changelog eintrag