Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 9077dc27

Von Jan Büren vor mehr als 4 Jahren hinzugefügt

  • ID 9077dc27c713758d631fe6d08c548613d4d2dde8
  • Vorgänger 38de15c0
  • Nachfolger caaa4f67

ZUGFeRD: ZUGFeRD-Controller der minimal ZUGFeRD PDF parst

Simples Upload Formular für ein PDF. Falls das PDF gültige
ZUGFeRD Daten hat und ein Lieferant mit UST-ID in kivi gefunden wird,
werden die Formulardaten in der ersten gefunden Kreditorenbelegvorlage
des Lieferanten angezeigt. Details und TODO, s.a. perldoc

Unterschiede anzeigen:

SL/Controller/ZUGFeRD.pm
1
package SL::Controller::ZUGFeRD;
2
use strict;
3
use parent qw(SL::Controller::Base);
4

  
5
use SL::DB::RecordTemplate;
6
use SL::Locale::String qw(t8);
7
use SL::Helper::DateTime;
8
use SL::ZUGFeRD;
9

  
10
use XML::LibXML;
11

  
12

  
13
__PACKAGE__->run_before('check_auth');
14

  
15
sub action_upload_zugferd {
16
  my ($self, %params) = @_;
17

  
18
  $self->setup_zugferd_action_bar;
19
  $self->render('zugferd/form', title => $::locale->text('ZUGFeRD import'));
20
}
21

  
22
sub action_import_zugferd {
23
  my ($self, %params) = @_;
24

  
25
  die t8("missing file for action import") unless $::form->{file};
26
  die t8("can only parse a pdf file")      unless $::form->{file} =~ m/^%PDF/;
27

  
28
  my $info = SL::ZUGFeRD->extract_from_pdf($::form->{file});
29

  
30
  if ($info->{result} != SL::ZUGFeRD::RES_OK()) {
31
    # An error occurred; log message from parser:
32
    $::lxdebug->message(LXDebug::DEBUG1(), "Could not extract ZUGFeRD data, error message: " . $info->{message});
33
    die t8("Could not extract ZUGFeRD data, data and error message:") . $info->{message};
34
  }
35
  # valid ZUGFeRD metadata
36
  my $dom   = XML::LibXML->load_xml(string => $info->{invoice_xml});
37

  
38
  # 1. check if ZUGFeRD SellerTradeParty has a VAT-ID
39
  my $ustid = $dom->findnodes('//ram:SellerTradeParty/ram:SpecifiedTaxRegistration')->string_value;
40
  die t8("No VAT Info for this ZUGFeRD invoice," .
41
         " please ask your vendor to add this for his ZUGFeRD data.") unless $ustid;
42

  
43
  $ustid     =~ s/^\s+|\s+$//g;
44

  
45
  # 1.1 check if we a have a vendor with this VAT-ID (vendor.ustid)
46
  my $vc     = $dom->findnodes('//ram:SellerTradeParty/ram:Name')->string_value;
47
  my $vendor = SL::DB::Manager::Vendor->find_by(ustid => $ustid);
48
  die t8("Please add a valid VAT-ID for this vendor: " . $vc) unless (ref $vendor eq 'SL::DB::Vendor');
49

  
50
  # 2. check if we have a ap record template for this vendor (TODO only the oldest template is choosen)
51
  my $template_ap = SL::DB::Manager::RecordTemplate->get_first(where => [vendor_id => $vendor->id]);
52
  die t8("No AP Record Template for this vendor found, please add one") unless (ref $template_ap eq 'SL::DB::RecordTemplate');
53

  
54

  
55
  # 3. parse the zugferd data and fill the ap record template
56
  # -> no need to check sign (credit notes will be negative) just record thei ZUGFeRD type in ap.notes
57
  # -> check direct debit (defaults to no)
58
  # -> set amount (net amount) and unset taxincluded
59
  #    (template and user cares for tax and if there is more than one booking accno)
60
  # -> date (can be empty)
61
  # -> duedate (may be empty)
62
  # -> compare record iban and generate a warning if this differs from vendor's master data iban
63
  my $total     = $dom->findnodes('//ram:SpecifiedTradeSettlementHeaderMonetarySummation' .
64
                                  '/ram:TaxBasisTotalAmount')->string_value;
65

  
66
  my $invnumber = $dom->findnodes('//rsm:ExchangedDocument/ram:ID')->string_value;
67

  
68
  # parse dates to kivi if set/valid
69
  my ($transdate, $duedate, $dt_to_kivi, $due_dt_to_kivi);
70
  $transdate = $dom->findnodes('//ram:IssueDateTime')->string_value;
71
  $duedate   = $dom->findnodes('//ram:DueDateDateTime')->string_value;
72
  $transdate =~ s/^\s+|\s+$//g;
73
  $duedate   =~ s/^\s+|\s+$//g;
74

  
75
  if ($transdate =~ /^[0-9]{8}$/) {
76
    $dt_to_kivi = DateTime->new(year  => substr($transdate,0,4),
77
                                month => substr ($transdate,4,2),
78
                                day   => substr($transdate,6,2))->to_kivitendo;
79
  }
80
  if ($duedate =~ /^[0-9]{8}$/) {
81
    $due_dt_to_kivi = DateTime->new(year  => substr($duedate,0,4),
82
                                    month => substr ($duedate,4,2),
83
                                    day   => substr($duedate,6,2))->to_kivitendo;
84
  }
85

  
86
  my $type = $dom->findnodes('//rsm:ExchangedDocument/ram:TypeCode')->string_value;
87

  
88
  my $dd   = $dom->findnodes('//ram:ApplicableHeaderTradeSettlement' .
89
                             '/ram:SpecifiedTradeSettlementPaymentMeans/ram:TypeCode')->string_value;
90
  my $direct_debit = $dd == 59 ? 1 : 0;
91

  
92
  my $iban = $dom->findnodes('//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans' .
93
                             '/ram:PayeePartyCreditorFinancialAccount/ram:IBANID')->string_value;
94
  my $ibanmessage;
95
  $ibanmessage = $iban ne $vendor->iban ? "Record IBAN $iban doesn't match vendor IBAN " . $vendor->iban : $iban if $iban;
96

  
97
  my $url = $self->url_for(
98
    controller                           => 'ap.pl',
99
    action                               => 'load_record_template',
100
    id                                   => $template_ap->id,
101
    'form_defaults.amount_1'             => $::form->format_amount(\%::myconfig, $total, 2),
102
    'form_defaults.transdate'            => $dt_to_kivi,
103
    'form_defaults.invnumber'            => $invnumber,
104
    'form_defaults.duedate'              => $due_dt_to_kivi,
105
    'form_defaults.no_payment_bookings'  => 0,
106
    'form_defaults.paid_1_suggestion'    => $::form->format_amount(\%::myconfig, $total, 2),
107
    'form_defaults.notes'                => "ZUGFeRD Import. Type: $type\nIBAN: " . $ibanmessage,
108
    'form_defaults.taxincluded'          => 0,
109
    'form_defaults.direct_debit'          => $direct_debit,
110
  );
111

  
112
  $self->redirect_to($url);
113

  
114
}
115

  
116
sub check_auth {
117
  $::auth->assert('ap_transactions');
118
}
119
sub setup_zugferd_action_bar {
120
  my ($self) = @_;
121

  
122
  for my $bar ($::request->layout->get('actionbar')) {
123
    $bar->add(
124
      action => [
125
        $::locale->text('Import'),
126
        submit    => [ '#form', { action => 'ZUGFeRD/import_zugferd' } ],
127
        accesskey => 'enter',
128
      ],
129
    );
130
  }
131
}
132

  
133

  
134
1;
135
__END__
136

  
137
=pod
138

  
139
=encoding utf8
140

  
141
=head1 NAME
142

  
143
SL::Controller::ZUGFeRD
144
Controller for importing ZUGFeRD pdf files to kivitendo
145

  
146
=head1 FUNCTIONS
147

  
148
=over 4
149

  
150
=item C<action_upload_zugferd>
151

  
152
Creates a web from with a single upload dialog.
153

  
154
=item C<action_import_zugferd $pdf>
155

  
156
Expects a single pdf with ZUGFeRD 2.0 metadata.
157
Checks if the param <C$pdf> is set and a valid pdf file.
158
Calls helper functions to validate and extract the ZUGFeRD data.
159
Needs a valid VAT ID (EU) for this vendor and
160
expects one ap template for this vendor in kivitendo.
161

  
162
Parses some basic ZUGFeRD data (invnumber, total net amount,
163
transdate, duedate, vendor VAT ID, IBAN) and uses the first
164
found ap template for this vendor to fill this template with
165
ZUGFeRD data.
166
If the vendor's master data contain a IBAN and the
167
ZUGFeRD record has a IBAN also these values will be compared.
168
If they  don't match a warning will be writte in ap.notes.
169
Furthermore the ZUGFeRD type code will be written to ap.notes.
170
No callback implemented.
171

  
172
=back
173

  
174
=head1 TODO and CAVEAT
175

  
176
This is just a very basic Parser for ZUGFeRD data.
177
We assume that the ZUGFeRD generator is a company with a
178
valid European VAT ID. Furthermore this vendor needs only
179
one and just noe ap template (the first match will be used).
180

  
181
The ZUGFeRD data should also be extracted in the helper package
182
and maybe a model should be used for this.
183
The user should set one ap template as a default for ZUGFeRD.
184
The ZUGFeRD pdf should be written to WebDAV or DMS.
185
If the ZUGFeRD data has a payment purpose set, this should
186
be the default for the SEPA-XML export.
187

  
188

  
189
=head1 AUTHOR
190

  
191
Jan Büren E<lt>jan@kivitendo-premium.deE<gt>,
192

  
193
=cut
locale/de/all
690 690
  'Correct taxkey'              => 'Richtiger Steuerschlüssel',
