diff --git a/__init__.py b/__init__.py index 9085ba3..85e7df6 100644 --- a/__init__.py +++ b/__init__.py @@ -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, diff --git a/account.py b/account.py index 8271e39..a0698e4 100644 --- a/account.py +++ b/account.py @@ -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' diff --git a/account.xml b/account.xml new file mode 100644 index 0000000..7826297 --- /dev/null +++ b/account.xml @@ -0,0 +1,13 @@ + + + + + + + account.configuration + + configuration_form + + + diff --git a/company.py b/company.py deleted file mode 100644 index 87ed499..0000000 --- a/company.py +++ /dev/null @@ -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.') - diff --git a/company.xml b/company.xml deleted file mode 100644 index 89ab3fa..0000000 --- a/company.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - company.company - - company_form - - - diff --git a/invoice.py b/invoice.py index 242dd76..8620b1d 100644 --- a/invoice.py +++ b/invoice.py @@ -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' diff --git a/locale/ca.po b/locale/ca.po index 074bbaa..1142d6e 100644 --- a/locale/ca.po +++ b/locale/ca.po @@ -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" diff --git a/locale/es.po b/locale/es.po index 8cbafc7..2d776c1 100644 --- a/locale/es.po +++ b/locale/es.po @@ -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" diff --git a/message.xml b/message.xml index 8e8a4e0..5222b29 100644 --- a/message.xml +++ b/message.xml @@ -3,8 +3,11 @@ this repository contains the full copyright notices and license terms. --> - - Missing Factura-e Certificate in company "%(company)s". + + Missing default configuration Factura-e Certificate. + + + Missing Password Factura-e Certificate in company "%(company)s". Missing VAT Identifier in company\'s Party "%(party)s", or its length is not between 3 and 30. @@ -55,6 +58,8 @@ Message returned by signing process: %(process_output)s The Factura-e Type and Account Bank Kind of Payment Type "%(payment_type)s" are not compatible. - + + Can not draft "%(invoices)s" that have been sent to Factura-e. + diff --git a/template_facturae_3.2.2.xml b/template_facturae_3.2.2.xml index 494f8a5..c969731 100644 --- a/template_facturae_3.2.2.xml +++ b/template_facturae_3.2.2.xml @@ -141,7 +141,7 @@ {{ invoice.credited_invoices[0].move.period.start_date.isoformat() }} {{ invoice.credited_invoices[0].move.period.end_date.isoformat() }} - {# 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 %} EUR {{ invoice.party_lang[:2] if invoice.party_lang else 'es' }} + {{ invoice.reference and invoice.reference[:20] or '' }} {% for invoice_tax in invoice.taxes_outputs %} @@ -247,14 +248,14 @@ {# TODO: optional, not supported - Issuer/ReceiverContractReference, Issuer/ReceiverContractDate (contract) - - Issuer/ReceiverTransactionReference, Issuer/ReceiverTransactionDate (sale, contract...) - FileReference, FileDate - SequenceNumber - DeliveryNotesReferences (account_invoice_stock) - TransactionDate - Extensions #} - {{ (line.description and line.description[:2500]) or (line.product and line.product.rec_name[:2500]) or '#'+line.id|string }} + {{ line.facturae_receiver_transaction_reference }} + {{ line.facturae_item_description }} {{ line.quantity }} {{ UOM_CODE2TYPE.get(line.unit.symbol, '05') if line.unit else '05' }} {{ Currency.compute(invoice.currency, line.unit_price, euro) }} @@ -339,7 +340,7 @@ {% endif %} {# TODO: SpecialTaxableEvent not supported #} {% if line.product and line.product.code %} - {{ line.product.code[:20] }} + {{ line.facturae_article_code[:20] }} {% endif %} {% endfor %} diff --git a/tests/test_module.py b/tests/test_module.py index fd45944..fd85cf7 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -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') diff --git a/tryton.cfg b/tryton.cfg index 9587a71..c229770 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -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 diff --git a/view/company_form.xml b/view/company_form.xml deleted file mode 100644 index 3ef4fd6..0000000 --- a/view/company_form.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/view/configuration_form.xml b/view/configuration_form.xml new file mode 100644 index 0000000..328a5fa --- /dev/null +++ b/view/configuration_form.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/view/generate_facturae_start_form.xml b/view/generate_facturae_start_form.xml index ad10464..c0696d4 100644 --- a/view/generate_facturae_start_form.xml +++ b/view/generate_facturae_start_form.xml @@ -10,7 +10,7 @@ yalign="0.0" xalign="0.0" xexpand="1"/>