Revision f05c4f4d
Von Tamino Steinert vor mehr als 1 Jahr hinzugefügt
SL/DB/Helper/ZUGFeRD.pm | ||
---|---|---|
use utf8;
|
||
|
||
use parent qw(Exporter);
|
||
our @EXPORT = qw(create_zugferd_data create_zugferd_xmp_data);
|
||
our @EXPORT_CREATE = qw(create_zugferd_data create_zugferd_xmp_data);
|
||
our @EXPORT_IMPORT = qw(import_zugferd_xml);
|
||
our @EXPORT_OK = (@EXPORT_CREATE, @EXPORT_IMPORT);
|
||
our %EXPORT_TAGS = (
|
||
ALL => (@EXPORT_CREATE, @EXPORT_IMPORT),
|
||
CREATE => \@EXPORT_CREATE,
|
||
IMPORT => \@EXPORT_IMPORT,
|
||
);
|
||
|
||
use SL::DB::BankAccount;
|
||
use SL::DB::GenericTranslation;
|
||
use SL::DB::Tax;
|
||
use SL::DB::TaxKey;
|
||
use SL::DB::RecordTemplate;
|
||
use SL::Helper::ISO3166;
|
||
use SL::Helper::ISO4217;
|
||
use SL::Helper::UNECERecommendation20;
|
||
... | ... | |
};
|
||
}
|
||
|
||
sub import_zugferd_xml {
|
||
my ($self, $zugferd_xml) = @_;
|
||
|
||
# 1. check if ZUGFeRD SellerTradeParty has a VAT-ID
|
||
my $ustid = $zugferd_xml->findnodes(
|
||
'//ram:SellerTradeParty/ram:SpecifiedTaxRegistration'
|
||
)->string_value;
|
||
die t8("No VAT Info for this Factur-X/ZUGFeRD invoice," .
|
||
" please ask your vendor to add this for his Factur-X/ZUGFeRD data.") unless $ustid;
|
||
|
||
$ustid = SL::VATIDNr->normalize($ustid);
|
||
|
||
# 1.1 check if we a have a vendor with this VAT-ID (vendor.ustid)
|
||
my $vendor_name = $zugferd_xml->findnodes('//ram:SellerTradeParty/ram:Name')->string_value;
|
||
my $vendor = SL::DB::Manager::Vendor->find_by(
|
||
ustid => $ustid,
|
||
or => [
|
||
obsolete => undef,
|
||
obsolete => 0,
|
||
]);
|
||
|
||
if (!$vendor) {
|
||
# 1.2 If no vendor with the exact VAT ID number is found, the
|
||
# number might be stored slightly different in the database
|
||
# (e.g. with spaces breaking up groups of numbers). Iterate over
|
||
# all existing vendors with VAT ID numbers, normalize their
|
||
# representation and compare those.
|
||
|
||
my $vendors = SL::DB::Manager::Vendor->get_all(
|
||
where => [
|
||
'!ustid' => undef,
|
||
'!ustid' => '',
|
||
or => [
|
||
obsolete => undef,
|
||
obsolete => 0,
|
||
],
|
||
]);
|
||
|
||
foreach my $other_vendor (@{ $vendors }) {
|
||
next unless SL::VATIDNr->normalize($other_vendor->ustid) eq $ustid;
|
||
|
||
$vendor = $other_vendor;
|
||
last;
|
||
}
|
||
}
|
||
|
||
die t8("Please add a valid VAT-ID for this vendor: #1", $vendor_name)
|
||
unless (ref $vendor eq 'SL::DB::Vendor');
|
||
|
||
# 2. check if we have a ap record template for this vendor (TODO only the oldest template is choosen)
|
||
my $template_ap = SL::DB::Manager::RecordTemplate->get_first(where => [vendor_id => $vendor->id]);
|
||
die t8("No AP Record Template for vendor #1 found, please add one", $vendor_name)
|
||
unless (ref $template_ap eq 'SL::DB::RecordTemplate');
|
||
|
||
|
||
# 3. parse the zugferd data and fill the ap record template
|
||
# -> no need to check sign (credit notes will be negative) just record thei ZUGFeRD type in ap.notes
|
||
# -> check direct debit (defaults to no)
|
||
# -> set amount (net amount) and unset taxincluded
|
||
# (template and user cares for tax and if there is more than one booking accno)
|
||
# -> date (can be empty)
|
||
# -> duedate (may be empty)
|
||
# -> compare record iban and generate a warning if this differs from vendor's master data iban
|
||
my $total = $zugferd_xml->findnodes(
|
||
'//ram:SpecifiedTradeSettlementHeaderMonetarySummation' .
|
||
'/ram:TaxBasisTotalAmount'
|
||
)->string_value;
|
||
|
||
my $invnumber = $zugferd_xml->findnodes(
|
||
'//rsm:ExchangedDocument/ram:ID'
|
||
)->string_value;
|
||
|
||
# parse dates to kivi if set/valid
|
||
my %dates = (
|
||
transdate => {
|
||
key => '//ram:IssueDateTime',
|
||
value => undef,
|
||
},
|
||
duedate => {
|
||
key => '//ram:DueDateDateTime',
|
||
value => undef,
|
||
},
|
||
);
|
||
foreach my $date (keys %dates) {
|
||
my $string_value = $zugferd_xml->findnodes($dates{$date}->{key})->string_value;
|
||
$string_value =~ s/^\s+|\s+$//g;
|
||
if ($string_value =~ /^[0-9]{8}$/) {
|
||
$dates{$date}->{value} = DateTime->new(
|
||
year => substr($string_value,0,4),
|
||
month => substr ($string_value,4,2),
|
||
day => substr($string_value,6,2)
|
||
)->to_kivitendo;
|
||
}
|
||
}
|
||
my $transdate = $dates{transdate}->{value};
|
||
my $duedate = $dates{duedate}->{value};
|
||
|
||
my $type = $zugferd_xml->findnodes(
|
||
'//rsm:ExchangedDocument/ram:TypeCode'
|
||
)->string_value;
|
||
|
||
my $dd = $zugferd_xml->findnodes(
|
||
'//ram:ApplicableHeaderTradeSettlement' .
|
||
'/ram:SpecifiedTradeSettlementPaymentMeans/ram:TypeCode'
|
||
)->string_value;
|
||
my $direct_debit = $dd == 59 ? 1 : 0;
|
||
|
||
my $iban = $zugferd_xml->findnodes(
|
||
'//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans' .
|
||
'/ram:PayeePartyCreditorFinancialAccount/ram:IBANID'
|
||
)->string_value;
|
||
|
||
my $ibanmessage;
|
||
$ibanmessage = $iban ne $vendor->iban ?
|
||
"Record IBAN $iban doesn't match vendor IBAN " . $vendor->iban
|
||
: $iban if $iban;
|
||
|
||
# write values to self
|
||
my $today = DateTime->today_local;
|
||
|
||
my $additional_notes = "ZUGFeRD Import. Type: $type\nIBAN: " . $ibanmessage;
|
||
|
||
my %params = (
|
||
invoice => 0,
|
||
vendor_id => $vendor->id,
|
||
taxzone_id => $vendor->taxzone_id,
|
||
currency_id => $template_ap->currency_id,
|
||
direct_debit => $direct_debit,
|
||
globalproject_id => $template_ap->project_id,
|
||
payment_id => $template_ap->payment_id,
|
||
invnumber => $invnumber,
|
||
transdate => $transdate || $today->to_kivitendo,
|
||
duedate => $duedate || (
|
||
$template_ap->vendor->payment ?
|
||
$template_ap->vendor->payment->calc_date(reference_date => $today)->to_kivitendo
|
||
: $today->to_kivitendo
|
||
),
|
||
department_id => $template_ap->department_id,
|
||
ordnumber => $template_ap->ordnumber,
|
||
taxincluded => 0,
|
||
notes => join("\n", $template_ap->notes, $additional_notes),
|
||
transactions => [],
|
||
transaction_description => $template_ap->transaction_description,
|
||
);
|
||
|
||
$self->assign_attributes(%params);
|
||
|
||
foreach my $template_item (@{$template_ap->items}) {
|
||
my %line_params = (
|
||
amount => $total,
|
||
project_id => $template_item->project_id,
|
||
tax_id => $template_item->tax_id,
|
||
chart => $template_item->chart,
|
||
);
|
||
|
||
$self->add_ap_amount_row(%line_params);
|
||
}
|
||
$self->recalculate_amounts();
|
||
|
||
my $ap_chart = SL::DB::Manager::Chart->get_first(
|
||
where => [id => $template_ap->ar_ap_chart_id]
|
||
);
|
||
$self->create_ap_row(chart => $ap_chart);
|
||
|
||
return $self;
|
||
}
|
||
|
||
1;
|
Auch abrufbar als: Unified diff
ZUGFeRD: Kreditorenbuchung direkt aus ZUGFeRD-XML erstellen