691 691
  'Cost Center'                 => 'Kostenstelle',
692 692
  'Costs'                       => 'Kosten',
693
  'Could not extract ZUGFeRD data, data and error message:' => 'Konnte keine ZUGFeRD Daten extrahieren, folgende Fehlermeldung und das PDF:',
693 694
  'Could not find an entry for this part in the pricegroup.' => 'Konnte keine Eintrag für diesen Artikel in der Preisgruppe finden.',
694 695
  'Could not load class #1 (#2): "#3"' => 'Konnte Klasse #1 (#2) nicht laden: "#3"',
695 696
  'Could not load class #1, #2' => 'Konnte Klasse #1 nicht laden: "#2"',
......
1626 1627
  'Import CSV'                  => 'CSV-Import',
1627 1628
  'Import Status'               => 'Import Status',
1628 1629
  'Import a MT940 file:'        => 'Laden Sie eine MT940 Datei hoch:',
1630
  'Import a ZUGFeRD file:'      => 'Eine ZUGFeRD-Datei importieren',
1629 1631
  'Import all'                  => 'Importiere Alle',
1630 1632
  'Import documents from #1'    => 'Importiere Dateien von Quelle \'#1\'',
1631 1633
  'Import file'                 => 'Import-Datei',
......
1990 1992
  'Next run at'                 => 'Nächste Ausführung um',
