mirror of
https://github.com/NaN-tic/trytond-account_invoice_facturae.git
synced 2023-12-14 03:32:59 +01:00
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:
parent
ffdbf950ed
commit
16f74f6507
16 changed files with 422 additions and 157 deletions
|
@ -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,
|
||||
|
|
|
@ -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
24
company.py
Normal 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
12
company.xml
Normal 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>
|
133
invoice.py
133
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'
|
||||
|
|
108
locale/ca.po
108
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"
|
||||
|
|
100
locale/es.po
100
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"
|
||||
|
|
87
party.py
87
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')
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
21
view/company_form.xml
Normal 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>
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue