Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision edb6e61e

Von Tamino Steinert vor 11 Monaten hinzugefügt

  • ID edb6e61e6a84957253e3afeecfd7d69d5b3c008b
  • Vorgänger 33475bc9
  • Nachfolger 687f2511

ZUGFeRD: Kreditorenbuchung direkt aus ZUGFeRD-XML erstellen

Unterschiede anzeigen:

SL/DB/Helper/ZUGFeRD.pm
4 4
use utf8;
5 5

  
6 6
use parent qw(Exporter);
7
our @EXPORT = qw(create_zugferd_data create_zugferd_xmp_data);
7
our @EXPORT_CREATE = qw(create_zugferd_data create_zugferd_xmp_data);
8
our @EXPORT_IMPORT = qw(import_zugferd_xml);
9
our @EXPORT_OK = (@EXPORT_CREATE, @EXPORT_IMPORT);
10
our %EXPORT_TAGS     = (
11
  ALL => (@EXPORT_CREATE, @EXPORT_IMPORT),
12
  CREATE => \@EXPORT_CREATE,
13
  IMPORT => \@EXPORT_IMPORT,
14
);
8 15

  
9 16
use SL::DB::BankAccount;
10 17
use SL::DB::GenericTranslation;
11 18
use SL::DB::Tax;
12 19
use SL::DB::TaxKey;
20
use SL::DB::RecordTemplate;
13 21
use SL::Helper::ISO3166;
14 22
use SL::Helper::ISO4217;
15 23
use SL::Helper::UNECERecommendation20;
......
700 708
  };