1991 1993
  'No'                          => 'Nein',
1992 1994
  'No 1:n or n:1 relation'      => 'Keine 1:n oder n:1 Beziehung',
1995
  'No AP Record Template for this vendor found, please add one' => 'Konnte keine Kreditorenbuchungsvorlage für diesen Lieferanten finden, bitte legen Sie eine an.',
1993 1996
  'No AP template was found.'   => 'Keine Kreditorenbuchungsvorlage gefunden.',
1994 1997
  'No Company Address given'    => 'Keine Firmenadresse hinterlegt!',
1995 1998
  'No Company Name given'       => 'Kein Firmenname hinterlegt!',
......
1998 2001
  'No Journal'                  => 'Kein Journal',
1999 2002
  'No Shopdescription'          => 'Keine Shop-Artikelbeschreibung',
2000 2003
  'No Shopimages'               => 'Keine Shop-Bilder',
2004
  'No VAT Info for this ZUGFeRD invoice, please ask your vendor to add this for his ZUGFeRD data.' => 'Konnte keine UST-ID für diese ZUGFeRD Rechnungen finden, bitte fragen Sie bei Ihren Lieferanten nach, ob dieses Feld im ZUGFeRD Datensatz gesetzt wird.',
2001 2005
  'No Vendor was found matching the search parameters.' => 'Zu dem Suchbegriff wurde kein Händler gefunden',
2002 2006
  'No action defined.'          => 'Keine Aktion definiert.',
2003 2007
  'No article has been selected yet.' => 'Es wurde noch kein Artikel ausgewählt.',
......
2312 2316
  'Pictures for search parts'   => 'Bilder für Warensuche',
