Add certificate_manager depends + Add property facturae_receiver_transaction_reference, facturae_item_description and facturae_article_code

#047304
#159379
This commit is contained in:
Raimon Esteve 2023-06-14 11:24:26 +02:00
parent 94eab8c166
commit 9b9fe4ee67
16 changed files with 335 additions and 149 deletions

View File

@ -1,18 +1,21 @@
# 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 company
from . import account
from . import invoice
from . import party
from . import payment_type
from . import account
from .invoice import FACTURAE_SCHEMA_VERSION
__all__ = [FACTURAE_SCHEMA_VERSION]
def register():
Pool.register(
account.Configuration,
account.ConfigurationFacturae,
account.TaxTemplate,
account.Tax,
company.Company,
invoice.Invoice,
invoice.InvoiceLine,
invoice.CreditInvoiceStart,

View File

@ -1,10 +1,8 @@
# 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__ = ['TaxTemplate', 'Tax']
from trytond.model import ModelSQL, fields
from trytond.pool import Pool, PoolMeta
from trytond.modules.company.model import CompanyValueMixin
REPORT_TYPES = [
(None, ""),
@ -43,6 +41,50 @@ REPORT_TYPES = [
]
class Configuration(metaclass=PoolMeta):
__name__ = 'account.configuration'
facturae_certificate = fields.MultiValue(
fields.Many2One('certificate', "Factura-e Certificate",
help='Certificate to sign Factura-e'))
facturae_service = fields.MultiValue(
fields.Selection('get_facturae_service', "Factura-e Service",
help='Service to be used when post the invoice'))
invoice_facturae_after = fields.TimeDelta("Send Factura-e after",
help="Grace period after which the invoice will be sent to the facturae "
"service. Applies only if a worker queue is activated.")
@classmethod
def default_facturae_service(cls, **pattern):
return cls.multivalue_model('facturae_service').default_facturae_service()
@classmethod
def get_facturae_service(cls):
pool = Pool()
ConfigurationFacturae = pool.get('account.configuration.facturae')
return ConfigurationFacturae.fields_get(['facturae_service'])['facturae_service']['selection']
@classmethod
def multivalue_model(cls, field):
pool = Pool()
if field in {'facturae_certificate', 'facturae_service'}:
return pool.get('account.configuration.facturae')
return super().multivalue_model(field)
class ConfigurationFacturae(ModelSQL, CompanyValueMixin):
"Account Configuration Factura-e"
__name__ = 'account.configuration.facturae'
facturae_certificate = fields.Many2One('certificate', "Factura-e Certificate",
help='Certificate to sign Factura-e')
facturae_service = fields.Selection([
(None, ''),
], "Factura-e Service")
@staticmethod
def default_facturae_service():
return None
class TaxTemplate(metaclass=PoolMeta):
__name__ = 'account.tax.template'

13
account.xml Normal file
View File

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

View File

@ -1,14 +0,0 @@
# 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__ = ['Company']
class Company(metaclass=PoolMeta):
__name__ = 'company.company'
facturae_certificate = fields.Binary('Factura-e Certificate',
help='The certificate to generate the XAdES electronic firm for '
'invoices.')

View File

@ -1,12 +0,0 @@
<?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

@ -25,6 +25,8 @@ from trytond import backend
from trytond.i18n import gettext
from trytond.exceptions import UserError
FACTURAE_SCHEMA_VERSION = '3.2.2'
# Get from XSD scheme of Facturae 3.2.2
# http://www.facturae.gob.es/formato/Versiones/Facturaev3_2_2.xml
RECTIFICATIVE_REASON_CODES = [
@ -142,16 +144,17 @@ class Invoice(metaclass=PoolMeta):
filename='invoice_facturae_filename')
invoice_facturae_filename = fields.Function(fields.Char(
'Factura-e filename'), 'get_invoice_facturae_filename')
invoice_facturae_sent = fields.Boolean('Factura-e Sent')
@classmethod
def __setup__(cls):
super(Invoice, cls).__setup__()
cls._check_modify_exclude.add('invoice_facturae')
cls._check_modify_exclude |= {'invoice_facturae', 'invoice_facturae_sent'}
cls._buttons.update({
'generate_facturae_wizard': {
'invisible': ((Eval('type') != 'out')
| ~Eval('state').in_(['posted', 'paid'])),
'readonly': Bool(Eval('invoice_facturae')),
'readonly': Bool(Eval('invoice_facturae_sent')),
}
})
@ -163,6 +166,7 @@ class Invoice(metaclass=PoolMeta):
default = default.copy()
default.setdefault('invoice_facturae', None)
default.setdefault('rectificative_reason_code', None)
default.setdefault('invoice_facturae_sent', None)
return super(Invoice, cls).copy(invoices, default=default)
def get_credited_invoices(self, name):
@ -207,6 +211,28 @@ class Invoice(metaclass=PoolMeta):
if ml.account.type.receivable],
key=attrgetter('maturity_date'))
@classmethod
def draft(cls, invoices):
invoice_facturae_sends = [invoice for invoice in invoices if invoice.invoice_facturae_sent]
if invoice_facturae_sends:
names = ', '.join(m.rec_name for m in invoice_facturae_sends[:5])
if len(invoice_facturae_sends) > 5:
names += '...'
raise UserError(gettext('account_invoice_facturae.msg_draft_invoice_facturae_sent',
invoices=names))
cls.write(invoices, {
'invoice_facturae': None,
})
super(Invoice, cls).draft(invoices)
@classmethod
def post(cls, invoices):
super(Invoice, cls).post(invoices)
for invoice in invoices:
cls.__queue__.generate_facturae(invoice)
def _credit(self, **values):
credit = super(Invoice, self)._credit(**values)
rectificative_reason_code = Transaction().context.get(
@ -221,23 +247,38 @@ class Invoice(metaclass=PoolMeta):
def generate_facturae_wizard(cls, invoices):
pass
@classmethod
def generate_facturae_default(cls, invoices, certificate_password):
to_write = ([],)
for invoice in invoices:
if invoice.invoice_facturae:
continue
facturae_content = invoice.get_facturae()
invoice._validate_facturae(facturae_content)
def generate_facturae(self, certificate=None, service=None):
pool = Pool()
Configuration = pool.get('account.configuration')
Invoice = pool.get('account.invoice')
config = Configuration(1)
transaction = Transaction()
if not self.invoice_facturae:
facturae_content = self.get_facturae()
self._validate_facturae(facturae_content)
if backend.name != 'sqlite':
invoice_facturae = invoice._sign_facturae(
facturae_content, certificate_password)
invoice_facturae = self._sign_facturae(facturae_content,
'default', certificate)
else:
invoice_facturae = facturae_content
to_write[0].append(invoice)
to_write += ({'invoice_facturae': invoice_facturae},)
if to_write[0]:
cls.write(*to_write)
self.invoice_facturae = invoice_facturae
self.save()
# send facturae to service
if not service and config.facturae_service:
service = config.facturae_service
if self.invoice_facturae and service:
with transaction.set_context(
queue_scheduled_at=config.invoice_facturae_after):
Invoice.__queue__.send_facturae(self, service)
def send_facturae(self, service):
Invoice = Pool().get('account.invoice')
method = 'send_facturae_%s' % service
getattr(Invoice, method)(self)
def get_facturae(self):
jinja_env = Environment(
@ -427,32 +468,41 @@ class Invoice(metaclass=PoolMeta):
invoice=self.rec_name, message=e))
return True
def _sign_facturae(self, xml_string, certificate_password):
def _sign_facturae(self, xml_string, service='default', certificate=None):
"""
Inspired by https://github.com/pedrobaeza/l10n-spain/blob/d01d049934db55130471e284012be7c860d987eb/l10n_es_facturae/wizard/create_facturae.py
"""
if not self.company.facturae_certificate:
raise UserError(gettext(
'account_invoice_facturae.missing_certificate',
company=self.company.rec_name))
Configuration = Pool().get('account.configuration')
if not certificate:
certificate = Configuration(1).facturae_certificate
if not certificate:
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')
unsigned_file = NamedTemporaryFile(suffix='.xml', delete=False)
unsigned_file.write(xml_string)
unsigned_file.close()
with NamedTemporaryFile(suffix='.xml', delete=False) as unsigned_file:
unsigned_file.write(xml_string)
cert_file = NamedTemporaryFile(suffix='.pfx', delete=False)
cert_file.write(self.company.facturae_certificate)
cert_file.close()
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
(
private_key,
certificate,
additional_certificates,
) = pkcs12.load_key_and_certificates(cert, password)
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(str(e))
# DER is an ASN.1 encoding type
crt = certificate.public_bytes(serialization.Encoding.DER)
@ -656,7 +706,7 @@ class Invoice(metaclass=PoolMeta):
return etree.tostring(root, xml_declaration=True, encoding="UTF-8")
signed_file_content = _sign_file(
self.company.facturae_certificate,
certificate_facturae,
certificate_password.encode(),
xml_string,
)
@ -670,6 +720,23 @@ class Invoice(metaclass=PoolMeta):
class InvoiceLine(metaclass=PoolMeta):
__name__ = 'account.invoice.line'
@property
def facturae_article_code(self):
return self.product and self.product.code or ''
@property
def facturae_item_description(self):
return (
(self.description and self.description[:2500])
or (self.product and self.product.rec_name[:2500])
or '#'+str(self.id)
)
@property
def facturae_receiver_transaction_reference(self):
# TODO Issuer/ReceiverTransactionDate (sale, contract...)
return ''
@property
def taxes_outputs(self):
"""Return list of 'impuestos repecutidos'"""
@ -723,17 +790,17 @@ class GenerateFacturaeStart(ModelView):
'Generate Factura-e file - Start'
__name__ = 'account.invoice.generate_facturae.start'
service = fields.Selection([
('default', 'Default'),
], 'Service', required=True)
certificate_password = fields.Char('Certificate Password',
(None, ''),
], 'Service')
certificate_facturae = fields.Many2One('certificate',
'Certificate Factura-e',
states={
'required': Eval('service') == 'default',
'invisible': Eval('service') != 'default',
'invisible': ~Bool(Eval('service')),
}, depends=['service'])
@staticmethod
def default_service():
return 'default'
return None
class GenerateFacturae(Wizard):
@ -750,6 +817,7 @@ class GenerateFacturae(Wizard):
Invoice = Pool().get('account.invoice')
invoices = Invoice.browse(Transaction().context['active_ids'])
service = 'generate_facturae_%s' % self.start.service
getattr(Invoice, service)(invoices, self.start.certificate_password)
for invoice in invoices:
invoice.generate_facturae(certificate=self.start.certificate_facturae,
service=self.start.service)
return 'end'

