From a376c04bce2d579ec7ba3c83caf7a5cf64f97bbe Mon Sep 17 00:00:00 2001 From: Bernat Brunet Date: Fri, 4 Aug 2023 17:22:55 +0200 Subject: [PATCH] allow to have more than one Facturae deffinition per party. Moving the fields from party model to address. For the company move from party to company model. Update the way that the certificate to sign xml is used, based with the way that the certificats are all load the same way. --- __init__.py | 8 +- account.py | 2 +- company.py | 24 ++++ company.xml | 12 ++ invoice.py | 133 ++++++++++++---------- locale/ca.po | 108 +++++++++++++----- locale/es.po | 100 +++++++++++++--- party.py | 87 +++++++++++++- party.xml | 8 +- template_facturae_3.2.2.xml | 40 +++---- template_facturae_macros.xml | 18 +-- tests/test_module.py | 8 +- tryton.cfg | 4 +- view/{party_form.xml => address_form.xml} | 2 +- view/company_form.xml | 21 ++++ view/generate_facturae_start_form.xml | 4 +- 16 files changed, 422 insertions(+), 157 deletions(-) create mode 100644 company.py create mode 100644 company.xml rename view/{party_form.xml => address_form.xml} (91%) create mode 100644 view/company_form.xml diff --git a/__init__.py b/__init__.py index 85e7df6..415abe4 100644 --- a/__init__.py +++ b/__init__.py @@ -1,10 +1,7 @@ # The COPYRIGHT file at the top level of this repository contains the full # copyright notices and license terms. from trytond.pool import Pool -from . import account -from . import invoice -from . import party -from . import payment_type +from . import account, invoice, party, company, payment_type from .invoice import FACTURAE_SCHEMA_VERSION __all__ = [FACTURAE_SCHEMA_VERSION] @@ -20,8 +17,9 @@ def register(): invoice.InvoiceLine, invoice.CreditInvoiceStart, invoice.GenerateFacturaeStart, - party.Party, + party.Address, payment_type.PaymentType, + company.Company, module='account_invoice_facturae', type_='model') Pool.register( invoice.CreditInvoice, diff --git a/account.py b/account.py index a0698e4..d529ca3 100644 --- a/account.py +++ b/account.py @@ -77,7 +77,7 @@ class ConfigurationFacturae(ModelSQL, CompanyValueMixin): facturae_certificate = fields.Many2One('certificate', "Factura-e Certificate", help='Certificate to sign Factura-e') facturae_service = fields.Selection([ - (None, ''), + (None, 'Only Generate Facturae'), ], "Factura-e Service") @staticmethod diff --git a/company.py b/company.py new file mode 100644 index 0000000..4964f3c --- /dev/null +++ b/company.py @@ -0,0 +1,24 @@ +# The COPYRIGHT file at the top level of this repository contains the full +# copyright notices and license terms. +from trytond.model import fields +from trytond.pool import PoolMeta + + +class Company(metaclass=PoolMeta): + __name__ = 'company.company' + + facturae_person_type = fields.Selection([ + (None, ''), + ('J', 'Legal Entity'), + ('F', 'Individual'), + ], 'Person Type', sort=False) + facturae_residence_type = fields.Selection([ + (None, ''), + ('R', 'Resident in Spain'), + ('U', 'Resident in other EU country'), + ('E', 'Foreigner'), + ], 'Residence Type', sort=False) + oficina_contable = fields.Char('Oficina contable') + organo_gestor = fields.Char('Organo gestor') + unidad_tramitadora = fields.Char('Unidad tramitadora') + organo_proponente = fields.Char('Organo proponente') diff --git a/company.xml b/company.xml new file mode 100644 index 0000000..89ab3fa --- /dev/null +++ b/company.xml @@ -0,0 +1,12 @@ + + + + + + company.company + + company_form + + + diff --git a/invoice.py b/invoice.py index bd9ff57..762e94a 100644 --- a/invoice.py +++ b/invoice.py @@ -14,9 +14,6 @@ from decimal import Decimal from jinja2 import Environment, FileSystemLoader from lxml import etree from operator import attrgetter -from tempfile import NamedTemporaryFile -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.serialization import pkcs12 from trytond.model import ModelView, fields from trytond.pool import Pool, PoolMeta from trytond.pyson import Bool, Eval @@ -25,6 +22,8 @@ from trytond.wizard import Wizard, StateView, StateTransition, Button from trytond import backend from trytond.i18n import gettext from trytond.exceptions import UserError +from trytond.modules.certificate_manager.certificate_manager import ( + ENCODING_DER) FACTURAE_SCHEMA_VERSION = '3.2.2' @@ -319,13 +318,13 @@ class Invoice(metaclass=PoolMeta): "Missing some tax in invoice %s" % self.id) for field in FACe_REQUIRED_FIELDS: - for party in [self.party, self.company.party]: - if not getattr(party, field): - raise UserError(gettext( - 'account_invoice_facturae.party_facturae_fields', - party=party.rec_name, - invoice=self.rec_name, - field=field)) + if (not getattr(self.invoice_address, field) + or not getattr(self.company, field)): + raise UserError(gettext( + 'account_invoice_facturae.party_facturae_fields', + party=party.rec_name, + invoice=self.rec_name, + field=field)) if (not self.company.party.tax_identifier or len(self.company.party.tax_identifier.code) < 3 or len(self.company.party.tax_identifier.code) > 30): @@ -351,7 +350,7 @@ class Invoice(metaclass=PoolMeta): 'account_invoice_facturae.party_vat_identifier', party=self.party.rec_name, invoice=self.rec_name)) - if (self.party.facturae_person_type == 'F' + if (self.invoice_address.facturae_person_type == 'F' and len(self.party.name.split(' ', 2)) < 2): raise UserError(gettext( 'account_invoice_facturae.party_name_surname', @@ -475,7 +474,8 @@ class Invoice(metaclass=PoolMeta): """ Inspired by https://github.com/pedrobaeza/l10n-spain/blob/d01d049934db55130471e284012be7c860d987eb/l10n_es_facturae/wizard/create_facturae.py """ - Configuration = Pool().get('account.configuration') + pool = Pool() + Configuration = pool.get('account.configuration') if not certificate: certificate = Configuration(1).facturae_certificate @@ -483,34 +483,14 @@ class Invoice(metaclass=PoolMeta): raise UserError(gettext( 'account_invoice_facturae.msg_missing_certificate')) - certificate_facturae = certificate.pem_certificate - certificate_password = certificate.certificate_password - logger = logging.getLogger('account_invoice_facturae') - with NamedTemporaryFile(suffix='.xml', delete=False) as unsigned_file: - unsigned_file.write(xml_string) - - with NamedTemporaryFile(suffix='.pfx', delete=False) as cert_file: - cert_file.write(certificate_facturae) - - def _sign_file(cert, password, request): - # get key and certificates from PCK12 file - try: - ( - private_key, - certificate, - additional_certificates, - ) = pkcs12.load_key_and_certificates(cert, password) - except ValueError as e: - logger.warning("Error load_key_and_certificates file", - exc_info=True) - raise UserError(gettext( - 'account_invoice_facturae.msg_certificate_error', - error=str(e))) + def _sign_file(cert, request): + key = cert.load_pem_key() + pem = cert.load_pem_certificate() # DER is an ASN.1 encoding type - crt = certificate.public_bytes(serialization.Encoding.DER) + crt = pem.public_bytes(ENCODING_DER) # Set variables values rand_min = 1 @@ -574,10 +554,9 @@ class Invoice(metaclass=PoolMeta): # Set the certificate values ctx = xmlsig.SignatureContext() - ctx.private_key = private_key - ctx.x509 = certificate - ctx.ca_certificates = additional_certificates - ctx.public_key = certificate.public_key() + ctx.private_key = key + ctx.x509 = pem + ctx.public_key = pem.public_key() # Set the footer validation object_node = etree.SubElement( @@ -632,11 +611,11 @@ class Invoice(metaclass=PoolMeta): etree.SubElement( issuer_serial, etree.QName(xmlsig.constants.DSigNs, "X509IssuerName") - ).text = xmlsig.utils.get_rdns_name(certificate.issuer.rdns) + ).text = xmlsig.utils.get_rdns_name(pem.issuer.rdns) etree.SubElement( issuer_serial, etree.QName(xmlsig.constants.DSigNs, "X509SerialNumber") - ).text = str(certificate.serial_number) + ).text = str(pem.serial_number) signature_policy_identifier = etree.SubElement( signed_signature_properties, @@ -710,11 +689,7 @@ class Invoice(metaclass=PoolMeta): return etree.tostring(root, xml_declaration=True, encoding="UTF-8") - signed_file_content = _sign_file( - certificate_facturae, - certificate_password.encode(), - xml_string, - ) + signed_file_content = _sign_file(certificate, xml_string) logger.info("Factura-e for invoice %s (%s) generated and signed", self.rec_name, self.id) @@ -750,8 +725,8 @@ class InvoiceLine(metaclass=PoolMeta): @property def facturae_item_description(self): return ( - (self.description and self.description[:2500]) - or (self.product and self.product.rec_name[:2500]) + (self.description and self.description) + or (self.product and self.product.rec_name) or '#'+str(self.id) ) @@ -760,6 +735,34 @@ class InvoiceLine(metaclass=PoolMeta): # TODO Issuer/ReceiverTransactionDate (sale, contract...) return '' + @property + def facturae_start_date(self): + try: + pool = Pool() + Consumption = pool.get('contract.consumption') + except: + Consumption = None + + if (self.origin and Consumption + and isinstance(self.origin, Consumption)): + return self.origin.start_date + return None + + @property + def facturae_end_date(self): + try: + pool = Pool() + Consumption = pool.get('contract.consumption') + except: + Consumption = None + + if (self.origin and Consumption + and isinstance(self.origin, Consumption)): + return self.origin.end_date + elif self.facturae_start_date: + return self.facturae_start_date + return None + @property def taxes_outputs(self): """Return list of 'impuestos repecutidos'""" @@ -813,17 +816,10 @@ class GenerateFacturaeStart(ModelView): 'Generate Factura-e file - Start' __name__ = 'account.invoice.generate_facturae.start' service = fields.Selection([ - (None, ''), - ], 'Service') - certificate_facturae = fields.Many2One('certificate', - 'Certificate Factura-e', - states={ - 'invisible': ~Bool(Eval('service')), - }, depends=['service']) - - @staticmethod - def default_service(): - return None + (None, 'Only generate facturae'), + ], 'Factura-e Service') + certificate = fields.Many2One('certificate', + 'Factura-e Certificate', depends=['service']) class GenerateFacturae(Wizard): @@ -836,11 +832,26 @@ class GenerateFacturae(Wizard): ]) generate = StateTransition() + def default_start(self, fields): + pool = Pool() + Configuration = pool.get('account.configuration') + + default = { + 'service': None, + 'certificate': None, + } + + config = Configuration(1) + if config.facturae_certificate: + default['certificate'] = config.facturae_certificate.id + + return default + def transition_generate(self): Invoice = Pool().get('account.invoice') invoices = Invoice.browse(Transaction().context['active_ids']) for invoice in invoices: - invoice.generate_facturae(certificate=self.start.certificate_facturae, + invoice.generate_facturae(certificate=self.start.certificate, service=self.start.service) return 'end' diff --git a/locale/ca.po b/locale/ca.po index 8423479..a941c7d 100644 --- a/locale/ca.po +++ b/locale/ca.po @@ -38,17 +38,25 @@ msgctxt "field:account.invoice.credit.start,rectificative_reason_code:" msgid "Rectificative Reason Code" msgstr "Codi motiu rectificació" -msgctxt "field:account.invoice.generate_facturae.start,certificate_facturae:" -msgid "Certificate Factura-e" -msgstr "Certificat" +msgctxt "field:account.invoice.generate_facturae.start,service:" +msgid "Factura-e Service" +msgstr "Servei Factura-e" + +msgctxt "field:account.invoice.generate_facturae.start,certificate:" +msgid "Factura-e Certificate" +msgstr "Certificat Factura-e" msgctxt "field:account.invoice.generate_facturae.start,certificate_password:" msgid "Certificate Password" msgstr "Contrasenya certificat" -msgctxt "field:account.invoice.generate_facturae.start,service:" -msgid "Service" -msgstr "Servei" +msgctxt "field:account.configuration,facturae_certificate:" +msgid "Factura-e Certificate" +msgstr "Certificat Factura-e" + +msgctxt "field:account.configuration,facturae_service:" +msgid "Factura-e Service" +msgstr "Servei Factura-e" msgctxt "field:account.payment.type,facturae_type:" msgid "Factura-e Type" @@ -58,31 +66,55 @@ msgctxt "field:account.tax,report_type:" msgid "Report Type" msgstr "Tipus informe" +msgctxt "field:company.company,facturae_person_type:" +msgid "Person Type" +msgstr "Tipus persona" + +msgctxt "field:company.company,facturae_residence_type:" +msgid "Residence Type" +msgstr "Tipus residència" + +msgctxt "field:company.company,oficina_contable:" +msgid "Oficina contable" +msgstr "Oficina contable" + +msgctxt "field:company.company,organo_gestor:" +msgid "Organo gestor" +msgstr "Organo gestor" + +msgctxt "field:company.company,organo_proponente:" +msgid "Organo proponente" +msgstr "Organo proponente" + +msgctxt "field:company.company,unidad_tramitadora:" +msgid "Unidad tramitadora" +msgstr "Unidad tramitadora" + msgctxt "field:account.tax.template,report_type:" msgid "Report Type" msgstr "Tipus informe" -msgctxt "field:party.party,facturae_person_type:" +msgctxt "field:party.address,facturae_person_type:" msgid "Person Type" msgstr "Tipus persona" -msgctxt "field:party.party,facturae_residence_type:" +msgctxt "field:party.address,facturae_residence_type:" msgid "Residence Type" msgstr "Tipus residència" -msgctxt "field:party.party,oficina_contable:" +msgctxt "field:party.address,oficina_contable:" msgid "Oficina contable" msgstr "Oficina contable" -msgctxt "field:party.party,organo_gestor:" +msgctxt "field:party.address,organo_gestor:" msgid "Organo gestor" msgstr "Organo gestor" -msgctxt "field:party.party,organo_proponente:" +msgctxt "field:party.address,organo_proponente:" msgid "Organo proponente" msgstr "Organo proponente" -msgctxt "field:party.party,unidad_tramitadora:" +msgctxt "field:party.address,unidad_tramitadora:" msgid "Unidad tramitadora" msgstr "Unidad tramitadora" @@ -753,31 +785,43 @@ msgctxt "selection:account.tax.template,report_type:" msgid "Value-Added Tax" msgstr "" -msgctxt "selection:party.party,facturae_person_type:" -msgid "" -msgstr " " - -msgctxt "selection:party.party,facturae_person_type:" +msgctxt "selection:company.company,facturae_person_type:" msgid "Individual" msgstr "Persona física" -msgctxt "selection:party.party,facturae_person_type:" +msgctxt "selection:company.company,facturae_person_type:" msgid "Legal Entity" msgstr "Persona jurídica" -msgctxt "selection:party.party,facturae_residence_type:" -msgid "" -msgstr " " - -msgctxt "selection:party.party,facturae_residence_type:" +msgctxt "selection:company.company,facturae_residence_type:" msgid "Foreigner" msgstr "Estranger (Fora Unió Europea)" -msgctxt "selection:party.party,facturae_residence_type:" +msgctxt "selection:company.company,facturae_residence_type:" msgid "Resident in Spain" msgstr "Resident (a Espanya)" -msgctxt "selection:party.party,facturae_residence_type:" +msgctxt "selection:company.company,facturae_residence_type:" +msgid "Resident in other EU country" +msgstr "Resident a la Unió Europea (excepte Espanya)" + +msgctxt "selection:party.address,facturae_person_type:" +msgid "Individual" +msgstr "Persona física" + +msgctxt "selection:party.address,facturae_person_type:" +msgid "Legal Entity" +msgstr "Persona jurídica" + +msgctxt "selection:party.address,facturae_residence_type:" +msgid "Foreigner" +msgstr "Estranger (Fora Unió Europea)" + +msgctxt "selection:party.address,facturae_residence_type:" +msgid "Resident in Spain" +msgstr "Resident (a Espanya)" + +msgctxt "selection:party.address,facturae_residence_type:" msgid "Resident in other EU country" msgstr "Resident a la Unió Europea (excepte Espanya)" @@ -807,7 +851,11 @@ msgctxt "view:account.tax:" msgid "Factura-e" msgstr "Factura-e" -msgctxt "view:party.party:" +msgctxt "view:company.company:" +msgid "Factura-e" +msgstr "Factura-e" + +msgctxt "view:party.address:" msgid "Factura-e" msgstr "Factura-e" @@ -818,3 +866,11 @@ msgstr "Cancel·la" msgctxt "wizard_button:account.invoice.generate_facturae,start,generate:" msgid "Generate" msgstr "Genera" + +msgctxt "selection:account.configuration.facturae,facturae_service:" +msgid "Only Generate Facturae" +msgstr "Només genera la Factura-e" + +msgctxt "selection:account.invoice.generate_facturae.start,service:" +msgid "Only Generate Facturae" +msgstr "Només genera la Factura-e" diff --git a/locale/es.po b/locale/es.po index 29f743b..6f70587 100644 --- a/locale/es.po +++ b/locale/es.po @@ -38,17 +38,25 @@ msgctxt "field:account.invoice.credit.start,rectificative_reason_code:" msgid "Rectificative Reason Code" msgstr "Código motivo rectificación" -msgctxt "field:account.invoice.generate_facturae.start,certificate_facturae:" -msgid "Certificate Factura-e" -msgstr "Certificado Factura-e" - msgctxt "field:account.invoice.generate_facturae.start,certificate_password:" msgid "Certificate Password" msgstr "Contraseña certificado" msgctxt "field:account.invoice.generate_facturae.start,service:" -msgid "Service" -msgstr "Servicio" +msgid "Factura-e Service" +msgstr "Servicio Factura-e" + +msgctxt "field:account.invoice.generate_facturae.start,certificate:" +msgid "Factura-e Certificate" +msgstr "Certificado Factura-e" + +msgctxt "field:account.configuration,facturae_certificate:" +msgid "Factura-e Certificate" +msgstr "Certificado Factura-e" + +msgctxt "field:account.configuration,facturae_service:" +msgid "Factura-e Service" +msgstr "Servicio Factura-e" msgctxt "field:account.payment.type,facturae_type:" msgid "Factura-e Type" @@ -62,27 +70,51 @@ msgctxt "field:account.tax.template,report_type:" msgid "Report Type" msgstr "Tipo informe" -msgctxt "field:party.party,facturae_person_type:" +msgctxt "field:company.company,facturae_person_type:" msgid "Person Type" msgstr "Tipo persona" -msgctxt "field:party.party,facturae_residence_type:" +msgctxt "field:company.company,facturae_residence_type:" msgid "Residence Type" msgstr "Tipo residencia" -msgctxt "field:party.party,oficina_contable:" +msgctxt "field:company.company,oficina_contable:" msgid "Oficina contable" msgstr "Oficina contable" -msgctxt "field:party.party,organo_gestor:" +msgctxt "field:company.company,organo_gestor:" msgid "Organo gestor" msgstr "Organo gestor" -msgctxt "field:party.party,organo_proponente:" +msgctxt "field:company.company,organo_proponente:" msgid "Organo proponente" msgstr "Organo proponente" -msgctxt "field:party.party,unidad_tramitadora:" +msgctxt "field:company.company,unidad_tramitadora:" +msgid "Unidad tramitadora" +msgstr "Unidad tramitadora" + +msgctxt "field:party.address,facturae_person_type:" +msgid "Person Type" +msgstr "Tipo persona" + +msgctxt "field:party.address,facturae_residence_type:" +msgid "Residence Type" +msgstr "Tipo residencia" + +msgctxt "field:party.address,oficina_contable:" +msgid "Oficina contable" +msgstr "Oficina contable" + +msgctxt "field:party.address,organo_gestor:" +msgid "Organo gestor" +msgstr "Organo gestor" + +msgctxt "field:party.address,organo_proponente:" +msgid "Organo proponente" +msgstr "Organo proponente" + +msgctxt "field:party.address,unidad_tramitadora:" msgid "Unidad tramitadora" msgstr "Unidad tramitadora" @@ -751,23 +783,43 @@ msgctxt "selection:account.tax.template,report_type:" msgid "Value-Added Tax" msgstr "" -msgctxt "selection:party.party,facturae_person_type:" +msgctxt "selection:company.company,facturae_person_type:" msgid "Individual" msgstr "Persona física" -msgctxt "selection:party.party,facturae_person_type:" +msgctxt "selection:company.company,facturae_person_type:" msgid "Legal Entity" msgstr "Persona jurídica" -msgctxt "selection:party.party,facturae_residence_type:" +msgctxt "selection:company.company,facturae_residence_type:" msgid "Foreigner" msgstr "Extranjero (Fuera Unión Europea)" -msgctxt "selection:party.party,facturae_residence_type:" +msgctxt "selection:company.company,facturae_residence_type:" msgid "Resident in Spain" msgstr "Residente (en España)" -msgctxt "selection:party.party,facturae_residence_type:" +msgctxt "selection:company.company,facturae_residence_type:" +msgid "Resident in other EU country" +msgstr "Residente en la Unión Europea (excepto España)" + +msgctxt "selection:party.address,facturae_person_type:" +msgid "Individual" +msgstr "Persona física" + +msgctxt "selection:party.address,facturae_person_type:" +msgid "Legal Entity" +msgstr "Persona jurídica" + +msgctxt "selection:party.address,facturae_residence_type:" +msgid "Foreigner" +msgstr "Extranjero (Fuera Unión Europea)" + +msgctxt "selection:party.address,facturae_residence_type:" +msgid "Resident in Spain" +msgstr "Residente (en España)" + +msgctxt "selection:party.address,facturae_residence_type:" msgid "Resident in other EU country" msgstr "Residente en la Unión Europea (excepto España)" @@ -793,7 +845,11 @@ msgctxt "view:account.tax:" msgid "Factura-e" msgstr "Factura-e" -msgctxt "view:party.party:" +msgctxt "view:company.company:" +msgid "Factura-e" +msgstr "Factura-e" + +msgctxt "view:party.address:" msgid "Factura-e" msgstr "Factura-e" @@ -804,3 +860,11 @@ msgstr "Cancelar" msgctxt "wizard_button:account.invoice.generate_facturae,start,generate:" msgid "Generate" msgstr "Generar" + +msgctxt "selection:account.configuration.facturae,service:" +msgid "Only Generate Facturae" +msgstr "Solo genera la Factura-e" + +msgctxt "selection:account.invoice.generate_facturae.start,service:" +msgid "Only Generate Facturae" +msgstr "Solo genera la Factura-e" diff --git a/party.py b/party.py index 40a974a..3c9d024 100644 --- a/party.py +++ b/party.py @@ -1,13 +1,13 @@ # The COPYRIGHT file at the top level of this repository contains the full # copyright notices and license terms. from trytond.model import fields -from trytond.pool import PoolMeta - -__all__ = ['Party'] +from trytond.pool import Pool, PoolMeta +from trytond.transaction import Transaction -class Party(metaclass=PoolMeta): - __name__ = 'party.party' +class Address(metaclass=PoolMeta): + __name__ = 'party.address' + facturae_person_type = fields.Selection([ (None, ''), ('J', 'Legal Entity'), @@ -23,3 +23,80 @@ class Party(metaclass=PoolMeta): organo_gestor = fields.Char('Organo gestor') unidad_tramitadora = fields.Char('Unidad tramitadora') organo_proponente = fields.Char('Organo proponente') + + @classmethod + def __register__(cls, module_name): + pool = Pool() + Party = pool.get('party.party') + address_table = cls.__table__() + party_table = Party.__table__() + party = Party.__table_handler__() + cursor = Transaction().connection.cursor() + + super().__register__(module_name) + + # Migrat facturae fields from party to invoice addresses or in case of + # missing the fist address. + if party.column_exist('facturae_person_type'): + cursor.execute(*party_table.select( + party_table.id, + where=party_table.facturae_person_type != None)) + addresses_update = [] + for party_id in cursor.fetchall(): + cursor.execute(*address_table.select( + address_table.id, address_table.invoice, + where=address_table.party.in_(party_id))) + addresses = cursor.fetchall() + addresses = {a[0]: a[1] for a in addresses} + if not addresses: + continue + if len(addresses) == 1: + addresses_update.append(next(iter(addresses))) + else: + for address, invoice in addresses.items(): + if invoice: + addresses_update.append(address) + if not any(addresses.values()): + addresses_update.extend(addresses) + + for address_update in addresses_update: + cursor.execute(*address_table.select( + address_table.party, + where=address_table.id == address_update)) + party_id = cursor.fetchone() + + cursor.execute(*party_table.select( + party_table.facturae_person_type, + party_table.facturae_residence_type, + party_table.oficina_contable, + party_table.organo_gestor, + party_table.unidad_tramitadora, + party_table.organo_proponente, + where=party_table.id == party_id)) + party_values = cursor.fetchone() + + cursor.execute(*address_table.update( + columns=[ + address_table.facturae_person_type, + address_table.facturae_residence_type, + address_table.oficina_contable, + address_table.organo_gestor, + address_table.unidad_tramitadora, + address_table.organo_proponente + ], + values=[ + party_values[0], + party_values[1], + party_values[2], + party_values[3], + party_values[4], + party_values[5], + ], + where=address_table.id == address_update)) + + party.drop_column('facturae_person_type') + party.drop_column('facturae_residence_type') + party.drop_column('oficina_contable') + party.drop_column('organo_gestor') + party.drop_column('unidad_tramitadora') + party.drop_column('organo_proponente') diff --git a/party.xml b/party.xml index ddc1806..05dbf51 100644 --- a/party.xml +++ b/party.xml @@ -3,10 +3,10 @@ copyright notices and license terms. --> - - party.party - - party_form + + party.address + + address_form diff --git a/template_facturae_3.2.2.xml b/template_facturae_3.2.2.xml index 2570764..1b24574 100644 --- a/template_facturae_3.2.2.xml +++ b/template_facturae_3.2.2.xml @@ -38,20 +38,20 @@ - {{ invoice.company.party.facturae_person_type }} - {{ invoice.company.party.facturae_residence_type }} + {{ invoice.company.facturae_person_type }} + {{ invoice.company.facturae_residence_type }} {{ invoice.company.party.tax_identifier.code[:30] }} {# Optional. It could be the ID or the code #} {% if invoice.company.party.code and invoice.company.party.code | length < 10 %} {{ invoice.company.party.code|int or invoice.company.party.id }} {% endif %} - {% if invoice.company.party.oficina_contable or invoice.company.party.organo_gestor or invoice.company.party.unidad_tramitadora or invoice.company.party.organo_proponente %} + {% if invoice.company.oficina_contable or invoice.company.organo_gestor or invoice.company.unidad_tramitadora or invoice.company.organo_proponente %} - {% if invoice.company.party.oficina_contable %}{{ administrative_center(invoice.company.party.oficina_contable, '01', invoice.company.party) }}{% endif %} - {% if invoice.company.party.organo_gestor %}{{ administrative_center(invoice.company.party.organo_gestor, '02', invoice.company.party) }}{% endif %} - {% if invoice.company.party.unidad_tramitadora %}{{ administrative_center(invoice.company.party.unidad_tramitadora, '03', invoice.company.party) }}{% endif %} - {% if invoice.company.party.organo_proponente %}{{ administrative_center(invoice.company.party.organo_proponente, '04', invoice.company.party) }}{% endif %} + {% if invoice.company.oficina_contable %}{{ administrative_center(invoice.company.oficina_contable, '01', invoice.company.facturae_person_type, invoice.company.party.address_get('invoice')) }}{% endif %} + {% if invoice.company.organo_gestor %}{{ administrative_center(invoice.company.organo_gestor, '02', invoice.company.facturae_person_type, invoice.company.party.address_get('invoice')) }}{% endif %} + {% if invoice.company.unidad_tramitadora %}{{ administrative_center(invoice.company.unidad_tramitadora, '03', invoice.company.facturae_person_type, invoice.company.party.address_get('invoice')) }}{% endif %} + {% if invoice.company.organo_proponente %}{{ administrative_center(invoice.company.organo_proponente, '04', invoice.company.facturae_person_type, invoice.company.party.address_get('invoice')) }}{% endif %} {% endif %} @@ -73,23 +73,23 @@ - {{ invoice.party.facturae_person_type }} - {{ invoice.party.facturae_residence_type }} + {{ invoice.invoice_address.facturae_person_type }} + {{ invoice.invoice_address.facturae_residence_type }} {{ invoice.party.tax_identifier.code[:30] }} {# Optional. It could be the ID or the code #} {% if invoice.party.code and invoice.party.code | length < 10 %} {{ invoice.party.code|int or invoice.party.id }} {% endif %} - {% if invoice.party.oficina_contable or invoice.party.organo_gestor or invoice.party.unidad_tramitadora or invoice.party.organo_proponente %} + {% if invoice.invoice_address.oficina_contable or invoice.invoice_address.organo_gestor or invoice.invoice_address.unidad_tramitadora or invoice.invoice_address.organo_proponente %} - {% if invoice.party.oficina_contable %}{{ administrative_center(invoice.party.oficina_contable, '01', invoice.party) }}{% endif %} - {% if invoice.party.organo_gestor %}{{ administrative_center(invoice.party.organo_gestor, '02', invoice.party) }}{% endif %} - {% if invoice.party.unidad_tramitadora %}{{ administrative_center(invoice.party.unidad_tramitadora, '03', invoice.party) }}{% endif %} - {% if invoice.party.organo_proponente %}{{ administrative_center(invoice.party.organo_proponente, '04', invoice.party) }}{% endif %} + {% if invoice.invoice_address.oficina_contable %}{{ administrative_center(invoice.invoice_address.oficina_contable, '01', invoice.invoice_address.facturae_person_type, invoice.invoice_address) }}{% endif %} + {% if invoice.invoice_address.organo_gestor %}{{ administrative_center(invoice.invoice_address.organo_gestor, '02', invoice.invoice_address.facturae_person_type, invoice.invoice_address) }}{% endif %} + {% if invoice.invoice_address.unidad_tramitadora %}{{ administrative_center(invoice.invoice_address.unidad_tramitadora, '03', invoice.invoice_address.facturae_person_type, invoice.invoice_address) }}{% endif %} + {% if invoice.invoice_address.organo_proponente %}{{ administrative_center(invoice.invoice_address.organo_proponente, '04', invoice.invoice_address.facturae_person_type, invoice.invoice_address) }}{% endif %} {% endif %} - {% if invoice.party.facturae_person_type == 'J' %} + {% if invoice.invoice_address.facturae_person_type == 'J' %} {{ invoice.party.name and invoice.party.name[:80] or invoice.party.code[:80] }} {% if invoice.party.trade_name %} @@ -162,7 +162,7 @@ {% if invoice.currency != euro %} {{ Invoice.double_up_to_eight(exchange_rate) }} - {{ exchange_rate_date }} + {% if exchange_rate_date %}{{ exchange_rate_date.isoformat() }}{% endif %} {% endif %} EUR @@ -255,7 +255,7 @@ - Extensions #} {{ line.facturae_receiver_transaction_reference }} - {{ line.facturae_item_description }} + {{ line.facturae_item_description[:2500] }} {{ line.quantity }} {{ UOM_CODE2TYPE.get(line.unit.symbol, '05') if line.unit else '05' }} {{ Invoice.double_up_to_eight(line.unit_price) }} @@ -321,10 +321,10 @@ {% endfor %} - {% if line.origin and line.origin.contract %} + {% if line.facturae_start_date %} - {{ line.origin.start_date }} - {{ line.origin.end_date }} + {{ line.facturae_start_date.isoformat() }} + {{ line.facturae_end_date.isoformat() }} {% endif %} {% if line.taxes_additional_line_item_information or line.note %} diff --git a/template_facturae_macros.xml b/template_facturae_macros.xml index fa9964c..341d557 100644 --- a/template_facturae_macros.xml +++ b/template_facturae_macros.xml @@ -1,19 +1,19 @@ -{% macro administrative_center(centre_code, role_type_code, party) %} +{% macro administrative_center(centre_code, role_type_code, person_type, addrs) %} {{ centre_code }} {{ role_type_code }} - {% if party.facturae_person_type == 'J' %}{{ party.name and party.name[:40] }}{% else %}{{ party.name and party.name.split(' ', 2)[0][:40] or party.code[:40] }}{% endif %} - {% if party.facturae_person_type == 'F' %} - {{ party.name and party.name.split(' ', 2)[1][:40] }} + {% if person_type == 'J' %}{{ addrs.party.name and addrs.party.name[:40] }}{% else %}{{ addrs.party.name and addrs.party.name.split(' ', 2)[0][:40] or addrs.party.code[:40] }}{% endif %} + {% if person_type == 'F' %} + {{ addrs.party.name and addrs.party.name.split(' ', 2)[1][:40] }} {% endif %} - {% if party.facturae_person_type == 'F' and party.name.split(' ') | length > 2 %} - {{ party.name and party.name.split(' ', 2)[2][:40] }} + {% if person_type == 'F' and addrs.party.name.split(' ') | length > 2 %} + {{ addrs.party.name and addrs.party.name.split(' ', 2)[2][:40] }} {% endif %} - {% if party.addresses %}{{ address(party.addresses[0]) }}{% endif %} - {% if party.contact_mechanisms %}{{ contact(party) }}{% endif %} + {% if addrs %}{{ address(addrs) }}{% endif %} + {% if addrs.party.contact_mechanisms %}{{ contact(addrs.party) }}{% endif %} - {{ party.name and party.name.split(' ', 2)[0][:40] or party.code[:40] }} + {{ addrs.party.name and addrs.party.name.split(' ', 2)[0][:40] or addrs.party.code[:40] }} {% endmacro %} diff --git a/tests/test_module.py b/tests/test_module.py index fd85cf7..54f530a 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -58,8 +58,8 @@ class AccountInvoiceFacturaeTestCase(CompanyTestMixin, ModuleTestCase): company.header = 'Report Header' company.party.name = 'Seller' company.party.identifiers = [tax_identifier] - company.party.facturae_person_type = 'J' - company.party.facturae_residence_type = 'R' + company.facturae_person_type = 'J' + company.facturae_residence_type = 'R' company.party.save() company.save() @@ -108,8 +108,6 @@ class AccountInvoiceFacturaeTestCase(CompanyTestMixin, ModuleTestCase): company_address.country = country company_address.save() party = Party(name='Buyer') - party.facturae_person_type = 'J' - party.facturae_residence_type = 'R' tax_identifier = PartyIdentifier() tax_identifier.type = 'eu_vat' tax_identifier.code = 'BE0897290877' @@ -123,6 +121,8 @@ class AccountInvoiceFacturaeTestCase(CompanyTestMixin, ModuleTestCase): 'postal_code': '08201', 'subdivision': subdivision.id, 'country': country.id, + 'address.facturae_person_type': 'J', + 'address.facturae_residence_type': 'R', } address, = Address.create([address_dict]) diff --git a/tryton.cfg b/tryton.cfg index c229770..fca33e2 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -8,10 +8,12 @@ depends: extras_depend: account_bank account_es + contract xml: account.xml + account_es.xml + company.xml invoice.xml party.xml payment_type.xml - account_es.xml message.xml diff --git a/view/party_form.xml b/view/address_form.xml similarity index 91% rename from view/party_form.xml rename to view/address_form.xml index 05ac85b..0f2fab7 100644 --- a/view/party_form.xml +++ b/view/address_form.xml @@ -2,7 +2,7 @@ - +