2313 2317
  'Please Check the bank information for each customer:' => 'Bitte überprüfen Sie die Bankinformationen der Kunden:',
2314 2318
  'Please Check the bank information for each vendor:' => 'Bitte überprüfen Sie die Kontoinformationen der Lieferanten:',
2319
  'Please add a valid VAT-ID for this vendor: ' => 'Bitte prüfen Sie ob dieser Lieferant eine valide UST-ID (Großschreibungen und Leerzeichen beachten) besitzt:',
2315 2320
  'Please ask your administrator to create warehouses and bins.' => 'Bitten Sie Ihren Administrator, dass er Lager und Lagerplätze anlegt.',
2316 2321
  'Please change the partnumber of the following parts and run the update again:' => 'Bitte ändern Sie daher die Artikelnummer folgender Artikel:',
2317 2322
  'Please choose a part.'       => 'Bitte wählen Sie einen Artikel aus.',
......
4003 4008
  'Your download does not exist anymore. Please re-run the DATEV export assistant.' => 'Ihr Download existiert nicht mehr. Bitte starten Sie den DATEV-Exportassistenten erneut.',
4004 4009
  'Your import is being processed.' => 'Ihr Import wird verarbeitet',
4005 4010
  'Your target quantity will be added to the stocked quantity.' => 'Ihre gezählte Zielmenge wird zum Lagerbestand hinzugezählt.',
4011
  'ZUGFeRD import'              => 'ZUGFeRD Import',
4006 4012
  'ZUGFeRD invoice'             => 'ZUGFeRD-Rechnung',
4007 4013
  'ZUGFeRD notes for each invoice' => 'ZUGFeRD-Notizen für jede Rechnung',
4008 4014
  'Zeitraum'                    => 'Zeitraum',
......
4011 4017
  'Zip, City'                   => 'PLZ, Ort',
4012 4018
  'Zipcode'                     => 'PLZ',
4013 4019
  'Zipcode and city'            => 'PLZ und Stadt',
4020
  'ZugFeRD Import'              => 'ZUGFeRD Import',
4014 4021
  '[email]'                     => '[email]',
4015 4022
  'absolute'                    => 'absolut',
4016 4023
  'account_description'         => 'Beschreibung',
......
4045 4052
  'brutto'                      => 'brutto',
4046 4053
  'building data'               => 'Verarbeite Daten',
4047 4054
  'building report'             => 'Erstelle Bericht',
4055
  'can only parse a pdf file'   => 'Kann nur eine gültige PDF-Datei verwenden.',
4048 4056
  'cash'                        => 'Ist-Versteuerung',
4049 4057
  'chargenumber #1'             => 'Chargennummer #1',
4050 4058
  'chart_of_accounts'           => 'kontenuebersicht',
......
4149 4157
  'male'                        => 'männlich',
4150 4158
  'max filesize'                => 'maximale Dateigröße',
4151 4159
  'missing'                     => 'Fehlbestand',
4160
  'missing file for action import' => 'Es wurde keine Datei zum Hochladen ausgewählt',
4152 4161
  'missing_br'                  => 'Fehl.',
4153 4162
  'month'                       => 'Monatliche Abgabe',
4154 4163
  'monthly'                     => 'monatlich',
menus/user/00-erp.yaml
641 641
  access: general_ledger
642 642
  params:
643 643
    action: YearEndTransactions/form
644
- parent: general_ledger
645
  id: zugferd_import
646
  name: ZugFeRD Import
647
  icon: cbob
648
  order: 485
649
  access: ap_transactions
650
  params:
651
    action: ZUGFeRD/upload_zugferd
644 652
- parent: general_ledger
645 653
  id: general_ledger_reports
646 654
  name: Reports
templates/webpages/zugferd/form.html
1
[%- USE HTML %]
2
[%- USE LxERP %]
3
[%- USE L %]
4
[%- USE T8 %]
5
[%- INCLUDE 'common/flash.html' %]
6
 <div class="listtop">[% FORM.title %]</div>
7

  
8
 <p>
9
 [% "Import a ZUGFeRD file:" | $T8 %]
10
 </p>
11

  
12
 <form method="post" action="controller.pl" enctype="multipart/form-data" id="form">
13
    [% L.input_tag('file', '', type => 'file', accept => '.pdf') %]
14
 </form>
15

  

Auch abrufbar als: Unified diff