701 709
}
702 710

  
711
sub import_zugferd_xml {
712
  my ($self, $zugferd_xml) = @_;
713

  
714
  # 1. check if ZUGFeRD SellerTradeParty has a VAT-ID
715
  my $ustid = $zugferd_xml->findnodes(
716
    '//ram:SellerTradeParty/ram:SpecifiedTaxRegistration'
717
  )->string_value;
718
  die t8("No VAT Info for this Factur-X/ZUGFeRD invoice," .
719
         " please ask your vendor to add this for his Factur-X/ZUGFeRD data.") unless $ustid;
720

  
721
  $ustid = SL::VATIDNr->normalize($ustid);
722

  
723
  # 1.1 check if we a have a vendor with this VAT-ID (vendor.ustid)
724
  my $vendor_name = $zugferd_xml->findnodes('//ram:SellerTradeParty/ram:Name')->string_value;
725
  my $vendor = SL::DB::Manager::Vendor->find_by(
726
    ustid => $ustid,
727
    or    => [
728
      obsolete => undef,
729
      obsolete => 0,
730
    ]);
731

  
732
  if (!$vendor) {
733
    # 1.2 If no vendor with the exact VAT ID number is found, the
734
    # number might be stored slightly different in the database
735
    # (e.g. with spaces breaking up groups of numbers). Iterate over
736
    # all existing vendors with VAT ID numbers, normalize their
737
    # representation and compare those.
738

  
739
    my $vendors = SL::DB::Manager::Vendor->get_all(
740
      where => [
741
        '!ustid' => undef,
742
        '!ustid' => '',
743
        or       => [
744
          obsolete => undef,
745
          obsolete => 0,
746
        ],
747
      ]);
748

  
749
    foreach my $other_vendor (@{ $vendors }) {
750
      next unless SL::VATIDNr->normalize($other_vendor->ustid) eq $ustid;
751

  
752
      $vendor = $other_vendor;
753
      last;
754
    }
755
  }
756

  
757
  die t8("Please add a valid VAT-ID for this vendor: #1", $vendor_name)
758
    unless (ref $vendor eq 'SL::DB::Vendor');
759

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

  
765

  
766
  # 3. parse the zugferd data and fill the ap record template
767
  # -> no need to check sign (credit notes will be negative) just record thei ZUGFeRD type in ap.notes
768
  # -> check direct debit (defaults to no)
769
  # -> set amount (net amount) and unset taxincluded
770
  #    (template and user cares for tax and if there is more than one booking accno)
771
  # -> date (can be empty)
772
  # -> duedate (may be empty)
773
  # -> compare record iban and generate a warning if this differs from vendor's master data iban
774
  my $total = $zugferd_xml->findnodes(
775
    '//ram:SpecifiedTradeSettlementHeaderMonetarySummation' .
776
    '/ram:TaxBasisTotalAmount'
777
  )->string_value;
778

  
779
  my $invnumber = $zugferd_xml->findnodes(
780
    '//rsm:ExchangedDocument/ram:ID'
781
  )->string_value;
782

  
783
  # parse dates to kivi if set/valid
784
  my %dates = (
785
    transdate => {
786
      key => '//ram:IssueDateTime',
787
      value => undef,
788
    },
789
    duedate   => {
790
      key => '//ram:DueDateDateTime',
791
      value => undef,
792
    },
793
  );
794
  foreach my $date (keys %dates) {
795
    my $string_value = $zugferd_xml->findnodes($dates{$date}->{key})->string_value;
796
    $string_value =~ s/^\s+|\s+$//g;
797
    if ($string_value =~ /^[0-9]{8}$/) {
798
      $dates{$date}->{value} = DateTime->new(
799
        year  => substr($string_value,0,4),
800
        month => substr ($string_value,4,2),
801
        day   => substr($string_value,6,2)
802
      )->to_kivitendo;
803
    }
804
  }
805
  my $transdate = $dates{transdate}->{value};
806
  my $duedate   = $dates{duedate}->{value};
807

  
808
  my $type = $zugferd_xml->findnodes(
809
    '//rsm:ExchangedDocument/ram:TypeCode'
810
  )->string_value;
811

  
812
  my $dd   = $zugferd_xml->findnodes(
813
    '//ram:ApplicableHeaderTradeSettlement' .
814
    '/ram:SpecifiedTradeSettlementPaymentMeans/ram:TypeCode'
815
  )->string_value;
816
  my $direct_debit = $dd == 59 ? 1 : 0;
817

  
818
  my $iban = $zugferd_xml->findnodes(
819
    '//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans' .
820
    '/ram:PayeePartyCreditorFinancialAccount/ram:IBANID'
821
  )->string_value;
822

  
823
  my $ibanmessage;
824
  $ibanmessage = $iban ne $vendor->iban ?
825
      "Record IBAN $iban doesn't match vendor IBAN " . $vendor->iban
826
    : $iban if $iban;
827

  
828
  # write values to self
829
  my $today = DateTime->today_local;
830

  
831
  my $additional_notes = "ZUGFeRD Import. Type: $type\nIBAN: " . $ibanmessage;
832

  
833
  my %params = (
834
    invoice      => 0,
835
    vendor_id   => $vendor->id,
836
    taxzone_id   => $vendor->taxzone_id,
837
    currency_id => $template_ap->currency_id,
838
    direct_debit => $direct_debit,
839
    globalproject_id  => $template_ap->project_id,
840
    payment_id  => $template_ap->payment_id,
841
    invnumber   => $invnumber,
842
    transdate   => $transdate || $today->to_kivitendo,
843
    duedate     => $duedate   || (
844
      $template_ap->vendor->payment ?
845
        $template_ap->vendor->payment->calc_date(reference_date => $today)->to_kivitendo
846
      : $today->to_kivitendo
847
    ),
848
    department_id => $template_ap->department_id,
849
    ordnumber => $template_ap->ordnumber,
850
    taxincluded => 0,
851
    notes    => join("\n", $template_ap->notes, $additional_notes),
852
    transactions => [],
853
    transaction_description => $template_ap->transaction_description,
854
  );
855

  
856
  $self->assign_attributes(%params);
857

  
858
  foreach my $template_item (@{$template_ap->items}) {
859
    my %line_params = (
860
      amount => $total,
861
      project_id => $template_item->project_id,
862
      tax_id => $template_item->tax_id,
863
      chart  => $template_item->chart,
864
    );
865

  
866
    $self->add_ap_amount_row(%line_params);
867
  }
868
  $self->recalculate_amounts();
869

  
870
  my $ap_chart = SL::DB::Manager::Chart->get_first(
871
    where => [id => $template_ap->ar_ap_chart_id]
872
  );
873
  $self->create_ap_row(chart => $ap_chart);
874

  
875
  return $self;
876
}
877

  
703 878
1;
SL/DB/Invoice.pm
19 19
use SL::DB::Helper::RecordLink qw(RECORD_ID RECORD_TYPE_REF RECORD_ITEM_ID RECORD_ITEM_TYPE_REF);
20 20
use SL::DB::Helper::SalesPurchaseInvoice;
21 21
use SL::DB::Helper::TransNumberGenerator;
22
use SL::DB::Helper::ZUGFeRD;
22
use SL::DB::Helper::ZUGFeRD qw(:CREATE);
23 23
use SL::Locale::String qw(t8);
24 24

  
25 25
__PACKAGE__->meta->add_relationship(
SL/DB/PurchaseInvoice.pm
14 14
use SL::DB::Helper::Payment qw(:ALL);
15 15
use SL::DB::Helper::RecordLink qw(RECORD_ID RECORD_TYPE_REF RECORD_ITEM_ID RECORD_ITEM_TYPE_REF);
16 16
use SL::DB::Helper::SalesPurchaseInvoice;
17
use SL::DB::Helper::ZUGFeRD qw(:IMPORT);
17 18
use SL::Locale::String qw(t8);
18 19
use Rose::DB::Object::Helpers qw(has_loaded_related forget_related as_tree strip);
19 20

  
......
159 160
  return $reclamation;
160 161
}
161 162

  
163
sub create_from_zugferd_xml {
164
  my ($class, $zugferd_xml) = @_;
165

  
166
  my $ap_invoice = $class->new();
167

  
168
  $ap_invoice->import_zugferd_xml($zugferd_xml)->save();
169
}
170

  
162 171
sub create_ap_row {
163 172
  my ($self, %params) = @_;
164 173
  # needs chart as param
locale/de/all
2434 2434
  'No'                          => 'Nein',
2435 2435
  'No 1:n or n:1 relation'      => 'Keine 1:n oder n:1 Beziehung',
2436 2436
  'No AP Record Template for vendor \'#1\' found.' => 'Keine Kreditorenbuchungsvorlage für Lieferant \'#1\' gefunden.',
2437
  'No AP Record Template for vendor #1 found, please add one' => 'Kein Belegvorlage für Lieferant #1 gefunden, bitte eine anlegen',
2437 2438
  'No AP template was found.'   => 'Keine Kreditorenbuchungsvorlage gefunden.',
2438 2439
  'No Billing and ship to address, for Order Number #1 with ID Billing #2 and ID Shipping #3' => 'Keine Rechnungs- und Lieferadresse zur Bestellnummer #1 mit Rechnungs-ID #2 und Liefer-ID #3 gefunden',
2439 2440
  'No Company Address given'    => 'Keine Firmenadresse hinterlegt!',
locale/en/all
2433 2433
  'No'                          => '',
2434 2434
  'No 1:n or n:1 relation'      => '',
2435 2435
  'No AP Record Template for vendor \'#1\' found.' => '',
2436
  'No AP Record Template for this vendor found, please add one' => '',
2437
  'No AP Record Template for vendor #1 found, please add one' => '',
2436 2438
  'No AP template was found.'   => '',
2437 2439
  'No Billing and ship to address, for Order Number #1 with ID Billing #2 and ID Shipping #3' => '',
2438 2440
  'No Company Address given'    => '',

Auch abrufbar als: Unified diff