View File

@ -2,6 +2,22 @@
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:account.configuration,certificate_service_facturae:"
msgid "Certificate Service Post Factura-e"
msgstr "Servei certificat comptabilitar Factura-e"
msgctxt "field:account.configuration,invoice_facturae_after:"
msgid "Send Factura-e after"
msgstr "Envia "
msgctxt "field:account.configuration.facturae,certificate_service_facturae:"
msgid "Certificate Service Post Factura-e"
msgstr "Servei certificat comptabilitar Factura-e"
msgctxt "field:account.configuration.facturae,company:"
msgid "Company"
msgstr "Empresa"
msgctxt "field:account.invoice,credited_invoices:"
msgid "Credited Invoices"
msgstr "Factures abonades"
@ -22,6 +38,10 @@ 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,certificate_password:"
msgid "Certificate Password"
msgstr "Contrasenya certificat"
@ -42,10 +62,6 @@ msgctxt "field:account.tax.template,report_type:"
msgid "Report Type"
msgstr "Tipus informe"
msgctxt "field:company.company,facturae_certificate:"
msgid "Factura-e Certificate"
msgstr "Certificat Factura-e"
msgctxt "field:party.party,facturae_person_type:"
msgid "Person Type"
msgstr "Tipus persona"
@ -70,9 +86,13 @@ msgctxt "field:party.party,unidad_tramitadora:"
msgid "Unidad tramitadora"
msgstr "Unidad tramitadora"
msgctxt "help:company.company,facturae_certificate:"
msgid "The certificate to generate the XAdES electronic firm for invoices."
msgstr "El certificat per generar la firma electrònica XAdES de les factures."
msgctxt "help:account.configuration,certificate_service_facturae:"
msgid "Service to be used when post the invoice"
msgstr "El servei que s'utilitzarà quan es comptabilitzi una factura"
msgctxt "model:account.configuration.facturae,name:"
msgid "Account Configuration Factura-e"
msgstr "Configuració comptable Factura-e"
msgctxt "model:account.invoice.generate_facturae.start,name:"
msgid "Generate Factura-e file - Start"
@ -84,8 +104,8 @@ msgstr "Genera Factura-e"
msgctxt "model:ir.message,text:company_address_fields"
msgid ""
"Missing Street, Postal Code, City, Subdivision or Country in default address of "
"company\\'s Party \"%(party)s\"."
"Missing Street, Postal Code, City, Subdivision or Country in default address"
" of company\\'s Party \"%(party)s\"."
msgstr ""
"Falta el carrer, codi postal, ciutat, província o país de la direcció per "
"defecte del tercer de l'empresa \"%(party)s\"."
@ -100,42 +120,40 @@ msgstr ""
msgctxt "model:ir.message,text:error_signing"
msgid ""
"Error signing invoice \"%(invoice)s\".\\nMessage returned by signing "
"process: %(process_output)s"
"Error signing invoice \"%(invoice)s\".\n"
"Message returned by signing process: %(process_output)s"
msgstr ""
"Error firmant la factura \"%(invoice)s\".\n"
"Missatge retornat pel procés de firma: %(process_output)s"
msgctxt "model:ir.message,text:incompatible_facturae_type_account_bank"
msgid ""
"The Factura-e Type and Account Bank Kind of Payment Type \"%(payment_type)s\""
" are not compatible."
"The Factura-e Type and Account Bank Kind of Payment Type "
"\"%(payment_type)s\" are not compatible."
msgstr ""
"El Tipus Fatura-e i el Tipus compte bancari del Tipus de pagament "
"\"%(payment_type)s\" no són compatibles."
msgctxt "model:ir.message,text:invalid_factura_xml_file"
msgid ""
"The Factura-e file (XML) generated for invoice \"%(invoice)s\" is not valid "
"against the oficial XML Schema Definition:\\n%(message)s"
"The Factura-e file (XML) generated for invoice \"%(invoice)s\" is not valid against the oficial XML Schema Definition:\n"
"%(message)s"
msgstr ""
"El fitxer Factura-e (XML) generat per la factura \"%(invoice)s\" no és vàlid segons el XML Schema Definition oficial:\n"
"%(message)s"
msgctxt "model:ir.message,text:invoice_address_fields"
msgid ""
"Missing Street, Postal Code, City, Subdivision or Country in Invoice Address of "
"invoice \"%(invoice)s\"."
"Missing Street, Postal Code, City, Subdivision or Country in Invoice Address"
" of invoice \"%(invoice)s\"."
msgstr ""
"Falta el carrer, codi postal, ciutat, subdivisió o país a l'adreça de la "
"factura \"%(invoice)s\"."
msgctxt "model:ir.message,text:missing_account_bank_module"
msgid ""
"You must to install \"account_bank\" module to inform the Bank Account in "
"invoices.\\nThe payment type \"%(payment_type)s\" used in invoice "
"\"%(invoice)s\" is configured as \"Direct Debit\" or \"Credit Transfer\" and"
" it requires the company/party bank account."
"You must to install \"account_bank\" module to inform the Bank Account in invoices.\n"
"The payment type \"%(payment_type)s\" used in invoice \"%(invoice)s\" is configured as \"Direct Debit\" or \"Credit Transfer\" and it requires the company/party bank account."
msgstr ""
"S'ha d'instal·lar el mòdul \"account_bank\" per poder indicar el compte bancari a les factures.\n"
"El tipus de pagament \"%(payment_type)s\" utilitzat a la factura \"%(invoice)s\" està configurat com \"Rebut domiciliat\" o \"Transferència\" i es requereix el copmte bancari de l'empresa o del tercer."
@ -145,10 +163,6 @@ msgid "The Bank Account is missing in some move lines of invoice \"%(invoice)s\"
msgstr ""
"Falta el compte bancari en algun dels apunts de la factura \"%(invoice)s\"."
msgctxt "model:ir.message,text:missing_certificate"
msgid "Missing Factura-e Certificate in company \"%(company)s\"."
msgstr "Falta el Certificat Factura-e a l'empresa \"%(company)s\"."
msgctxt "model:ir.message,text:missing_iban"
msgid ""
"The Bank Account \"%(bank_account)s\" used in invoice \"%(invoice)s\" "
@ -172,6 +186,15 @@ msgstr ""
"Falta el Tipus Factura-e al tipus de pagament \"%(payment_type)s\" utilitzat"
" a la factura \"%(invoice)s\"."
msgctxt "model:ir.message,text:msg_missing_certificate"
msgid "Missing default configuration Factura-e Certificate."
msgstr "Falta la configuració del Certificat Factura-e"
msgctxt "model:ir.message,text:msg_missing_password_certificate"
msgid "Missing Password Factura-e Certificate in company \"%(company)s\"."
msgstr ""
"Falta la contrasenya del certificat Factura-e a l'empresa \"%(company)s\"."
msgctxt "model:ir.message,text:no_rate"
msgid "No rate found for currency \"%(currency)s\" on \"%(date)s\""
msgstr ""
@ -188,9 +211,8 @@ msgstr ""
msgctxt "model:ir.message,text:party_name_surname"
msgid ""
"The name of \"%(party)s\" of invoice \"%(invoice)s\" doesn\\'t contain the "
"Name and the, at least, First Surname.\\n They must to be separated by one "
"space: Name Surname."
"The name of \"%(party)s\" of invoice \"%(invoice)s\" doesn\\'t contain the Name and the, at least, First Surname.\n"
"They must to be separated by one space: Name Surname."
msgstr ""
"El nom del tercer \"%(party)s\" de la factura \"%(invoice)s\" no conté el nom i, al menys, el primer cognom.\n"
"Aquests han d'anar separats per un espai: Nom Cognom."
@ -205,7 +227,7 @@ msgstr ""
msgctxt "model:ir.model.button,string:generate_facturae_button"
msgid "Generate Facturae"
msgstr "Genera Facturae"
msgstr "Crea Facturae"
msgctxt "selection:account.invoice,rectificative_reason_code:"
msgid ""
@ -755,6 +777,10 @@ msgctxt "selection:party.party,facturae_residence_type:"
msgid "Resident in other EU country"
msgstr "Resident a la Unió Europea (excepte Espanya)"
msgctxt "view:account.configuration:"
msgid "Factura-e"
msgstr "Factura-e "
msgctxt "view:account.invoice.generate_facturae.start:"
msgid ""
"It will generate a Factura-e file for selected invoices, will sign them with"
@ -769,6 +795,14 @@ msgctxt "view:account.invoice:"
msgid "Generate Factura-e"
msgstr "Genera Factura-e"
msgctxt "view:account.tax.template:"
msgid "Factura-e"
msgstr "Factura-e"
msgctxt "view:account.tax:"
msgid "Factura-e"
msgstr "Factura-e"
msgctxt "view:party.party:"
msgid "Factura-e"
msgstr "Factura-e"

