Revision 04b00d8d
Von Tamino Steinert vor etwa 1 Jahr hinzugefügt
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; |
Auch abrufbar als: Unified diff
ZUGFeRD: Kreditorenbuchung direkt aus ZUGFeRD-XML erstellen