feat: UPDATE Habilitacion RUSTIK
This commit is contained in:
		| @@ -1,12 +1,11 @@ | |||||||
| # This file is part of facho.  The COPYRIGHT file at the top level of | # This file is part of facho.  The COPYRIGHT file at the top level of | ||||||
| # this repository contains the full copyright notices and license terms. | # this repository contains the full copyright notices and license terms. | ||||||
|  |  | ||||||
| from lxml import etree | from lxml import etree | ||||||
| from lxml.etree import Element, SubElement, tostring | from lxml.etree import Element, tostring | ||||||
| import re | import re | ||||||
| from collections import defaultdict | from collections import defaultdict | ||||||
| from copy import deepcopy | from copy import deepcopy | ||||||
| from pprint import pprint |  | ||||||
|  |  | ||||||
| class FachoValueInvalid(Exception): | class FachoValueInvalid(Exception): | ||||||
|     def __init__(self, xpath): |     def __init__(self, xpath): | ||||||
| @@ -32,7 +31,10 @@ class LXMLBuilder: | |||||||
|  |  | ||||||
|     def __init__(self, nsmap): |     def __init__(self, nsmap): | ||||||
|         self.nsmap = nsmap |         self.nsmap = nsmap | ||||||
|         self._re_node_expr = re.compile(r'^(?P<path>((?P<ns>\w+):)?(?P<tag>[a-zA-Z0-9_-]+))(?P<attrs>\[.+\])?') |         self._re_node_expr = \ | ||||||
|  |             re.compile( | ||||||
|  |                 r'^(?P<path>((?P<ns>\w+):)?(?P<tag>[a-zA-Z0-9_-]+))' | ||||||
|  |                 r'(?P<attrs>\[.+\])?') | ||||||
|         self._re_attrs = re.compile(r'(\w+)\s*=\s*\"?(\w+)\"?') |         self._re_attrs = re.compile(r'(\w+)\s*=\s*\"?(\w+)\"?') | ||||||
|  |  | ||||||
|     def match_expression(self, node_expr): |     def match_expression(self, node_expr): | ||||||
| @@ -143,7 +145,8 @@ class LXMLBuilder: | |||||||
|             self.remove_attributes(el, keys, exclude=['facho_optional']) |             self.remove_attributes(el, keys, exclude=['facho_optional']) | ||||||
|  |  | ||||||
|             is_optional = el.get('facho_optional', 'False') == 'True' |             is_optional = el.get('facho_optional', 'False') == 'True' | ||||||
|             if is_optional and el.getchildren() == [] and el.keys() == ['facho_optional']: |             if is_optional and el.getchildren() == [] and el.keys() == [ | ||||||
|  |                     'facho_optional']: | ||||||
|                 el.getparent().remove(el) |                 el.getparent().remove(el) | ||||||
|  |  | ||||||
|         return tostring(elem, **attrs).decode('utf-8') |         return tostring(elem, **attrs).decode('utf-8') | ||||||
| @@ -153,7 +156,8 @@ class FachoXML: | |||||||
|     """ |     """ | ||||||
|     Decora XML con funciones de consulta XPATH de un solo elemento |     Decora XML con funciones de consulta XPATH de un solo elemento | ||||||
|     """ |     """ | ||||||
|     def __init__(self, root, builder=None, nsmap=None, fragment_prefix='',fragment_root_element=None): |     def __init__(self, root, builder=None, nsmap=None, fragment_prefix='', | ||||||
|  |                  fragment_root_element=None): | ||||||
|         if builder is None: |         if builder is None: | ||||||
|             self.builder = LXMLBuilder(nsmap) |             self.builder = LXMLBuilder(nsmap) | ||||||
|         else: |         else: | ||||||
| @@ -188,8 +192,8 @@ class FachoXML: | |||||||
|     def add_extension(self, extension): |     def add_extension(self, extension): | ||||||
|         extension.build(self) |         extension.build(self) | ||||||
|  |  | ||||||
|  |     def fragment( | ||||||
|     def fragment(self, xpath, append=False, append_not_exists=False): |             self, xpath, append=False, append_not_exists=False): | ||||||
|         nodes = xpath.split('/') |         nodes = xpath.split('/') | ||||||
|         nodes.pop() |         nodes.pop() | ||||||
|         root_prefix = '/'.join(nodes) |         root_prefix = '/'.join(nodes) | ||||||
| @@ -199,7 +203,9 @@ class FachoXML: | |||||||
|  |  | ||||||
|         if parent is None: |         if parent is None: | ||||||
|             parent = self.find_or_create_element(xpath, append=append) |             parent = self.find_or_create_element(xpath, append=append) | ||||||
|         return FachoXML(parent, nsmap=self.nsmap, fragment_prefix=root_prefix, fragment_root_element=self.root) |         return FachoXML( | ||||||
|  |             parent, nsmap=self.nsmap, fragment_prefix=root_prefix, | ||||||
|  |             fragment_root_element=self.root) | ||||||
|  |  | ||||||
|     def register_alias_xpath(self, alias, xpath): |     def register_alias_xpath(self, alias, xpath): | ||||||
|         self.xpath_for[alias] = xpath |         self.xpath_for[alias] = xpath | ||||||
| @@ -235,7 +241,8 @@ class FachoXML: | |||||||
|         """ |         """ | ||||||
|         xpath = self._path_xpath_for(xpath) |         xpath = self._path_xpath_for(xpath) | ||||||
|         node_paths = xpath.split('/') |         node_paths = xpath.split('/') | ||||||
|         node_paths.pop(0) #remove empty / |         # remove empty / | ||||||
|  |         node_paths.pop(0) | ||||||
|         root_tag = node_paths.pop(0) |         root_tag = node_paths.pop(0) | ||||||
|  |  | ||||||
|         root_node = self.builder.build_from_expression(root_tag) |         root_node = self.builder.build_from_expression(root_tag) | ||||||
| @@ -245,8 +252,8 @@ class FachoXML: | |||||||
|             root_node = self.root |             root_node = self.root | ||||||
|  |  | ||||||
|         if not self.builder.same_tag(root_node.tag, self.root.tag): |         if not self.builder.same_tag(root_node.tag, self.root.tag): | ||||||
|              |             raise ValueError('xpath %s must be absolute to /%s' % ( | ||||||
|             raise ValueError('xpath %s must be absolute to /%s' % (xpath, self.root.tag)) |                 xpath, self.root.tag)) | ||||||
|  |  | ||||||
|         # crea jerarquia segun xpath indicado |         # crea jerarquia segun xpath indicado | ||||||
|         parent = None |         parent = None | ||||||
| @@ -256,8 +263,8 @@ class FachoXML: | |||||||
|         for node_path in node_paths: |         for node_path in node_paths: | ||||||
|             node_expr = self.builder.match_expression(node_path) |             node_expr = self.builder.match_expression(node_path) | ||||||
|             node = self.builder.build_from_expression(node_path) |             node = self.builder.build_from_expression(node_path) | ||||||
|  |             child = self.builder.find_relative( | ||||||
|             child = self.builder.find_relative(current_elem, node_expr['path'], self.nsmap) |                 current_elem, node_expr['path'], self.nsmap) | ||||||
|  |  | ||||||
|             parent = current_elem |             parent = current_elem | ||||||
|             if child is not None: |             if child is not None: | ||||||
| @@ -268,7 +275,8 @@ class FachoXML: | |||||||
|  |  | ||||||
|         node_expr = self.builder.match_expression(node_tag) |         node_expr = self.builder.match_expression(node_tag) | ||||||
|         node = self.builder.build_from_expression(node_tag) |         node = self.builder.build_from_expression(node_tag) | ||||||
|         child = self.builder.find_relative(current_elem, node_expr['path'], self.nsmap) |         child = self.builder.find_relative( | ||||||
|  |             current_elem, node_expr['path'], self.nsmap) | ||||||
|         parent = current_elem |         parent = current_elem | ||||||
|         if child is not None: |         if child is not None: | ||||||
|             current_elem = child |             current_elem = child | ||||||
| @@ -289,7 +297,8 @@ class FachoXML: | |||||||
|                 self.builder.append(parent, node) |                 self.builder.append(parent, node) | ||||||
|                 return node |                 return node | ||||||
|  |  | ||||||
|             if self.builder.is_attribute(last_slibing, 'facho_placeholder', 'True'): |             if self.builder.is_attribute( | ||||||
|  |                     last_slibing, 'facho_placeholder', 'True'): | ||||||
|                 self._remove_facho_attributes(last_slibing) |                 self._remove_facho_attributes(last_slibing) | ||||||
|                 return last_slibing |                 return last_slibing | ||||||
|             self.builder.append_next(last_slibing, node) |             self.builder.append_next(last_slibing, node) | ||||||
| @@ -302,7 +311,8 @@ class FachoXML: | |||||||
|         self._remove_facho_attributes(current_elem) |         self._remove_facho_attributes(current_elem) | ||||||
|         return current_elem |         return current_elem | ||||||
|  |  | ||||||
|     def set_element_validator(self, xpath, validator = False): |     def set_element_validator( | ||||||
|  |             self, xpath, validator=False): | ||||||
|         """ |         """ | ||||||
|         validador al asignar contenido a xpath indicado |         validador al asignar contenido a xpath indicado | ||||||
|  |  | ||||||
| @@ -316,7 +326,8 @@ class FachoXML: | |||||||
|         else: |         else: | ||||||
|             self._validators[key] = validator |             self._validators[key] = validator | ||||||
|  |  | ||||||
|     def set_element(self, xpath, content, **attrs): |     def set_element( | ||||||
|  |             self, xpath, content, **attrs): | ||||||
|         """ |         """ | ||||||
|         asigna contenido ubicado por ruta tipo XPATH. |         asigna contenido ubicado por ruta tipo XPATH. | ||||||
|         @param xpath ruta tipo XPATH |         @param xpath ruta tipo XPATH | ||||||
| @@ -358,7 +369,8 @@ class FachoXML: | |||||||
|                 self.builder.set_attribute(elem, k, str(v)) |                 self.builder.set_attribute(elem, k, str(v)) | ||||||
|         return self |         return self | ||||||
|  |  | ||||||
|     def get_element_attribute(self, xpath, attribute, multiple=False): |     def get_element_attribute( | ||||||
|  |             self, xpath, attribute, multiple=False): | ||||||
|         elem = self.get_element(xpath, multiple=multiple) |         elem = self.get_element(xpath, multiple=multiple) | ||||||
|  |  | ||||||
|         if elem is None: |         if elem is None: | ||||||
| @@ -395,14 +407,16 @@ class FachoXML: | |||||||
|                 return None |                 return None | ||||||
|             return format_(text) |             return format_(text) | ||||||
|  |  | ||||||
|     def get_element_text_or_attribute(self, xpath, default=None, multiple=False, raise_on_fail=False): |     def get_element_text_or_attribute( | ||||||
|  |             self, xpath, default=None, multiple=False, raise_on_fail=False): | ||||||
|         parts = xpath.split('/') |         parts = xpath.split('/') | ||||||
|         is_attribute = parts[-1].startswith('@') |         is_attribute = parts[-1].startswith('@') | ||||||
|         if is_attribute: |         if is_attribute: | ||||||
|             attribute_name = parts.pop(-1).lstrip('@') |             attribute_name = parts.pop(-1).lstrip('@') | ||||||
|             element_path = "/".join(parts) |             element_path = "/".join(parts) | ||||||
|             try: |             try: | ||||||
|                 val = self.get_element_attribute(element_path, attribute_name, multiple=multiple) |                 val = self.get_element_attribute( | ||||||
|  |                     element_path, attribute_name, multiple=multiple) | ||||||
|                 if val is None: |                 if val is None: | ||||||
|                     return default |                     return default | ||||||
|                 return val |                 return val | ||||||
| @@ -435,7 +449,8 @@ class FachoXML: | |||||||
|             if isinstance(xpath, tuple): |             if isinstance(xpath, tuple): | ||||||
|                 val = xpath[0] |                 val = xpath[0] | ||||||
|             else: |             else: | ||||||
|                 val = self.get_element_text_or_attribute(xpath, raise_on_fail=raise_on_fail) |                 val = self.get_element_text_or_attribute( | ||||||
|  |                     xpath, raise_on_fail=raise_on_fail) | ||||||
|             vals.append(val) |             vals.append(val) | ||||||
|         return vals |         return vals | ||||||
|  |  | ||||||
| @@ -457,7 +472,8 @@ class FachoXML: | |||||||
|         return True |         return True | ||||||
|  |  | ||||||
|     def _remove_facho_attributes(self, elem): |     def _remove_facho_attributes(self, elem): | ||||||
|         self.builder.remove_attributes(elem, ['facho_optional', 'facho_placeholder']) |         self.builder.remove_attributes( | ||||||
|  |             elem, ['facho_optional', 'facho_placeholder']) | ||||||
|  |  | ||||||
|     def tostring(self, **kw): |     def tostring(self, **kw): | ||||||
|         return self.builder.tostring(self.root, **kw) |         return self.builder.tostring(self.root, **kw) | ||||||
| @@ -475,9 +491,11 @@ class FachoXML: | |||||||
|             ns = nsmap[etree.QName(root).namespace] + ':' |             ns = nsmap[etree.QName(root).namespace] + ':' | ||||||
|  |  | ||||||
|         if self.fragment_root_element is not None: |         if self.fragment_root_element is not None: | ||||||
|             new_xpath = '/' + ns + etree.QName(root).localname + '/' + etree.QName(self.root).localname + '/' + xpath.lstrip('/') |             new_xpath = '/' + ns + etree.QName(root).localname + '/' + \ | ||||||
|  |                 etree.QName(self.root).localname + '/' + xpath.lstrip('/') | ||||||
|         else: |         else: | ||||||
|             new_xpath = '/' + ns + etree.QName(root).localname + '/' + xpath.lstrip('/') |             new_xpath = '/' + ns + etree.QName(root).localname + '/' + \ | ||||||
|  |                 xpath.lstrip('/') | ||||||
|         return new_xpath |         return new_xpath | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|   | |||||||
| @@ -30,28 +30,45 @@ SCHEME_AGENCY_ATTRS = { | |||||||
| POLICY_ID = 'https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf' | POLICY_ID = 'https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf' | ||||||
| POLICY_NAME = u'Política de firma para facturas electrónicas de la República de Colombia.' | POLICY_NAME = u'Política de firma para facturas electrónicas de la República de Colombia.' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # NAMESPACES = { | ||||||
|  | #     'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2', | ||||||
|  | #     'nomina': 'dian:gov:co:facturaelectronica:NominaIndividual', | ||||||
|  | #     'nominaajuste': 'dian:gov:co:facturaelectronica:NominaIndividualDeAjuste',     | ||||||
|  | #     'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1', | ||||||
|  | #     'xs': 'http://www.w3.org/2001/XMLSchema-instance',     | ||||||
|  | #     'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', | ||||||
|  | #     'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2', | ||||||
|  | #     'cdt': 'urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1', | ||||||
|  | #     'clm54217': 'urn:un:unece:uncefact:codelist:specification:54217:2001', | ||||||
|  | #     'clmIANAMIMEMediaType': 'urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003', | ||||||
|  | #     'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2', | ||||||
|  | #     'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2', | ||||||
|  | #     'sts': 'dian:gov:co:facturaelectronica:Structures-2-1', | ||||||
|  | #     'udt': 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2', | ||||||
|  | #     'xsi': 'http://www.w3.org/2001/XMLSchema-instance', | ||||||
|  | #     'xades': 'http://uri.etsi.org/01903/v1.3.2#', | ||||||
|  | #     'xades141': 'http://uri.etsi.org/01903/v1.4.1#',     | ||||||
|  | #     'ds': 'http://www.w3.org/2000/09/xmldsig#', | ||||||
|  | #     'sig': 'http://www.w3.org/2000/09/xmldsig#', | ||||||
|  | # } | ||||||
|  |  | ||||||
|  |  | ||||||
| NAMESPACES = { | NAMESPACES = { | ||||||
|     'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2', |  | ||||||
|     'nomina': 'dian:gov:co:facturaelectronica:NominaIndividual', |  | ||||||
|     'nominaajuste': 'dian:gov:co:facturaelectronica:NominaIndividualDeAjuste',     |  | ||||||
|     'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1', |     'fe': 'http://www.dian.gov.co/contratos/facturaelectronica/v1', | ||||||
|     'xs': 'http://www.w3.org/2001/XMLSchema-instance',     |  | ||||||
|     'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', |     'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', | ||||||
|     'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2', |     'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2', | ||||||
|     'cdt': 'urn:DocumentInformation:names:specification:ubl:colombia:schema:xsd:DocumentInformationAggregateComponents-1', |  | ||||||
|     'clm54217': 'urn:un:unece:uncefact:codelist:specification:54217:2001', |  | ||||||
|     'clmIANAMIMEMediaType': 'urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003', |  | ||||||
|     'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2', |     'ext': 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2', | ||||||
|     'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2', |     'qdt': 'urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2', | ||||||
|     'sts': 'dian:gov:co:facturaelectronica:Structures-2-1', |     'sts': 'dian:gov:co:facturaelectronica:Structures-2-1', | ||||||
|     'udt': 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2',  |     'udt': 'urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2',  | ||||||
|     'xsi': 'http://www.w3.org/2001/XMLSchema-instance', |     'xsi': 'http://www.w3.org/2001/XMLSchema-instance', | ||||||
|     'xades': 'http://uri.etsi.org/01903/v1.3.2#', |  | ||||||
|     'xades141': 'http://uri.etsi.org/01903/v1.4.1#',     |  | ||||||
|     'ds': 'http://www.w3.org/2000/09/xmldsig#', |     'ds': 'http://www.w3.org/2000/09/xmldsig#', | ||||||
|     'sig': 'http://www.w3.org/2000/09/xmldsig#', |     'xades': 'http://uri.etsi.org/01903/v1.3.2#', | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def fe_from_string(document: str) -> FachoXML: | def fe_from_string(document: str) -> FachoXML: | ||||||
|     return FeXML.from_string(document) |     return FeXML.from_string(document) | ||||||
|  |  | ||||||
| @@ -77,7 +94,7 @@ def mock_xades_policy(): | |||||||
| class FeXML(FachoXML): | class FeXML(FachoXML): | ||||||
|  |  | ||||||
|     def __init__(self, root, namespace): |     def __init__(self, root, namespace): | ||||||
|  |         # raise Exception(namespace) | ||||||
|         super().__init__("{%s}%s" % (namespace, root), |         super().__init__("{%s}%s" % (namespace, root), | ||||||
|                          nsmap=NAMESPACES) |                          nsmap=NAMESPACES) | ||||||
|  |  | ||||||
| @@ -92,7 +109,8 @@ class FeXML(FachoXML): | |||||||
|         return super().tostring(**kw)\ |         return super().tostring(**kw)\ | ||||||
|                         .replace(xmlns_name + ':', '')\ |                         .replace(xmlns_name + ':', '')\ | ||||||
|                         .replace('xmlns:'+xmlns_name, 'xmlns')\ |                         .replace('xmlns:'+xmlns_name, 'xmlns')\ | ||||||
|             .replace('schemaLocation', 'xsi:schemaLocation') |                         .replace('http://www.dian.gov.co/contratos/facturaelectronica/v1', 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2') | ||||||
|  |  | ||||||
|  |  | ||||||
| class DianXMLExtensionCUDFE(FachoXMLExtension): | class DianXMLExtensionCUDFE(FachoXMLExtension): | ||||||
|  |  | ||||||
|   | |||||||
| @@ -652,7 +652,6 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|         fexml.placeholder_for('./cbc:ProfileExecutionID') |         fexml.placeholder_for('./cbc:ProfileExecutionID') | ||||||
|         fexml.set_element('./cbc:ID', invoice.invoice_ident) |         fexml.set_element('./cbc:ID', invoice.invoice_ident) | ||||||
|         fexml.placeholder_for('./cbc:UUID') |         fexml.placeholder_for('./cbc:UUID') | ||||||
|         fexml.set_element('./cbc:DocumentCurrencyCode', 'COP') |  | ||||||
|         fexml.set_element('./cbc:IssueDate', invoice.invoice_issue.strftime('%Y-%m-%d')) |         fexml.set_element('./cbc:IssueDate', invoice.invoice_issue.strftime('%Y-%m-%d')) | ||||||
|         #DIAN 1.7.-2020: FAD10 |         #DIAN 1.7.-2020: FAD10 | ||||||
|         fexml.set_element('./cbc:IssueTime', invoice.invoice_issue.strftime('%H:%M:%S-05:00')) |         fexml.set_element('./cbc:IssueTime', invoice.invoice_issue.strftime('%H:%M:%S-05:00')) | ||||||
| @@ -661,24 +660,22 @@ class DIANInvoiceXML(fe.FeXML): | |||||||
|                         listAgencyID='195', |                         listAgencyID='195', | ||||||
|                         listAgencyName='No matching global declaration available for the validation root', |                         listAgencyName='No matching global declaration available for the validation root', | ||||||
|                         listURI='http://www.dian.gov.co') |                         listURI='http://www.dian.gov.co') | ||||||
|  |         fexml.set_element('./cbc:DocumentCurrencyCode', 'COP') | ||||||
|         fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines)) |         fexml.set_element('./cbc:LineCountNumeric', len(invoice.invoice_lines)) | ||||||
|         fexml.set_element('./cac:%sPeriod/cbc:StartDate' % (fexml.tag_document()), |         fexml.set_element('./cac:%sPeriod/cbc:StartDate' % (fexml.tag_document()), | ||||||
|                           invoice.invoice_period_start.strftime('%Y-%m-%d')) |                           invoice.invoice_period_start.strftime('%Y-%m-%d')) | ||||||
|  |  | ||||||
|         fexml.set_element('./cac:%sPeriod/cbc:EndDate' % (fexml.tag_document()), |         fexml.set_element('./cac:%sPeriod/cbc:EndDate' % (fexml.tag_document()), | ||||||
|                           invoice.invoice_period_end.strftime('%Y-%m-%d')) |                           invoice.invoice_period_end.strftime('%Y-%m-%d')) | ||||||
|  |         fexml.set_billing_reference(invoice) | ||||||
|         fexml.customize(invoice) |         fexml.customize(invoice) | ||||||
|  |  | ||||||
|         fexml.set_supplier(invoice) |         fexml.set_supplier(invoice) | ||||||
|         fexml.set_customer(invoice) |         fexml.set_customer(invoice) | ||||||
|         fexml.set_legal_monetary(invoice) |  | ||||||
|         fexml.set_invoice_totals(invoice) |  | ||||||
|         fexml.set_invoice_lines(invoice) |  | ||||||
|         fexml.set_payment_mean(invoice) |         fexml.set_payment_mean(invoice) | ||||||
|  |         fexml.set_invoice_totals(invoice) | ||||||
|  |         fexml.set_legal_monetary(invoice) | ||||||
|  |         fexml.set_invoice_lines(invoice) | ||||||
|         fexml.set_allowance_charge(invoice) |         fexml.set_allowance_charge(invoice) | ||||||
|         fexml.set_billing_reference(invoice) |  | ||||||
|  |  | ||||||
|         return fexml |         return fexml | ||||||
|  |  | ||||||
|     def customize(fexml, invoice): |     def customize(fexml, invoice): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user