View File

@ -2,6 +2,22 @@
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:account.configuration,certificate_service_facturae:"
msgid "Certificate Service Post Factura-e"
msgstr "Servicio certificado contabilizar Factura-e"
msgctxt "field:account.configuration,invoice_facturae_after:"
msgid "Send Factura-e after"
msgstr "Enviar Factura-e después de"
msgctxt "field:account.configuration.facturae,certificate_service_facturae:"
msgid "Certificate Service Post Factura-e"
msgstr "Servicio certificado contabilizar Factura-e"
msgctxt "field:account.configuration.facturae,company:"
msgid "Company"
msgstr "Empresa"
msgctxt "field:account.invoice,credited_invoices:"
msgid "Credited Invoices"
msgstr "Facturas abonadas"
@ -22,6 +38,10 @@ 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"
@ -42,10 +62,6 @@ msgctxt "field:account.tax.template,report_type:"
msgid "Report Type"
msgstr "Tipo informe"
msgctxt "field:company.company,facturae_certificate:"
msgid "Factura-e Certificate"
msgstr "Certificado Factura-e"
msgctxt "field:party.party,facturae_person_type:"
msgid "Person Type"
msgstr "Tipo persona"
@ -70,10 +86,13 @@ msgctxt "field:party.party,unidad_tramitadora:"
msgid "Unidad tramitadora"
msgstr "Unidad tramitadora"
msgctxt "help:company.company,facturae_certificate:"
msgid "The certificate to generate the XAdES electronic firm for invoices."
msgstr ""
"El certificado para generar la firma electrónica XAdES de las facturas."
msgctxt "help:account.configuration,certificate_service_facturae:"
msgid "Service to be used when post the invoice"
msgstr "El servicio que se utilitzará cuando se contabilize una factura"
msgctxt "model:account.configuration.facturae,name:"
msgid "Account Configuration Factura-e"
msgstr "Configuración contable Factura-e"
msgctxt "model:account.invoice.generate_facturae.start,name:"
msgid "Generate Factura-e file - Start"
@ -85,8 +104,8 @@ msgstr "Generar Factura-e"
msgctxt "model:ir.message,text:company_address_fields"
msgid ""
"Missing Street, Postal Code, City, Subdivision or Country in default address of "
"company\\'s Party \"%(party)s\"."
"Missing Street, Postal Code, City, Subdivision or Country in default address"
" of company\\'s Party \"%(party)s\"."
msgstr ""
"Falta la calle, código postal, ciudad, provincia o país de la dirección por "
"defecto del tercero de la empresa \"%(party)s\"."
@ -109,8 +128,8 @@ msgstr ""
msgctxt "model:ir.message,text:incompatible_facturae_type_account_bank"
msgid ""
"The Factura-e Type and Account Bank Kind of Payment Type \"%(payment_type)s\""
" are not compatible."
"The Factura-e Type and Account Bank Kind of Payment Type "
"\"%(payment_type)s\" are not compatible."
msgstr ""
"El Tipo Factura-e y el Tipo cuenta bancaria del Tipo de pago "
"\"%(payment_type)s\" no son compatibles."
@ -125,8 +144,8 @@ msgstr ""
msgctxt "model:ir.message,text:invoice_address_fields"
msgid ""
"Missing Street, Postal Code, City, Subdivision or Country in Invoice Address of "
"invoice \"%(invoice)s\"."
"Missing Street, Postal Code, City, Subdivision or Country in Invoice Address"
" of invoice \"%(invoice)s\"."
msgstr ""
"Falta la calle, código postal, ciudad, província o país de la dirección de "
"la factura \"%(invoice)s\"."
@ -145,10 +164,6 @@ msgstr ""
"Falta la cuenta bancaria en alguno de los apuntes de la factura "
"\"%(invoice)s\"."
msgctxt "model:ir.message,text:missing_certificate"
msgid "Missing Factura-e Certificate in company \"%(company)s\"."
msgstr "Falta el Certificado Factura-e en la empresa \"%(company)s\"."
msgctxt "model:ir.message,text:missing_iban"
msgid ""
"The Bank Account \"%(bank_account)s\" used in invoice \"%(invoice)s\" "
@ -170,6 +185,21 @@ msgstr ""
"Falta el Tipo Factura-e en el tipo de pago \"%(payment_type)s\" usado en la "
"factura \"%(invoice)s\""
msgctxt "model:ir.message,text:msg_missing_certificate"
msgid "Missing default configuration Factura-e Certificate."
msgstr "Falta la configuració del Certificat Factura-e"
msgctxt "model:ir.message,text:msg_missing_certificate_service"
msgid "Missing default \"%(service)s\" Factura-e Certificate."
msgstr ""
"Falta el por defecto del servicio \"%(service)s\" del certificado Factura-e."
msgctxt "model:ir.message,text:msg_missing_password_certificate"
msgid "Missing Password Factura-e Certificate in company \"%(company)s\"."
msgstr ""
"Falta la contraseña del certificado Factura-e Certificate en la empresa "
"\"%(company)s\"."
msgctxt "model:ir.message,text:no_rate"
msgid "No rate found for currency \"%(currency)s\" on \"%(date)s\""
msgstr ""
@ -202,7 +232,7 @@ msgstr ""
msgctxt "model:ir.model.button,string:generate_facturae_button"
msgid "Generate Facturae"
msgstr ""
msgstr "Crear Facturae"
msgctxt "selection:account.invoice,rectificative_reason_code:"
msgid "Applicable Date/Period"

