Factur-x specs say /AFRelationship must be /Data (and not /Alternative)
Add support for additionnal attachments (via generate_facturx_from_file()) Add factur-x lib version in Creator metadata table Add /PageMode = /UseAttachments, so that the attachments are displayed by default when opening Factur-X PDF invoice with Acrobat Reader Improve and enrich PDF objects (ModDate, CheckSum, Size) Better variable names in the code that generate PDF objects Update Factur-X XSD to v1.0 final
This commit is contained in:
parent
3eaf3852cd
commit
a136ef46db
16
MANIFEST.in
16
MANIFEST.in
|
@ -4,13 +4,13 @@ include facturx/xsd/zugferd/ZUGFeRD1p0_urn_un_unece_uncefact_data_standard_Reusa
|
|||
include facturx/xsd/zugferd/ZUGFeRD1p0_urn_un_unece_uncefact_data_standard_QualifiedDataType_12.xsd
|
||||
include facturx/xsd/zugferd/ZUGFeRD1p0_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_15.xsd
|
||||
include facturx/xsd/zugferd/ZUGFeRD1p0.xsd
|
||||
include facturx/xsd/factur-x/Factur-X_BASIC_WL_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd
|
||||
include facturx/xsd/factur-x/Factur-X_EN16931_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd
|
||||
include facturx/xsd/factur-x/Factur-X_EN16931_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd
|
||||
include facturx/xsd/factur-x/Factur-X_BASIC_WL_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd
|
||||
include facturx/xsd/factur-x/Factur-X_EN16931.xsd
|
||||
include facturx/xsd/factur-x/Factur-X_EN16931_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd
|
||||
include facturx/xsd/factur-x/Factur-X_BASIC_WL_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd
|
||||
include facturx/xsd/factur-x/Factur-X_BASIC_WL.xsd
|
||||
include facturx/xsd/factur-x/FACTUR-X_BASIC-WL_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd
|
||||
include facturx/xsd/factur-x/FACTUR-X_BASIC-WL_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd
|
||||
include facturx/xsd/factur-x/FACTUR-X_BASIC-WL_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd
|
||||
include facturx/xsd/factur-x/FACTUR-X_BASIC-WL.xsd
|
||||
include facturx/xsd/factur-x/FACTUR-X_EN16931_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd
|
||||
include facturx/xsd/factur-x/FACTUR-X_EN16931_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd
|
||||
include facturx/xsd/factur-x/FACTUR-X_EN16931_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd
|
||||
include facturx/xsd/factur-x/FACTUR-X_EN16931.xsd
|
||||
include facturx/xmp/ZUGFeRD_extension_schema.xmp
|
||||
include facturx/xmp/Factur-X_extension_schema.xmp
|
||||
|
|
|
@ -10,8 +10,8 @@ import logging
|
|||
from os.path import isfile, isdir
|
||||
|
||||
__author__ = "Alexis de Lattre <alexis.delattre@akretion.com>"
|
||||
__date__ = "August 2017"
|
||||
__version__ = "0.2"
|
||||
__date__ = "March 2018"
|
||||
__version__ = "0.3"
|
||||
|
||||
options = [
|
||||
{'names': ('-l', '--log-level'), 'dest': 'log_level',
|
||||
|
@ -68,15 +68,16 @@ def main(options, arguments):
|
|||
log_level, ', '.join(log_map.keys()))
|
||||
sys.exit(1)
|
||||
|
||||
if len(arguments) != 3:
|
||||
if len(arguments) < 3:
|
||||
logger.error(
|
||||
'This command requires 3 arguments (%d used). '
|
||||
'This command requires at least 3 arguments (%d used). '
|
||||
'Use --help to get the details.', len(arguments))
|
||||
sys.exit(1)
|
||||
pdf_filename = arguments[0]
|
||||
xml_filename = arguments[1]
|
||||
facturx_pdf_filename = arguments[2]
|
||||
for filename in [pdf_filename, xml_filename]:
|
||||
additional_attachment_filenames = arguments[3:]
|
||||
for filename in [pdf_filename, xml_filename] + additional_attachment_filenames:
|
||||
if not isfile(filename):
|
||||
logger.error('Argument %s is not a filename', filename)
|
||||
sys.exit(1)
|
||||
|
@ -105,24 +106,30 @@ def main(options, arguments):
|
|||
if isfile(facturx_pdf_filename):
|
||||
logger.warn(
|
||||
'File %s already exists. Overwriting it!', facturx_pdf_filename)
|
||||
additional_attachments = {}
|
||||
for additional_attachment_filename in additional_attachment_filenames:
|
||||
additional_attachments[additional_attachment_filename] = '' # desc
|
||||
try:
|
||||
generate_facturx_from_file(
|
||||
pdf_filename, xml_file, check_xsd=check_xsd,
|
||||
facturx_level=options.facturx_level, pdf_metadata=pdf_metadata,
|
||||
output_pdf_file=facturx_pdf_filename)
|
||||
output_pdf_file=facturx_pdf_filename,
|
||||
additional_attachments=additional_attachments)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
usage = "Usage: facturx-pdfgen <regular_invoice.pdf> <factur-x_xml.xml> "\
|
||||
"<facturx_invoice.pdf>\n"\
|
||||
"<facturx_invoice.pdf> <optional_attachment1.xyz> "\
|
||||
"<optional_attach2.abc> [...]\n"\
|
||||
"\nIf you use one of the --meta-* arguments, you should specify "\
|
||||
"all the meta-* arguments because the default values for "\
|
||||
"metadata only apply if none of the meta-* arguments are used."
|
||||
epilog = "Author: %s\n\nVersion: %s" % (__author__, __version__)
|
||||
description = "This script generate a Factur-X PDF invoice from a "\
|
||||
"regular PDF invoice and a Factur-X XML file."
|
||||
"regular PDF invoice and a Factur-X XML file."\
|
||||
"It can also include additional embedded files in the PDF."
|
||||
parser = OptionParser(usage=usage, epilog=epilog, description=description)
|
||||
for option in options:
|
||||
param = option['names']
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .facturx import generate_facturx_from_file, generate_facturx_from_binary, get_facturx_flavor, get_facturx_level, check_facturx_xsd, get_facturx_xml_from_pdf
|
||||
from ._version import __version__
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
__version__ = '0.4'
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# © 2016-2017, Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# Copyright 2016-2018, Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
|
@ -28,6 +28,7 @@
|
|||
# - have both python2 and python3 support
|
||||
# - add automated tests (currently, we only have tests at odoo module level)
|
||||
|
||||
from ._version import __version__
|
||||
from io import BytesIO
|
||||
from lxml import etree
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
@ -36,6 +37,9 @@ from PyPDF2 import PdfFileWriter, PdfFileReader
|
|||
from PyPDF2.generic import DictionaryObject, DecodedStreamObject,\
|
||||
NameObject, createStringObject, ArrayObject
|
||||
from pkg_resources import resource_filename
|
||||
import os.path
|
||||
import mimetypes
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
FORMAT = '%(asctime)s [%(levelname)s] %(message)s'
|
||||
|
@ -45,10 +49,10 @@ logger.setLevel(logging.INFO)
|
|||
|
||||
FACTURX_FILENAME = 'factur-x.xml'
|
||||
FACTURX_LEVEL2xsd = {
|
||||
'minimum': 'Factur-X_BASIC_WL.xsd',
|
||||
'basicwl': 'Factur-X_BASIC_WL.xsd',
|
||||
'basic': 'Factur-X_EN16931.xsd',
|
||||
'en16931': 'Factur-X_EN16931.xsd', # comfort
|
||||
'minimum': 'FACTUR-X_BASIC-WL.xsd',
|
||||
'basicwl': 'FACTUR-X_BASIC-WL.xsd',
|
||||
'basic': 'FACTUR-X_EN16931.xsd',
|
||||
'en16931': 'FACTUR-X_EN16931.xsd', # comfort
|
||||
}
|
||||
FACTURX_LEVEL2xmp = {
|
||||
'minimum': 'MINIMUM',
|
||||
|
@ -185,10 +189,11 @@ def get_facturx_xml_from_pdf(pdf_invoice, check_xsd=True):
|
|||
return (xml_filename, xml_string)
|
||||
|
||||
|
||||
def _get_pdf_timestamp():
|
||||
now_dt = datetime.now()
|
||||
def _get_pdf_timestamp(date=None):
|
||||
if date is None:
|
||||
date = datetime.now()
|
||||
# example date format: "D:20141006161354+02'00'"
|
||||
pdf_date = now_dt.strftime("D:%Y%m%d%H%M%S+00'00'")
|
||||
pdf_date = date.strftime("D:%Y%m%d%H%M%S+00'00'")
|
||||
return pdf_date
|
||||
|
||||
|
||||
|
@ -204,7 +209,8 @@ def _prepare_pdf_metadata_txt(pdf_metadata):
|
|||
info_dict = {
|
||||
'/Author': pdf_metadata.get('author', ''),
|
||||
'/CreationDate': pdf_date,
|
||||
'/Creator': u'factur-x Python lib by Alexis de Lattre',
|
||||
'/Creator':
|
||||
u'factur-x Python lib v%s by Alexis de Lattre' % __version__,
|
||||
'/Keywords': pdf_metadata.get('keywords', ''),
|
||||
'/ModDate': pdf_date,
|
||||
'/Subject': pdf_metadata.get('subject', ''),
|
||||
|
@ -296,62 +302,126 @@ def _prepare_pdf_metadata_xml(facturx_level, pdf_metadata):
|
|||
return xml_str
|
||||
|
||||
|
||||
# def createByteObject(string):
|
||||
# string_to_encode = u'\ufeff' + string
|
||||
# x = string_to_encode.encode('utf-16be')
|
||||
# return ByteStringObject(x)
|
||||
|
||||
|
||||
def _filespec_additional_attachments(pdf_filestream, file_dict, file_bin):
|
||||
filename = file_dict['filename']
|
||||
logger.debug('_filespec_additional_attachments filename=%s', filename)
|
||||
mod_date_pdf = _get_pdf_timestamp(file_dict['mod_date'])
|
||||
md5sum = hashlib.md5(file_bin).hexdigest()
|
||||
md5sum_obj = createStringObject(md5sum)
|
||||
params_dict = DictionaryObject({
|
||||
NameObject('/CheckSum'): md5sum_obj,
|
||||
NameObject('/ModDate'): createStringObject(mod_date_pdf),
|
||||
NameObject('/Size'): NameObject(str(len(file_bin))),
|
||||
})
|
||||
file_entry = DecodedStreamObject()
|
||||
file_entry.setData(file_bin)
|
||||
file_mimetype = mimetypes.guess_type(filename)[0]
|
||||
if not file_mimetype:
|
||||
file_mimetype = 'application/octet-stream'
|
||||
file_mimetype_insert = '/' + file_mimetype.replace('/', '#2f')
|
||||
file_entry.update({
|
||||
NameObject("/Type"): NameObject("/EmbeddedFile"),
|
||||
NameObject("/Params"): params_dict,
|
||||
NameObject("/Subtype"): NameObject(file_mimetype_insert),
|
||||
})
|
||||
file_entry_obj = pdf_filestream._addObject(file_entry)
|
||||
ef_dict = DictionaryObject({
|
||||
NameObject("/F"): file_entry_obj,
|
||||
})
|
||||
fname_obj = createStringObject(filename)
|
||||
filespec_dict = DictionaryObject({
|
||||
NameObject("/AFRelationship"): NameObject("/Unspecified"),
|
||||
NameObject("/Desc"): createStringObject(file_dict.get('desc', '')),
|
||||
NameObject("/Type"): NameObject("/Filespec"),
|
||||
NameObject("/F"): fname_obj,
|
||||
NameObject("/EF"): ef_dict,
|
||||
NameObject("/UF"): fname_obj,
|
||||
})
|
||||
filespec_obj = pdf_filestream._addObject(filespec_dict)
|
||||
return (filespec_obj, fname_obj)
|
||||
|
||||
|
||||
def _facturx_update_metadata_add_attachment(
|
||||
pdf_filestream, facturx_xml_str, pdf_metadata, facturx_level):
|
||||
pdf_filestream, facturx_xml_str, pdf_metadata, facturx_level,
|
||||
additional_attachments={}):
|
||||
'''This method is inspired from the code of the addAttachment()
|
||||
method of the PyPDF2 lib'''
|
||||
# The entry for the file
|
||||
moddate = DictionaryObject()
|
||||
moddate.update({
|
||||
NameObject('/ModDate'): createStringObject(_get_pdf_timestamp())})
|
||||
md5sum = hashlib.md5(facturx_xml_str).hexdigest()
|
||||
md5sum_obj = createStringObject(md5sum)
|
||||
params_dict = DictionaryObject({
|
||||
NameObject('/CheckSum'): md5sum_obj,
|
||||
NameObject('/ModDate'): createStringObject(_get_pdf_timestamp()),
|
||||
NameObject('/Size'): NameObject(str(len(facturx_xml_str))),
|
||||
})
|
||||
file_entry = DecodedStreamObject()
|
||||
file_entry.setData(facturx_xml_str)
|
||||
file_entry.setData(facturx_xml_str) # here we integrate the file itself
|
||||
file_entry.update({
|
||||
NameObject("/Type"): NameObject("/EmbeddedFile"),
|
||||
NameObject("/Params"): moddate,
|
||||
NameObject("/Params"): params_dict,
|
||||
# 2F is '/' in hexadecimal
|
||||
NameObject("/Subtype"): NameObject("/text#2Fxml"),
|
||||
})
|
||||
file_entry_obj = pdf_filestream._addObject(file_entry)
|
||||
# The Filespec entry
|
||||
efEntry = DictionaryObject()
|
||||
efEntry.update({
|
||||
ef_dict = DictionaryObject({
|
||||
NameObject("/F"): file_entry_obj,
|
||||
NameObject('/UF'): file_entry_obj,
|
||||
})
|
||||
|
||||
fname_obj = createStringObject(FACTURX_FILENAME)
|
||||
filespec = DictionaryObject()
|
||||
filespec.update({
|
||||
NameObject("/AFRelationship"): NameObject("/Alternative"),
|
||||
filespec_dict = DictionaryObject({
|
||||
NameObject("/AFRelationship"): NameObject("/Data"),
|
||||
NameObject("/Desc"): createStringObject("Factur-X Invoice"),
|
||||
NameObject("/Type"): NameObject("/Filespec"),
|
||||
NameObject("/F"): fname_obj,
|
||||
NameObject("/EF"): efEntry,
|
||||
NameObject("/EF"): ef_dict,
|
||||
NameObject("/UF"): fname_obj,
|
||||
})
|
||||
embeddedFilesNamesDictionary = DictionaryObject()
|
||||
embeddedFilesNamesDictionary.update({
|
||||
NameObject("/Names"): ArrayObject(
|
||||
[fname_obj, pdf_filestream._addObject(filespec)])
|
||||
filespec_obj = pdf_filestream._addObject(filespec_dict)
|
||||
name_arrayobj_content = [fname_obj, filespec_obj]
|
||||
for attach_bin, attach_dict in additional_attachments.items():
|
||||
additional_filespec_obj, additional_fname_obj =\
|
||||
_filespec_additional_attachments(
|
||||
pdf_filestream, attach_dict, attach_bin)
|
||||
name_arrayobj_content += [
|
||||
additional_fname_obj,
|
||||
additional_filespec_obj,
|
||||
]
|
||||
embedded_files_names_dict = DictionaryObject({
|
||||
NameObject("/Names"): ArrayObject(name_arrayobj_content),
|
||||
})
|
||||
embedded_files_names_obj = pdf_filestream._addObject(
|
||||
embedded_files_names_dict)
|
||||
# Then create the entry for the root, as it needs a
|
||||
# reference to the Filespec
|
||||
embeddedFilesDictionary = DictionaryObject()
|
||||
embeddedFilesDictionary.update({
|
||||
NameObject("/EmbeddedFiles"): embeddedFilesNamesDictionary
|
||||
embedded_files_dict = DictionaryObject({
|
||||
NameObject("/EmbeddedFiles"): embedded_files_names_obj,
|
||||
})
|
||||
embedded_files_obj = pdf_filestream._addObject(embedded_files_dict)
|
||||
# Update the root
|
||||
metadata_xml_str = _prepare_pdf_metadata_xml(facturx_level, pdf_metadata)
|
||||
metadata_file_entry = DecodedStreamObject()
|
||||
metadata_file_entry.setData(metadata_xml_str)
|
||||
metadata_value = pdf_filestream._addObject(metadata_file_entry)
|
||||
af_value = pdf_filestream._addObject(
|
||||
ArrayObject([pdf_filestream._addObject(filespec)]))
|
||||
metadata_file_entry.update({
|
||||
NameObject('/Subtype'): NameObject('/XML'),
|
||||
NameObject('/Type'): NameObject('/Metadata'),
|
||||
})
|
||||
metadata_obj = pdf_filestream._addObject(metadata_file_entry)
|
||||
af_value_obj = pdf_filestream._addObject(
|
||||
ArrayObject([filespec_obj]))
|
||||
pdf_filestream._root_object.update({
|
||||
NameObject("/AF"): af_value,
|
||||
NameObject("/Metadata"): metadata_value,
|
||||
NameObject("/Names"): embeddedFilesDictionary,
|
||||
NameObject("/AF"): af_value_obj,
|
||||
NameObject("/Metadata"): metadata_obj,
|
||||
NameObject("/Names"): embedded_files_obj,
|
||||
# show attachments when opening PDF
|
||||
NameObject("/PageMode"): NameObject("/UseAttachments"),
|
||||
})
|
||||
metadata_txt_dict = _prepare_pdf_metadata_txt(pdf_metadata)
|
||||
pdf_filestream.addMetadata(metadata_txt_dict)
|
||||
|
@ -499,7 +569,8 @@ def generate_facturx_from_binary(
|
|||
|
||||
def generate_facturx_from_file(
|
||||
pdf_invoice, facturx_xml, facturx_level='autodetect',
|
||||
check_xsd=True, pdf_metadata=None, output_pdf_file=None):
|
||||
check_xsd=True, pdf_metadata=None, output_pdf_file=None,
|
||||
additional_attachments=None):
|
||||
"""
|
||||
Generate a Factur-X invoice from a regular PDF invoice and a factur-X XML
|
||||
file. The method uses a file as input (regular PDF invoice) and re-writes
|
||||
|
@ -533,9 +604,13 @@ def generate_facturx_from_file(
|
|||
If you pass the pdf_metadata argument, you will not use the automatic
|
||||
generation based on the extraction of the Factur-X XML file, which will
|
||||
bring a very small perf improvement.
|
||||
:type pdf_metadata: dict
|
||||
:param output_pdf_file: File Path to the output Factur-X PDF file
|
||||
:type output_pdf_file: string or unicode
|
||||
:type pdf_metadata: dict
|
||||
:param additional_attachments: Specify the other files that you want to
|
||||
embed in the PDF file. It is a dict where keys are filepath and value
|
||||
is the description of the file (as unicode or string).
|
||||
:type additional_attachments: dict
|
||||
:return: Returns True. This method re-writes the input PDF invoice file,
|
||||
unless if the output_pdf_file is provided.
|
||||
:rtype: bool
|
||||
|
@ -546,6 +621,8 @@ def generate_facturx_from_file(
|
|||
logger.debug('optional arg facturx_level=%s', facturx_level)
|
||||
logger.debug('optional arg check_xsd=%s', check_xsd)
|
||||
logger.debug('optional arg pdf_metadata=%s', pdf_metadata)
|
||||
logger.debug(
|
||||
'optional arg additional_attachments=%s', additional_attachments)
|
||||
if not pdf_invoice:
|
||||
raise ValueError('Missing pdf_invoice argument')
|
||||
if not facturx_xml:
|
||||
|
@ -558,6 +635,9 @@ def generate_facturx_from_file(
|
|||
raise ValueError('pdf_metadata argument must be a dict or None')
|
||||
if not isinstance(pdf_metadata, (dict, type(None))):
|
||||
raise ValueError('pdf_metadata argument must be a dict or None')
|
||||
if not isinstance(additional_attachments, (dict, type(None))):
|
||||
raise ValueError(
|
||||
'additional_attachments argument must be a dict or None')
|
||||
if not isinstance(output_pdf_file, (type(None), str, unicode)):
|
||||
raise ValueError('output_pdf_file argument must be a string or None')
|
||||
if isinstance(pdf_invoice, (str, unicode)):
|
||||
|
@ -583,6 +663,20 @@ def generate_facturx_from_file(
|
|||
"The second argument of the method generate_facturx must be "
|
||||
"either a string, an etree.Element() object or a file "
|
||||
"(it is a %s)." % type(facturx_xml))
|
||||
additional_attachments_read = {}
|
||||
if additional_attachments:
|
||||
for attach_filepath, attach_desc in additional_attachments.items():
|
||||
filename = os.path.basename(attach_filepath)
|
||||
mod_timestamp = os.path.getmtime(attach_filepath)
|
||||
mod_dt = datetime.fromtimestamp(mod_timestamp)
|
||||
with open(attach_filepath, 'r') as fa:
|
||||
fa.seek(0)
|
||||
additional_attachments_read[fa.read()] = {
|
||||
'filename': filename,
|
||||
'desc': attach_desc,
|
||||
'mod_date': mod_dt,
|
||||
}
|
||||
fa.close()
|
||||
if pdf_metadata is None:
|
||||
if xml_root is None:
|
||||
xml_root = etree.fromstring(xml_string)
|
||||
|
@ -606,7 +700,8 @@ def generate_facturx_from_file(
|
|||
new_pdf_filestream = PdfFileWriter()
|
||||
new_pdf_filestream.appendPagesFromReader(original_pdf)
|
||||
_facturx_update_metadata_add_attachment(
|
||||
new_pdf_filestream, xml_string, pdf_metadata, facturx_level)
|
||||
new_pdf_filestream, xml_string, pdf_metadata, facturx_level,
|
||||
additional_attachments=additional_attachments_read)
|
||||
if output_pdf_file:
|
||||
with open(output_pdf_file, 'wb') as output_f:
|
||||
new_pdf_filestream.write(output_f)
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
|
||||
targetNamespace="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
|
||||
elementFormDefault="qualified">
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="Factur-X_BASIC_WL_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" schemaLocation="Factur-X_BASIC_WL_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="Factur-X_BASIC_WL_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="FACTUR-X_BASIC-WL_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" schemaLocation="FACTUR-X_BASIC-WL_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="FACTUR-X_BASIC-WL_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
|
||||
<xs:element name="CrossIndustryInvoice" type="rsm:CrossIndustryInvoiceType"/>
|
||||
<xs:complexType name="CrossIndustryInvoiceType">
|
||||
<xs:sequence>
|
|
@ -5,7 +5,7 @@
|
|||
targetNamespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
|
||||
elementFormDefault="qualified"
|
||||
version="100.D16B">
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="Factur-X_BASIC_WL_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="FACTUR-X_BASIC-WL_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
|
||||
<xs:simpleType name="AllowanceChargeReasonCodeContentType">
|
||||
<xs:restriction base="xs:token"/>
|
||||
</xs:simpleType>
|
|
@ -6,8 +6,8 @@
|
|||
targetNamespace="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
|
||||
elementFormDefault="qualified"
|
||||
version="100.D16B">
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="Factur-X_BASIC_WL_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="Factur-X_BASIC_WL_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="FACTUR-X_BASIC-WL_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="FACTUR-X_BASIC-WL_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
|
||||
<xs:complexType name="CreditorFinancialAccountType">
|
||||
<xs:sequence>
|
||||
<xs:element name="IBANID" type="udt:IDType" minOccurs="0"/>
|
||||
|
@ -35,6 +35,13 @@
|
|||
<xs:element name="ID" type="udt:IDType"/>
|
||||
<xs:element name="TypeCode" type="qdt:DocumentCodeType" minOccurs="0"/>
|
||||
<xs:element name="IssueDateTime" type="udt:DateTimeType"/>
|
||||
<xs:element name="IncludedNote" type="ram:NoteType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="NoteType">
|
||||
<xs:sequence>
|
||||
<xs:element name="Content" type="udt:TextType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="SubjectCode" type="udt:CodeType" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="HeaderTradeAgreementType">
|
||||
|
@ -123,22 +130,12 @@
|
|||
<xs:element name="CategoryTradeTax" type="ram:TradeTaxType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="TradeContactType">
|
||||
<xs:sequence>
|
||||
<xs:element name="PersonName" type="udt:TextType" minOccurs="0"/>
|
||||
<xs:element name="DepartmentName" type="udt:TextType" minOccurs="0"/>
|
||||
<xs:element name="TelephoneUniversalCommunication" type="ram:UniversalCommunicationType" minOccurs="0"/>
|
||||
<xs:element name="FaxUniversalCommunication" type="ram:UniversalCommunicationType" minOccurs="0"/>
|
||||
<xs:element name="EmailURIUniversalCommunication" type="ram:UniversalCommunicationType" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="TradePartyType">
|
||||
<xs:sequence>
|
||||
<xs:element name="ID" type="udt:IDType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="GlobalID" type="udt:IDType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="Name" type="udt:TextType" minOccurs="0"/>
|
||||
<xs:element name="SpecifiedLegalOrganization" type="ram:LegalOrganizationType" minOccurs="0"/>
|
||||
<xs:element name="DefinedTradeContact" type="ram:TradeContactType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="PostalTradeAddress" type="ram:TradeAddressType" minOccurs="0"/>
|
||||
<xs:element name="URIUniversalCommunication" type="ram:UniversalCommunicationType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="SpecifiedTaxRegistration" type="ram:TaxRegistrationType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
|
@ -184,7 +181,6 @@
|
|||
<xs:complexType name="UniversalCommunicationType">
|
||||
<xs:sequence>
|
||||
<xs:element name="URIID" type="udt:IDType" minOccurs="0"/>
|
||||
<xs:element name="CompleteNumber" type="udt:TextType" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
|
@ -6,9 +6,9 @@
|
|||
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
|
||||
targetNamespace="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
|
||||
elementFormDefault="qualified">
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="Factur-X_EN16931_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" schemaLocation="Factur-X_EN16931_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="Factur-X_EN16931_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="FACTUR-X_EN16931_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" schemaLocation="FACTUR-X_EN16931_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="FACTUR-X_EN16931_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
|
||||
<xs:element name="CrossIndustryInvoice" type="rsm:CrossIndustryInvoiceType"/>
|
||||
<xs:complexType name="CrossIndustryInvoiceType">
|
||||
<xs:sequence>
|
|
@ -5,7 +5,15 @@
|
|||
targetNamespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
|
||||
elementFormDefault="qualified"
|
||||
version="100.D16B">
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="Factur-X_EN16931_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="FACTUR-X_EN16931_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
|
||||
<xs:simpleType name="AccountingAccountTypeCodeContentType">
|
||||
<xs:restriction base="xs:token"/>
|
||||
</xs:simpleType>
|
||||
<xs:complexType name="AccountingAccountTypeCodeType">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="qdt:AccountingAccountTypeCodeContentType"/>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="AllowanceChargeReasonCodeContentType">
|
||||
<xs:restriction base="xs:token"/>
|
||||
</xs:simpleType>
|
|
@ -6,8 +6,8 @@
|
|||
targetNamespace="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
|
||||
elementFormDefault="qualified"
|
||||
version="100.D16B">
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="Factur-X_EN16931_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="Factur-X_EN16931_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="FACTUR-X_EN16931_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd"/>
|
||||
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="FACTUR-X_EN16931_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
|
||||
<xs:complexType name="CreditorFinancialAccountType">
|
||||
<xs:sequence>
|
||||
<xs:element name="IBANID" type="udt:IDType" minOccurs="0"/>
|
||||
|
@ -187,6 +187,7 @@
|
|||
<xs:complexType name="TradeAccountingAccountType">
|
||||
<xs:sequence>
|
||||
<xs:element name="ID" type="udt:IDType"/>
|
||||
<xs:element name="TypeCode" type="qdt:AccountingAccountTypeCodeType" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="TradeAddressType">
|
||||
|
@ -216,7 +217,6 @@
|
|||
<xs:element name="PersonName" type="udt:TextType" minOccurs="0"/>
|
||||
<xs:element name="DepartmentName" type="udt:TextType" minOccurs="0"/>
|
||||
<xs:element name="TelephoneUniversalCommunication" type="ram:UniversalCommunicationType" minOccurs="0"/>
|
||||
<xs:element name="FaxUniversalCommunication" type="ram:UniversalCommunicationType" minOccurs="0"/>
|
||||
<xs:element name="EmailURIUniversalCommunication" type="ram:UniversalCommunicationType" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
12
setup.py
12
setup.py
|
@ -2,10 +2,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
import re
|
||||
|
||||
VERSIONFILE = "facturx/_version.py"
|
||||
verstrline = open(VERSIONFILE, "rt").read()
|
||||
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
|
||||
mo = re.search(VSRE, verstrline, re.M)
|
||||
if mo:
|
||||
verstr = mo.group(1)
|
||||
else:
|
||||
raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE))
|
||||
|
||||
setup(
|
||||
name='factur-x',
|
||||
version='0.3',
|
||||
version=verstr,
|
||||
author='Alexis de Lattre',
|
||||
author_email='alexis.delattre@akretion.com',
|
||||
url='https://github.com/akretion/factur-x',
|
||||
|
|
Loading…
Reference in New Issue