#!/usr/bin/perl -w use strict; use XML::Twig; use FindBin qw($Bin); use lib $Bin; use wtr2_base; init_db(); my $DEBUG=0; # XML::Twig can output the updated document, whith the error messages my $CAN_OUTPUT= 1; my @files= @ARGV || (<$dir{invoices}/*.xml>); foreach my $file (@files) { my $doc= XML::Twig->new( pretty_print => 'indented')->parsefile( $file); my $errors= check_invoice( $doc); if( !@$errors) { store_invoice( $doc); } else { print "ERROR in $file\n ", join( "\n ", @$errors), "\n"; if( $CAN_OUTPUT) {my $rejected_file= rejected( $file); print "adding errors in $rejected_file\n" if( $DEBUG); add_errors( $doc, $errors); output_doc_to_check( $rejected_file, $doc); } }; } exit; sub check_invoice { my( $doc)= @_; my $root= $doc->root; my $errors=[]; # array ref, holds the error messages check_buyer( $root->first_child( 'BuyerPartyDetails')->field( 'BuyerPartyIdentifier'), $root->first_child( 'BuyerPartyDetails')->field( 'BuyerOrganisationName'), $errors ); check_po( $root->first_child( 'InvoiceDetails')->field( 'OrderIdentifier'), $errors); my @rows= $root->children( 'InvoiceRow'); reset_default_row_id(); foreach my $row( @rows) { # this does not cope well with broken row numbers my $row_id= $row->field( 'RowIdentifier') || default_row_id(); print "checking row $row_id\n" if $DEBUG; my $DeliveredQuantity = $row->first_child( 'DeliveredQuantity'); my $OrderedQuantity = $row->first_child( 'OrderedQuantity'); my $delivered_qty = $DeliveredQuantity ? $DeliveredQuantity->text : 0; my $delivered_unit = $DeliveredQuantity ? $DeliveredQuantity->att( 'QuantityUnitCode') : ''; my $ordered_qty = $OrderedQuantity ? $OrderedQuantity->text : 0; my $ordered_unit = $OrderedQuantity ? $OrderedQuantity->att( 'QuantityUnitCode') : ''; check_qtty( $row_id, $delivered_qty, $delivered_unit, $ordered_qty, $ordered_unit, $errors); } return $errors; } sub store_invoice { my( $doc)= @_; my $root= $doc->root; print "storing invoice " . $root->first_child( 'InvoiceDetails')->field('InvoiceNumber') . "\n"; # build the various data structures my $data; my $invoice = $root->first_child( 'InvoiceDetails'); $data->{invoice} = { number => $invoice->field( 'InvoiceNumber'), date => $invoice->field( 'InvoiceDate'), po => $invoice->field( 'OrderIdentifier'), amount_no_tax => $invoice->field( 'InvoiceTotalVatExcludedAmount'), tax => $invoice->field( 'InvoiceTotalVatAmount'), amount => $invoice->field( 'InvoiceTotalVatIncludedAmount'), payment_status => $root->first_child( 'PaymentStatusDetails') ->field( 'PaymentStatusCode'), }; my $seller = $root->first_child( 'SellerPartyDetails'); $data->{seller} = { identifier => $seller->field( 'SellerPartyIdentifier'), name => $seller->field( 'SellerOrganisationName'), tax_code => $seller->field( 'SellerOrganisationTaxCode'), }; my $address = $root->first_child( 'SellerPartyDetails') ->first_child( 'SellerPostalAddressDetails'); $data->{address} = { street => $address->field( 'SellerStreetName'), town => $address->field( 'SellerTownName'), zip => $address->field( 'SellerPostCodeIdentifier'), country_code => $address->field( 'CountryCode'), po_box => $address->field( 'SellerPostOfficeBoxIdentifier'), }; $data->{contact} = { name => $root->field( 'SellerContactPersonName'), phone => $root->first_child( 'SellerCommunicationDetails') ->field( 'SellerPhoneNumberIdentifier'), email => $root->first_child( 'SellerCommunicationDetails') ->field( 'SellerEmailaddressIdentifier'), }; $data->{invoicerow} ||= []; reset_default_row_id(); foreach my $invoicerow ($root->children( 'InvoiceRow')) { my $DeliveredQuantity= $invoicerow->first_child( 'DeliveredQuantity'); my $qty_unit= $DeliveredQuantity ? $DeliveredQuantity->att( 'QuantityUnitCode') : ''; push @{$data->{invoicerow}}, { row_id => $invoicerow->field( 'RowIdentifier') || default_row_id(), sku => $invoicerow->field( 'ArticleIdentifier'), name => $invoicerow->field( 'ArticleName'), qty => $invoicerow->field( 'DeliveredQuantity'), qty_unit => $qty_unit, unit_price => $invoicerow->field( 'UnitPriceAmount'), amount_no_tax => $invoicerow->field( 'RowVatExcludedAmount'), tax => $invoicerow->field( 'RowVatAmount'), amount => $invoicerow->field( 'RowAmount'), } } store_all( $data); } sub add_errors { my( $doc, $error_messages)= @_; my $errors= $doc->root->insert_new_elt( first_child => 'errors'); foreach my $message (@$error_messages) { $errors->insert_new_elt( last_child => error => $message); } return $doc; } sub output_doc_to_check { my( $file, $doc)= @_; open( FILE, ">$file") or die "cannot create file to check $file: $!"; $doc->print( \*FILE); close FILE; } __END__ =head1 NAME wtr2_twig =head1 SYNOPSYS perl wtr2_twig =head1 DESCRIPTION This code uses L to process the invoices. It uses the full-tree mode: load the entire XML document through the C method and then process it. It uses mostly navigation to access the information, the C and C methods. New elements are created using the C method. This was easy to write, but you would expect so, as I wrote XML::Twig and I am obviously quite familiar with it ;--) =head1 AUTHOR Michel Rodriguez =head1 LICENSE This code is Copyright (c) 2003 Michel Rodriguez. All rights reserved. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Comments can be sent to mirod@xmltwig.com =head1 SEE ALSO L Ways to Rome 2 - Kourallinen Dollareita: http://www.xmltwig.com/article/ways_to_rome_2/