View File

@ -3,8 +3,11 @@
this repository contains the full copyright notices and license terms. -->
<tryton>
<data grouped="1">
<record model="ir.message" id="missing_certificate">
<field name="text">Missing Factura-e Certificate in company "%(company)s".</field>
<record model="ir.message" id="msg_missing_certificate">
<field name="text">Missing default configuration Factura-e Certificate.</field>
</record>
<record model="ir.message" id="msg_missing_password_certificate">
<field name="text">Missing Password Factura-e Certificate in company "%(company)s".</field>
</record>
<record model="ir.message" id="company_vat_identifier">
<field name="text">Missing VAT Identifier in company\'s Party "%(party)s", or its length is not between 3 and 30.</field>
@ -55,6 +58,8 @@ Message returned by signing process: %(process_output)s</field>
<record model="ir.message" id="incompatible_facturae_type_account_bank">
<field name="text">The Factura-e Type and Account Bank Kind of Payment Type "%(payment_type)s" are not compatible.</field>
</record>
<record model="ir.message" id="msg_draft_invoice_facturae_sent">
<field name="text">Can not draft "%(invoices)s" that have been sent to Factura-e.</field>
</record>
</data>
</tryton>

View File

@ -141,7 +141,7 @@
<StartDate>{{ invoice.credited_invoices[0].move.period.start_date.isoformat() }}</StartDate>
<EndDate>{{ invoice.credited_invoices[0].move.period.end_date.isoformat() }}</EndDate>
</TaxPeriod>
{# TODO: Methods not supported:
{# TODO: Methods not supported:
- 02 (solo se anotan los detalles ya rectificados)
- 03 (Rectificación por descuento por volumen de operaciones durante un periodo)
- 04 (Autorizadas por la Agencia Tributaria)
@ -167,6 +167,7 @@
{% endif %}
<TaxCurrencyCode>EUR</TaxCurrencyCode>
<LanguageName>{{ invoice.party_lang[:2] if invoice.party_lang else 'es' }}</LanguageName>
<ReceiverTransactionReference>{{ invoice.reference and invoice.reference[:20] or '' }}</ReceiverTransactionReference>
</InvoiceIssueData>
<TaxesOutputs>
{% for invoice_tax in invoice.taxes_outputs %}
@ -247,14 +248,14 @@
<InvoiceLine>
{# TODO: optional, not supported
- Issuer/ReceiverContractReference, Issuer/ReceiverContractDate (contract)
- Issuer/ReceiverTransactionReference, Issuer/ReceiverTransactionDate (sale, contract...)
- FileReference, FileDate
- SequenceNumber
- DeliveryNotesReferences (account_invoice_stock)
- TransactionDate
- Extensions
#}
<ItemDescription>{{ (line.description and line.description[:2500]) or (line.product and line.product.rec_name[:2500]) or '#'+line.id|string }}</ItemDescription>
<ReceiverTransactionReference>{{ line.facturae_receiver_transaction_reference }}</ReceiverTransactionReference>
<ItemDescription>{{ line.facturae_item_description }}</ItemDescription>
<Quantity>{{ line.quantity }}</Quantity>
<UnitOfMeasure>{{ UOM_CODE2TYPE.get(line.unit.symbol, '05') if line.unit else '05' }}</UnitOfMeasure>
<UnitPriceWithoutTax>{{ Currency.compute(invoice.currency, line.unit_price, euro) }}</UnitPriceWithoutTax>
@ -339,7 +340,7 @@
{% endif %}
{# TODO: SpecialTaxableEvent not supported #}
{% if line.product and line.product.code %}
<ArticleCode>{{ line.product.code[:20] }}</ArticleCode>
<ArticleCode>{{ line.facturae_article_code[:20] }}</ArticleCode>
{% endif %}
</InvoiceLine>
{% endfor %}

View File

@ -23,6 +23,8 @@ class AccountInvoiceFacturaeTestCase(CompanyTestMixin, ModuleTestCase):
'Test invoice generation'
pool = Pool()
Configuration = pool.get('account.configuration')
Certificate = pool.get('certificate')
Account = pool.get('account.account')
FiscalYear = pool.get('account.fiscalyear')
Invoice = pool.get('account.invoice')
@ -61,12 +63,15 @@ class AccountInvoiceFacturaeTestCase(CompanyTestMixin, ModuleTestCase):
company.party.save()
company.save()
# Save certificate into company
with open(os.path.join(
CURRENT_PATH, 'certificate.pfx'), 'rb') as cert_file:
company.facturae_certificate = cert_file.read()
with set_company(company):
certificate = Certificate()
certificate.name = 'dummy Certificate'
# Save certificate
with open(os.path.join(
CURRENT_PATH, 'certificate.pfx'), 'rb') as cert_file:
certificate.pem_certificate = cert_file.read()
certificate.save()
create_chart(company, tax=True)
fiscalyear = set_invoice_sequences(get_fiscalyear(company))
@ -199,7 +204,7 @@ class AccountInvoiceFacturaeTestCase(CompanyTestMixin, ModuleTestCase):
invoice.save()
Invoice.post([invoice])
Invoice.generate_facturae_default([invoice], 'privatepassword')
invoice.generate_facturae()
self.assertNotEqual(invoice.invoice_facturae, None)
self.assertEqual(invoice.invoice_facturae_filename, 'facturae-1.xsig')

View File

@ -4,11 +4,12 @@ depends:
account
account_invoice
account_payment_type
certificate_manager
extras_depend:
account_bank
account_es
xml:
company.xml
account.xml
invoice.xml
party.xml
payment_type.xml

View File

@ -1,10 +0,0 @@
<?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="before">
<newline/>
<label name="facturae_certificate"/>
<field name="facturae_certificate"/>
</xpath>
</data>

View File

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license types. -->
<data>
<xpath expr="/form/separator[@id='invoice']" position="before">
<separator id="facturae" string="Factura-e" colspan="4"/>
<label name="facturae_certificate"/>
<field name="facturae_certificate"/>
<label name="facturae_service"/>
<field name="facturae_service"/>
<label name="invoice_facturae_after"/>
<field name="invoice_facturae_after"/>
<newline/>
</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_password"/>
<field name="certificate_password" widget="password"/>
<label name="certificate_facturae"/>
<field name="certificate_facturae"/>
</group>
</form>

View File

@ -14,6 +14,11 @@
<label name="invoice_facturae"/>
<field name="invoice_facturae"/>
</xpath>
<xpath expr="/form/notebook/page[@id='info']/field[@name='cancel_move']"
position="after">
<label name="invoice_facturae_sent"/>
<field name="invoice_facturae_sent"/>
</xpath>
<xpath expr="/form/group[@id='buttons']" position="inside">
<button name="generate_facturae_wizard"
string="Generate Factura-e" icon="tryton-lock"/>