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.
This commit is contained in:
Bernat Brunet 2023-08-04 17:22:55 +02:00
parent a235054bc4
commit a376c04bce
16 changed files with 422 additions and 157 deletions

View File

@ -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,

View File

@ -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

24
company.py Normal file
View File

@ -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')

12
company.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="company_view_form">
<field name="model">company.company</field>
<field name="inherit" ref="company.company_view_form"/>
<field name="name">company_form</field>
</record>
</data>
</tryton>

View File

@ -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'

View File

@ -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"

View File

@ -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"

View File

@ -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')

View File

@ -3,10 +3,10 @@
copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="party_view_form">
<field name="model">party.party</field>
<field name="inherit" ref="party.party_view_form"/>
<field name="name">party_form</field>
<record model="ir.ui.view" id="address_view_form">
<field name="model">party.address</field>
<field name="inherit" ref="party.address_view_form"/>
<field name="name">address_form</field>
</record>
</data>
</tryton>

View File

@ -38,20 +38,20 @@
<Parties>
<SellerParty>
<TaxIdentification>
<PersonTypeCode>{{ invoice.company.party.facturae_person_type }}</PersonTypeCode>
<ResidenceTypeCode>{{ invoice.company.party.facturae_residence_type }}</ResidenceTypeCode>
<PersonTypeCode>{{ invoice.company.facturae_person_type }}</PersonTypeCode>
<ResidenceTypeCode>{{ invoice.company.facturae_residence_type }}</ResidenceTypeCode>
<TaxIdentificationNumber>{{ invoice.company.party.tax_identifier.code[:30] }}</TaxIdentificationNumber>
</TaxIdentification>
{# Optional. It could be the ID or the code #}
{% if invoice.company.party.code and invoice.company.party.code | length < 10 %}
<PartyIdentification>{{ invoice.company.party.code|int or invoice.company.party.id }}</PartyIdentification>
{% 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 %}
<AdministrativeCentres>
{% 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 %}
</AdministrativeCentres>
{% endif %}
<LegalEntity>
@ -73,23 +73,23 @@
<BuyerParty>
<TaxIdentification>
<PersonTypeCode>{{ invoice.party.facturae_person_type }}</PersonTypeCode>
<ResidenceTypeCode>{{ invoice.party.facturae_residence_type }}</ResidenceTypeCode>
<PersonTypeCode>{{ invoice.invoice_address.facturae_person_type }}</PersonTypeCode>
<ResidenceTypeCode>{{ invoice.invoice_address.facturae_residence_type }}</ResidenceTypeCode>
<TaxIdentificationNumber>{{ invoice.party.tax_identifier.code[:30] }}</TaxIdentificationNumber>
</TaxIdentification>
{# Optional. It could be the ID or the code #}
{% if invoice.party.code and invoice.party.code | length < 10 %}
<PartyIdentification>{{ invoice.party.code|int or invoice.party.id }}</PartyIdentification>
{% 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 %}
<AdministrativeCentres>
{% 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 %}
</AdministrativeCentres>
{% endif %}
{% if invoice.party.facturae_person_type == 'J' %}
{% if invoice.invoice_address.facturae_person_type == 'J' %}
<LegalEntity>
<CorporateName>{{ invoice.party.name and invoice.party.name[:80] or invoice.party.code[:80] }}</CorporateName>
{% if invoice.party.trade_name %}
@ -162,7 +162,7 @@
{% if invoice.currency != euro %}
<ExchangeRateDetails>
<ExchangeRate>{{ Invoice.double_up_to_eight(exchange_rate) }}</ExchangeRate>
<ExchangeRateDate>{{ exchange_rate_date }}</ExchangeRateDate>
<ExchangeRateDate>{% if exchange_rate_date %}{{ exchange_rate_date.isoformat() }}{% endif %}</ExchangeRateDate>
</ExchangeRateDetails>
{% endif %}
<TaxCurrencyCode>EUR</TaxCurrencyCode>
@ -255,7 +255,7 @@
- Extensions
#}
<ReceiverTransactionReference>{{ line.facturae_receiver_transaction_reference }}</ReceiverTransactionReference>
<ItemDescription>{{ line.facturae_item_description }}</ItemDescription>
<ItemDescription>{{ line.facturae_item_description[:2500] }}</ItemDescription>
<Quantity>{{ line.quantity }}</Quantity>
<UnitOfMeasure>{{ UOM_CODE2TYPE.get(line.unit.symbol, '05') if line.unit else '05' }}</UnitOfMeasure>
<UnitPriceWithoutTax>{{ Invoice.double_up_to_eight(line.unit_price) }}</UnitPriceWithoutTax>
@ -321,10 +321,10 @@
</Tax>
{% endfor %}
</TaxesOutputs>
{% if line.origin and line.origin.contract %}
{% if line.facturae_start_date %}
<LineItemPeriod>
<StartDate>{{ line.origin.start_date }}</StartDate>
<EndDate>{{ line.origin.end_date }}</EndDate>
<StartDate>{{ line.facturae_start_date.isoformat() }}</StartDate>
<EndDate>{{ line.facturae_end_date.isoformat() }}</EndDate>
</LineItemPeriod>
{% endif %}
{% if line.taxes_additional_line_item_information or line.note %}

View File

@ -1,19 +1,19 @@
{% macro administrative_center(centre_code, role_type_code, party) %}
{% macro administrative_center(centre_code, role_type_code, person_type, addrs) %}
<AdministrativeCentre>
<CentreCode>{{ centre_code }}</CentreCode>
<RoleTypeCode>{{ role_type_code }}</RoleTypeCode>
<Name>{% 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 %}</Name>
{% if party.facturae_person_type == 'F' %}
<FirstSurname>{{ party.name and party.name.split(' ', 2)[1][:40] }}</FirstSurname>
<Name>{% 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 %}</Name>
{% if person_type == 'F' %}
<FirstSurname>{{ addrs.party.name and addrs.party.name.split(' ', 2)[1][:40] }}</FirstSurname>
{% endif %}
{% if party.facturae_person_type == 'F' and party.name.split(' ') | length > 2 %}
<SecondSurname>{{ party.name and party.name.split(' ', 2)[2][:40] }}</SecondSurname>
{% if person_type == 'F' and addrs.party.name.split(' ') | length > 2 %}
<SecondSurname>{{ addrs.party.name and addrs.party.name.split(' ', 2)[2][:40] }}</SecondSurname>
{% 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 %}
<PhysicalGLN/>
<LogicalOperationalPoint/>
<CentreDescription>{{ party.name and party.name.split(' ', 2)[0][:40] or party.code[:40] }}</CentreDescription>
<CentreDescription>{{ addrs.party.name and addrs.party.name.split(' ', 2)[0][:40] or addrs.party.code[:40] }}</CentreDescription>
</AdministrativeCentre>
{% endmacro %}

View File

@ -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])

View File

@ -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

View File

@ -2,7 +2,7 @@
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/form/notebook/page[@id='accounting']" position="after">
<xpath expr="/form/notebook" position="inside">
<page id="facturae" string="Factura-e">
<label name="facturae_person_type"/>
<field name="facturae_person_type"/>

21
view/company_form.xml Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/form/notebook" position="inside">
<page id="facturae" string="Factura-e">
<label name="facturae_person_type"/>
<field name="facturae_person_type"/>
<label name="facturae_residence_type"/>
<field name="facturae_residence_type"/>
<label name="oficina_contable"/>
<field name="oficina_contable"/>
<label name="organo_gestor"/>
<field name="organo_gestor"/>
<label name="unidad_tramitadora"/>
<field name="unidad_tramitadora"/>
<label name="organo_proponente"/>
<field name="organo_proponente"/>
</page>
</xpath>
</data>

View File

@ -10,7 +10,7 @@
yalign="0.0" xalign="0.0" xexpand="1"/>
<label name="service"/>
<field name="service"/>
<label name="certificate_facturae"/>
<field name="certificate_facturae"/>
<label name="certificate"/>
<field name="certificate"/>
</group>
</form>