[ADD] l10n_es_edi_facturae: Add Facturae governmental EDI

Add an implementation to allow companies
to use the Facturae governmental EDI,
by generating and signing the XML

closes odoo/odoo#108350

Task: task-2793440
Signed-off-by: Laurent Smet <las@odoo.com>
This commit is contained in:
Julien Alardot (jual)
2023-05-12 19:54:23 +02:00
parent a171597d19
commit 25ac24361a
36 changed files with 3729 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import csv
from odoo.tools import file_open
from . import models
from . import wizard
def _edit_tax_types(env, template_data):
"""
Applies all existing tax l10n_es_edi_facturae_tax_type field to their proper value if any link between tax and their template is found
"""
concerned_company_ids = [
company.id
for company in env.companies
if company.chart_template and company.chart_template.startswith('es_')
]
if not concerned_company_ids:
return
current_taxes = env['account.tax'].search([('company_id', 'in', concerned_company_ids)])
if not current_taxes:
return
xmlid2tax = {
xml_id.split('.')[1].split('_', maxsplit=1)[1]: env['account.tax'].browse(record)
for record, xml_id in current_taxes.get_external_id().items() if xml_id
}
for xmlid, values in template_data.items():
# Only update the tax_type fields
oldtax = xmlid2tax.get(xmlid)
if oldtax and oldtax.l10n_es_edi_facturae_tax_type != values.get('l10n_es_edi_facturae_tax_type'):
oldtax.l10n_es_edi_facturae_tax_type = values.get('l10n_es_edi_facturae_tax_type')
def _l10n_es_edi_facturae_post_init_hook(env):
"""
We need to replace the existing spanish taxes following the template so the new fields are set properly
"""
if env['account.tax'].search_count([('country_id', '=', env.ref('base.es').id)], limit=1):
with file_open('l10n_es_edi_facturae/data/template/account.tax-es_common.csv') as template_file:
template_data = {record['id']: record['l10n_es_edi_facturae_tax_type'] for record in csv.DictReader(template_file)}
_edit_tax_types(env, template_data)
@@ -0,0 +1,42 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Spain - Facturae EDI',
'version': '1.0',
'category': 'Accounting/Localizations/EDI',
'website': 'https://www.facturae.gob.es/face/Paginas/FACE.aspx',
'description': """
This module create the Facturae file required to send the invoices information to the General State Administrations.
It allows the export and signature of the signing of Facturae files.
The current version of Facturae supported is the 3.2.1
for more informations, see https://www.facturae.gob.es/face/Paginas/FACE.aspx
""",
'depends': [
'l10n_es',
],
'data': [
'data/uom.uom.csv',
'data/facturae_templates.xml',
'data/signature_templates.xml',
'security/ir.model.access.csv',
'security/l10n_es_edi_certificate.xml',
'views/l10n_es_edi_facturae_views.xml',
'views/res_company_views.xml',
'views/account_tax_views.xml',
'views/uom_uom_views.xml',
'views/account_menuitem.xml',
'views/account_move_send_views.xml',
'wizard/account_move_reversal_view.xml',
],
'demo': [
'demo/l10n_es_edi_facturae_demo.xml',
],
'post_init_hook': '_l10n_es_edi_facturae_post_init_hook',
'installable': True,
'auto_install': True,
'license': 'LGPL-3',
}
@@ -0,0 +1,295 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Sub-template used for every instance of AddressType -->
<template id="address_type">
<t t-if="partner_country_code == 'ESP'">
<AddressInSpain>
<Address t-out="', '.join(val for val in (partner.street, partner.street2) if val)"/>
<PostCode t-out="str(partner.zip)"/>
<Town t-out="partner.city"/>
<Province t-out="partner.state_id.name if partner.state_id else partner.country_id.name"/>
<CountryCode t-out="partner_country_code"/>
</AddressInSpain>
</t>
<t t-else="">
<OverseasAddress>
<Address t-out="', '.join(val for val in (partner.street, partner.street2) if val)"/>
<PostCodeAndTown t-out="' '.join(var for var in (str(partner.zip), partner.city) if var)"/>
<Province t-out="partner.state_id.name if partner.state_id else partner.country_id.name"/>
<CountryCode t-out="partner_country_code"/>
</OverseasAddress>
</t>
</template>
<!-- Sub-template used for every instance of ContactDetails -->
<template id="contact_details_type">
<ContactDetails>
<Telephone t-out="partner_phone"/>
<WebAddress t-out="partner.website"/>
<ElectronicMail t-out="partner.email"/>
</ContactDetails>
</template>
<!-- Sub-template used for every instance of LegalEntityType -->
<template id="legal_entity_type">
<LegalEntity>
<CorporateName t-out="partner.name"/>
<TradeName t-out="partner.display_name"/>
<t t-call="l10n_es_edi_facturae.address_type"/>
<t t-call="l10n_es_edi_facturae.contact_details_type"/>
</LegalEntity>
</template>
<!-- Sub-template used for every instance of IndividualType -->
<template id="individual_type">
<Individual>
<Name t-out="partner_name.get('firstname')"/>
<FirstSurname t-out="partner_name.get('surname')"/>
<SecondSurname t-out="partner_name.get('surname2')"/>
<t t-call="l10n_es_edi_facturae.address_type"/>
<t t-call="l10n_es_edi_facturae.contact_details_type"/>
</Individual>
</template>
<!-- Sub-template used for every instance of TaxIdentificationType -->
<template id="tax_identification_type">
<TaxIdentification>
<PersonTypeCode t-out="'J' if partner.is_company else 'F'"/>
<ResidenceTypeCode t-out="partner.l10n_es_edi_facturae_residence_type"/>
<TaxIdentificationNumber t-out="partner.vat"/>
</TaxIdentification>
</template>
<!-- Sub-template used for every instance of BusinessType -->
<template id="business_type">
<t t-call="l10n_es_edi_facturae.tax_identification_type"/>
<t t-if="partner.is_company">
<t t-call="l10n_es_edi_facturae.legal_entity_type"/>
</t>
<t t-else="">
<t t-call="l10n_es_edi_facturae.individual_type"/>
</t>
</template>
<!-- Sub-template used for every instance of TaxType -->
<template id="tax_type">
<Tax>
<TaxTypeCode t-out="tax['tax_record'].l10n_es_edi_facturae_tax_type"/>
<TaxRate t-out="tax['TaxRate']"/>
<TaxableBase>
<TotalAmount t-out="float_repr(refund_multiplier*tax['TaxableBase']['TotalAmount'], file_currency.decimal_places)"/>
<EquivalentInEuros t-if="need_conversion" t-out="float_repr(refund_multiplier*tax['TaxableBase']['EquivalentInEuros'], eur.decimal_places)"/>
</TaxableBase>
<TaxAmount t-if="tax.get('TaxAmount')">
<TotalAmount t-out="float_repr(refund_multiplier*tax['TaxAmount']['TotalAmount'], file_currency.decimal_places)"/>
<EquivalentInEuros t-if="need_conversion" t-out="float_repr(refund_multiplier*tax['TaxAmount']['EquivalentInEuros'], eur.decimal_places)"/>
</TaxAmount>
<SpecialTaxableBase t-if="tax.get('SpecialTaxableBase')">
<TotalAmount t-out="float_repr(refund_multiplier*tax['SpecialTaxableBase']['TotalAmount'], file_currency.decimal_places)"/>
<EquivalentInEuros t-if="need_conversion" t-out="float_repr(refund_multiplier*tax['SpecialTaxableBase']['EquivalentInEuros'], eur.decimal_places)"/>
</SpecialTaxableBase>
<SpecialTaxAmount t-if="tax.get('SpecialTaxAmount')">
<TotalAmount t-out="float_repr(refund_multiplier*tax['SpecialTaxAmount']['TotalAmount'], file_currency.decimal_places)"/>
<EquivalentInEuros t-if="need_conversion" t-out="float_repr(refund_multiplier*tax['SpecialTaxAmount']['EquivalentInEuros'], eur.decimal_places)"/>
</SpecialTaxAmount>
<EquivalenceSurcharge t-out="tax.get('EquivalenceSurcharge')"/>
<EquivalenceSurchargeAmount t-if="tax.get('EquivalenceSurchargeAmount')">
<TotalAmount t-out="float_repr(refund_multiplier*tax['EquivalenceSurchargeAmount']['TotalAmount'], file_currency.decimal_places)"/>
<EquivalentInEuros t-if="need_conversion" t-out="float_repr(refund_multiplier*tax['EquivalenceSurchargeAmount']['EquivalentInEuros'], eur.decimal_places)"/>
</EquivalenceSurchargeAmount>
</Tax>
</template>
<!-- Sub-template used for every instance of InvoiceLineType -->
<template id="invoice_line_type">
<InvoiceLine>
<FileReference t-out="line.get('FileReference')"/>
<FileDate t-out="line.get('FileDate')"/>
<SequenceNumber t-out="line.get('SequenceNumber')"/>
<ItemDescription t-out="line['ItemDescription']"/>
<Quantity t-out="line['Quantity']"/>
<UnitOfMeasure t-out="line.get('UnitOfMeasure')"/>
<UnitPriceWithoutTax t-out="float_repr(refund_multiplier*line['UnitPriceWithoutTax'], file_currency.decimal_places)"/>
<TotalCost t-out="float_repr(refund_multiplier*line['TotalCost'], file_currency.decimal_places)"/>
<DiscountsAndRebates t-if="line.get('DiscountsAndRebates')">
<t t-foreach="line['DiscountsAndRebates']" t-as="discount">
<Discount>
<DiscountReason t-out="discount['DiscountReason']"/>
<DiscountRate t-out="discount.get('DiscountRate')"/>
<DiscountAmount t-out="float_repr(refund_multiplier*discount['DiscountAmount'], file_currency.decimal_places)"/>
</Discount>
</t>
</DiscountsAndRebates>
<Charges t-if="line.get('Charges')">
<t t-foreach="line['Charges']" t-as="charge">
<Charge>
<ChargeReason t-out="charge['ChargeReason']"/>
<ChargeRate t-out="charge.get('ChargeRate')"/>
<ChargeAmount t-out="float_repr(refund_multiplier*charge['ChargeAmount'], file_currency.decimal_places)"/>
</Charge>
</t>
</Charges>
<GrossAmount t-out="float_repr(refund_multiplier*line['GrossAmount'], file_currency.decimal_places)"/>
<TaxesOutputs>
<t t-foreach="line['TaxesOutputs']" t-as="tax"><t t-call="l10n_es_edi_facturae.tax_type"/></t>
</TaxesOutputs>
<TaxesWithheld t-if="line.get('TaxesWithheld')">
<t t-foreach="line['TaxesWithheld']" t-as="tax"><t t-call="l10n_es_edi_facturae.tax_type"/></t>
</TaxesWithheld>
<LineItemPeriod t-if="line.get('LineItemPeriod')">
<StartDate t-out="line['LineItemPeriod']['StartDate']"/>
<EndDate t-out="line['LineItemPeriod']['EndDate']"/>
</LineItemPeriod>
<TransactionDate t-out="line.get('TransactionDate')"/>
</InvoiceLine>
</template>
<template id="corrective_type">
<Corrective>
<InvoiceNumber t-out="invoice['Corrective']['refunded_invoice_record'].name"/>
<ReasonCode t-out="invoice['Corrective']['ReasonCode']"/>
<ReasonDescription t-out="invoice['Corrective']['Reason']"/>
<TaxPeriod>
<StartDate t-out="invoice['Corrective']['TaxPeriod']['StartDate']"/>
<EndDate t-out="invoice['Corrective']['TaxPeriod']['EndDate']"/>
</TaxPeriod>
<CorrectionMethod>01</CorrectionMethod>
<CorrectionMethodDescription>Rectificación modelo íntegro.</CorrectionMethodDescription>
</Corrective>
</template>
<!-- Sub-template used for every instance of InvoiceType -->
<template id="invoice_type">
<Invoice>
<InvoiceHeader>
<InvoiceNumber t-out="invoice['invoice_record'].name"/>
<InvoiceDocumentType t-out="invoice['InvoiceDocumentType']"/>
<InvoiceClass t-out="invoice['InvoiceClass']"/>
<t t-if="refund_multiplier == -1" t-call="l10n_es_edi_facturae.corrective_type"/>
</InvoiceHeader>
<InvoiceIssueData>
<IssueDate t-out="invoice['invoice_record'].invoice_date.isoformat()"/>
<OperationDate t-out="invoice['InvoiceIssueData'].get('OperationDate')"/>
<InvoicingPeriod t-if="invoice['InvoiceIssueData'].get('InvoicingPeriod')">
<StartDate t-out="invoice['InvoiceIssueData']['InvoicingPeriod']['StartDate']"/>
<EndDate t-out="invoice['InvoiceIssueData']['InvoicingPeriod']['EndDate']"/>
</InvoicingPeriod>
<InvoiceCurrencyCode t-out="invoice['invoice_currency'].name"/>
<ExchangeRateDetails t-if="invoice['InvoiceIssueData'].get('ExchangeRateDetails')">
<ExchangeRate t-out="invoice['InvoiceIssueData']['ExchangeRate']"/>
<ExchangeRateDate t-out="(invoice['invoice_record'].invoice_date or invoice['invoice_record'].date).isoformat()"/>
</ExchangeRateDetails>
<TaxCurrencyCode t-out="invoice['invoice_currency'].name"/>
<LanguageName t-out="invoice['InvoiceIssueData']['LanguageName']"/>
</InvoiceIssueData>
<TaxesOutputs>
<t t-foreach="invoice['TaxOutputs']" t-as="tax"><t t-call="l10n_es_edi_facturae.tax_type"/></t>
</TaxesOutputs>
<TaxesWithheld t-if="invoice.get('TaxesWithheld')">
<t t-foreach="invoice['TaxesWithheld']" t-as="tax"><t t-call="l10n_es_edi_facturae.tax_type"/></t>
</TaxesWithheld>
<InvoiceTotals>
<TotalGrossAmount t-out="float_repr(refund_multiplier*invoice['TotalGrossAmount'], file_currency.decimal_places)"/>
<GeneralDiscounts t-if="invoice.get('GeneralDiscounts')">
<t t-foreach="invoice['GeneralDiscounts']" t-as="discount">
<Discount>
<DiscountReason t-out="discount['DiscountReason']"/>
<DiscountRate t-out="discount.get('DiscountRate')"/>
<DiscountAmount t-out="float_repr(refund_multiplier*discount['DiscountAmount'], file_currency.decimal_places)"/>
</Discount>
</t>
</GeneralDiscounts>
<GeneralSurcharges t-if="invoice.get('GeneralSurcharges')">
<t t-foreach="invoice['GeneralSurcharges']" t-as="charge">
<Charge>
<ChargeReason t-out="charge['ChargeReason']"/>
<ChargeRate t-out="charge.get('ChargeRate')"/>
<ChargeAmount t-out="float_repr(refund_multiplier*charge['ChargeAmount'], file_currency.decimal_places)"/>
</Charge>
</t>
</GeneralSurcharges>
<TotalGeneralDiscounts t-if="invoice.get('TotalGeneralDiscounts')" t-out="float_repr(refund_multiplier*invoice['TotalGeneralDiscounts'], file_currency.decimal_places)"/>
<TotalGeneralSurcharges t-if="invoice.get('TotalGeneralSurcharges')" t-out="float_repr(refund_multiplier*invoice['TotalGeneralSurcharges'], file_currency.decimal_places)"/>
<TotalGrossAmountBeforeTaxes t-out="float_repr(refund_multiplier*invoice['TotalGrossAmountBeforeTaxes'], file_currency.decimal_places)"/>
<TotalTaxOutputs t-out="float_repr(refund_multiplier*invoice['TotalTaxOutputs'], file_currency.decimal_places)"/>
<TotalTaxesWithheld t-out="float_repr(refund_multiplier*invoice['TotalTaxesWithheld'], file_currency.decimal_places)"/>
<InvoiceTotal t-out="float_repr(refund_multiplier*invoice['InvoiceTotal'], file_currency.decimal_places)"/>
<PaymentsOnAccount t-if="invoice.get('PaymentsOnAccount')">
<t t-foreach="invoice['PaymentsOnAccount']" t-as="payment">
<PaymentOnAccount>
<PaymentOnAccountDate t-out="payment['PaymentOnAccountDate']"/>
<PaymentOnAccountAmount t-out="float_repr(refund_multiplier*payment['PaymentOnAccountAmount'], file_currency.decimal_places)"/>
</PaymentOnAccount>
</t>
</PaymentsOnAccount>
<TotalFinancialExpenses t-if="invoice.get('ReimbursableExpensesAmount')" t-out="float_repr(refund_multiplier*invoice['ReimbursableExpensesAmount'], file_currency.decimal_places)"/>
<TotalOutstandingAmount t-out="float_repr(refund_multiplier*invoice['TotalOutstandingAmount'], file_currency.decimal_places)"/>
<TotalPaymentsOnAccount t-if="invoice.get('TotalPaymentsOnAccount')" t-out="float_repr(refund_multiplier*invoice['TotalPaymentsOnAccount'], file_currency.decimal_places)"/>
<AmountsWithheld t-if="invoice.get('AmountsWithheld')">
<WithholdingReason t-out="invoice['AmountsWithheld']['WithholdingReason']"/>
<WithholdingRate t-out="invoice['AmountsWithheld'].get('WithholdingRate')"/>
<WithholdingAmount t-out="float_repr(refund_multiplier*invoice['AmountsWithheld']['WithholdingAmount'], file_currency.decimal_places)"/>
</AmountsWithheld>
<TotalExecutableAmount t-out="float_repr(refund_multiplier*invoice['TotalExecutableAmount'], file_currency.decimal_places)"/>
<TotalReimbursableExpenses t-if="invoice.get('TotalReimbursableExpenses')" t-out="float_repr(refund_multiplier*invoice['TotalReimbursableExpenses'], file_currency.decimal_places)"/>
</InvoiceTotals>
<Items>
<t t-foreach="invoice['Items']" t-as="line"><t t-call="l10n_es_edi_facturae.invoice_line_type"/></t>
</Items>
<LegalLiterals t-if="invoice.get('LegalLiterals')">
<t t-foreach="invoice['LegalLiterals']" t-as="reference"><LegalReference t-out="reference"/></t>
</LegalLiterals>
</Invoice>
</template>
<!-- Main template used for the EDI -->
<template id="account_invoice_facturae_export">
<fac:Facturae xmlns:fac="http://www.facturae.es/Facturae/2014/v3.2.1/Facturae">
<FileHeader>
<SchemaVersion>3.2.1</SchemaVersion>
<Modality t-out="Modality"/>
<InvoiceIssuerType t-out="'EM' if is_outstanding else 'RE'"/>
<Batch>
<BatchIdentifier t-out="BatchIdentifier"/>
<InvoicesCount t-out="InvoicesCount"/>
<TotalInvoicesAmount>
<TotalAmount t-out="float_repr(refund_multiplier*TotalInvoicesAmount['TotalAmount'], file_currency.decimal_places)"/>
<EquivalentInEuros t-if="conversion_needed" t-out="float_repr(refund_multiplier*TotalInvoicesAmount['EquivalentInEuros'], eur.decimal_places)"/>
</TotalInvoicesAmount>
<TotalOutstandingAmount>
<TotalAmount t-out="float_repr(refund_multiplier*TotalOutstandingAmount['TotalAmount'], file_currency.decimal_places)"/>
<EquivalentInEuros t-if="conversion_needed" t-out="float_repr(refund_multiplier*TotalOutstandingAmount['EquivalentInEuros'], eur.decimal_places)"/>
</TotalOutstandingAmount>
<TotalExecutableAmount>
<TotalAmount t-out="float_repr(refund_multiplier*TotalExecutableAmount['TotalAmount'], file_currency.decimal_places)"/>
<EquivalentInEuros t-if="conversion_needed" t-out="float_repr(refund_multiplier*TotalExecutableAmount['EquivalentInEuros'], eur.decimal_places)"/>
</TotalExecutableAmount>
<InvoiceCurrencyCode t-out="InvoiceCurrencyCode"/>
</Batch>
</FileHeader>
<Parties>
<SellerParty>
<t t-call="l10n_es_edi_facturae.business_type">
<t t-set="partner" t-value="self_party if is_outstanding else other_party"/>
<t t-set="partner_country_code" t-value="self_party_country_code if is_outstanding else other_party_country_code"/>
<t t-set="partner_phone" t-value="False if is_outstanding else other_party_phone"/>
<t t-set="partner_name" t-value="{} if is_outstanding else other_party_name"/>
</t>
</SellerParty>
<BuyerParty>
<t t-call="l10n_es_edi_facturae.business_type">
<t t-set="partner" t-value="other_party if is_outstanding else self_party"/>
<t t-set="partner_country_code" t-value="other_party_country_code if is_outstanding else self_party_country_code"/>
<t t-set="partner_phone" t-value="other_party_phone if is_outstanding else False"/>
<t t-set="partner_name" t-value="other_party_name if is_outstanding else {}"/>
</t>
</BuyerParty>
</Parties>
<Invoices>
<t t-foreach="Invoices" t-as="invoice"><t t-call="l10n_es_edi_facturae.invoice_type"/></t>
</Invoices>
</fac:Facturae>
</template>
</data>
</odoo>
@@ -0,0 +1,95 @@
<odoo>
<data>
<template id="template_xades_signature">
<ds:Signature t-att-Id="signature_id" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference t-att-Id="reference_uri" Type="http://www.w3.org/2000/09/xmldsig#Object" URI="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
<ds:XPath>not(ancestor-or-self::ds:Signature)</ds:XPath>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue></ds:DigestValue>
</ds:Reference>
<ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" t-attf-URI="##{sigproperties_id}">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue></ds:DigestValue>
</ds:Reference>
<ds:Reference t-attf-URI="##{keyinfo_id}">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue></ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue></ds:SignatureValue>
<ds:KeyInfo t-att-Id="keyinfo_id">
<ds:X509Data>
<ds:X509Certificate t-out="x509_certificate"/>
</ds:X509Data>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus t-out="public_modulus"/>
<ds:Exponent t-out="public_exponent"/>
</ds:RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
<ds:Object>
<xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" t-attf-Target="##{signature_id}">
<xades:SignedProperties t-att-Id="sigproperties_id">
<xades:SignedSignatureProperties>
<xades:SigningTime t-out="iso_now"/>
<xades:SigningCertificateV2>
<xades:Cert>
<xades:CertDigest>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue t-out="sigcertif_digest"/>
</xades:CertDigest>
<xades:IssuerSerial>
<ds:X509IssuerName t-out="x509_issuer_description"/>
<ds:X509SerialNumber t-out="x509_serial_number"/>
</xades:IssuerSerial>
</xades:Cert>
</xades:SigningCertificateV2>
<xades:SignaturePolicyIdentifier>
<xades:SignaturePolicyId>
<xades:SigPolicyId>
<xades:Identifier t-out="sigpolicy_url"/>
<xades:Description t-out="sigpolicy_description"/>
</xades:SigPolicyId>
<xades:SigPolicyHash>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>a/vDoueJq9zxWGCFwdsXdlXtkFSEek4XVtmlQCEDaE8=</ds:DigestValue>
</xades:SigPolicyHash>
<xades:SigPolicyQualifiers>
<xades:SigPolicyQualifier>
<xades:SPURI t-out="sigpolicy_url"/>
</xades:SigPolicyQualifier>
</xades:SigPolicyQualifiers>
</xades:SignaturePolicyId>
</xades:SignaturePolicyIdentifier>
</xades:SignedSignatureProperties>
<xades:SignedDataObjectProperties>
<xades:DataObjectFormat t-attf-ObjectReference="##{reference_uri}">
<xades:Description/>
<xades:ObjectIdentifier>
<!-- Identifier below is Uniform Resource Number for MimeType "text/xml" (no specific version) -->
<!-- 1.2.840.10003.5: see http://www.oid-info.com/cgi-bin/display?oid=1.2.840.10003.5&a=display -->
<!-- 109.10: see https://www.loc.gov/z3950/agency/defns/oids.html#5 -->
<xades:Identifier Qualifier="OIDAsURN">urn:oid:1.2.840.10003.5.109.10</xades:Identifier>
<xades:Description/>
</xades:ObjectIdentifier>
<xades:MimeType>text/xml</xades:MimeType>
<xades:Encoding/>
</xades:DataObjectFormat>
</xades:SignedDataObjectProperties>
</xades:SignedProperties>
</xades:QualifyingProperties>
</ds:Object>
</ds:Signature>
</template>
</data>
</odoo>
@@ -0,0 +1,89 @@
"id","l10n_es_edi_facturae_tax_type"
"l10n_es.account_tax_template_s_iva21b","01"
"l10n_es.account_tax_template_p_iva4_bc","01"
"l10n_es.account_tax_template_p_iva4_ibc","01"
"l10n_es.account_tax_template_p_iva4_sc","01"
"l10n_es.account_tax_template_p_iva4_sp_ex","01"
"l10n_es.account_tax_template_p_iva4_bi","01"
"l10n_es.account_tax_template_p_iva4_ibi","01"
"l10n_es.account_tax_template_p_iva10_bc","01"
"l10n_es.account_tax_template_p_iva10_ibc","01"
"l10n_es.account_tax_template_p_iva10_sc","01"
"l10n_es.account_tax_template_p_iva10_sp_ex","01"
"l10n_es.account_tax_template_p_iva10_bi","01"
"l10n_es.account_tax_template_p_iva10_ibi","01"
"l10n_es.account_tax_template_p_iva21_bc","01"
"l10n_es.account_tax_template_p_iva21_ibc","01"
"l10n_es.account_tax_template_p_iva21_sc","01"
"l10n_es.account_tax_template_p_iva21_sp_ex","01"
"l10n_es.account_tax_template_p_iva21_bi","01"
"l10n_es.account_tax_template_p_iva21_ibi","01"
"l10n_es.account_tax_template_s_iva4b","01"
"l10n_es.account_tax_template_s_iva0_e","01"
"l10n_es.account_tax_template_s_iva4s","01"
"l10n_es.account_tax_template_s_iva_e","01"
"l10n_es.account_tax_template_s_iva10b","01"
"l10n_es.account_tax_template_s_iva10s","01"
"l10n_es.account_tax_template_s_iva21s","01"
"l10n_es.account_tax_template_s_iva21isp","01"
"l10n_es.account_tax_template_p_iva4_ic_bc","01"
"l10n_es.account_tax_template_p_iva4_sp_in","01"
"l10n_es.account_tax_template_p_iva4_ic_bi","01"
"l10n_es.account_tax_template_p_iva10_ic_bc","01"
"l10n_es.account_tax_template_p_iva10_sp_in","01"
"l10n_es.account_tax_template_p_iva10_ic_bi","01"
"l10n_es.account_tax_template_p_iva21_ic_bc","01"
"l10n_es.account_tax_template_p_iva21_sp_in","01"
"l10n_es.account_tax_template_p_iva21_ic_bi","01"
"l10n_es.account_tax_template_s_iva0_ic","01"
"l10n_es.account_tax_template_s_iva0_sp_i","01"
"l10n_es.account_tax_template_p_iva0_ns_b","01"
"l10n_es.account_tax_template_p_iva0_ns","01"
"l10n_es.account_tax_template_s_iva_ns_b","01"
"l10n_es.account_tax_template_s_iva_ns","01"
"l10n_es.account_tax_template_s_req05","05"
"l10n_es.account_tax_template_s_req014","05"
"l10n_es.account_tax_template_s_req52","05"
"l10n_es.account_tax_template_p_req05","05"
"l10n_es.account_tax_template_p_req014","05"
"l10n_es.account_tax_template_p_req52","05"
"l10n_es.account_tax_template_s_iva0_isp","01"
"l10n_es.account_tax_template_s_irpf19a","04"
"l10n_es.account_tax_template_p_irpf19a","04"
"l10n_es.account_tax_template_s_iva0","01"
"l10n_es.account_tax_template_p_iva0_bc","01"
"l10n_es.account_tax_template_s_irpf195a","04"
"l10n_es.account_tax_template_p_irpf195a","04"
"l10n_es.account_tax_template_s_irpf20a","04"
"l10n_es.account_tax_template_p_irpf20a","04"
"l10n_es.account_tax_template_s_irpf21a","04"
"l10n_es.account_tax_template_p_irpf21a","04"
"l10n_es.account_tax_template_s_irpf21","04"
"l10n_es.account_tax_template_s_iva0_ns","01"
"l10n_es.account_tax_template_p_irpf21p","04"
"l10n_es.account_tax_template_s_irpf20","04"
"l10n_es.account_tax_template_p_irpf20","04"
"l10n_es.account_tax_template_s_irpf24","04"
"l10n_es.account_tax_template_p_irpf24","04"
"l10n_es.account_tax_template_s_irpf15","04"
"l10n_es.account_tax_template_p_irpf15","04"
"l10n_es.account_tax_template_s_irpf18","04"
"l10n_es.account_tax_template_p_irpf18","04"
"l10n_es.account_tax_template_s_irpf19","04"
"l10n_es.account_tax_template_p_irpf19","04"
"l10n_es.account_tax_template_s_irpf9","04"
"l10n_es.account_tax_template_p_irpf9","04"
"l10n_es.account_tax_template_s_irpf7","04"
"l10n_es.account_tax_template_p_irpf7","04"
"l10n_es.account_tax_template_s_irpf2","04"
"l10n_es.account_tax_template_p_irpf2","04"
"l10n_es.account_tax_template_s_irpf1","04"
"l10n_es.account_tax_template_p_irpf1","04"
"l10n_es.account_tax_template_p_iva4_isp","01"
"l10n_es.account_tax_template_p_iva4_isp_bi","01"
"l10n_es.account_tax_template_p_iva10_isp","01"
"l10n_es.account_tax_template_p_iva10_isp_bi","01"
"l10n_es.account_tax_template_p_iva21_isp","01"
"l10n_es.account_tax_template_p_iva21_isp_bi","01"
"l10n_es.account_tax_template_p_iva12_agr","01"
"l10n_es.account_tax_template_p_iva105_gan","01"
1 id l10n_es_edi_facturae_tax_type
2 l10n_es.account_tax_template_s_iva21b 01
3 l10n_es.account_tax_template_p_iva4_bc 01
4 l10n_es.account_tax_template_p_iva4_ibc 01
5 l10n_es.account_tax_template_p_iva4_sc 01
6 l10n_es.account_tax_template_p_iva4_sp_ex 01
7 l10n_es.account_tax_template_p_iva4_bi 01
8 l10n_es.account_tax_template_p_iva4_ibi 01
9 l10n_es.account_tax_template_p_iva10_bc 01
10 l10n_es.account_tax_template_p_iva10_ibc 01
11 l10n_es.account_tax_template_p_iva10_sc 01
12 l10n_es.account_tax_template_p_iva10_sp_ex 01
13 l10n_es.account_tax_template_p_iva10_bi 01
14 l10n_es.account_tax_template_p_iva10_ibi 01
15 l10n_es.account_tax_template_p_iva21_bc 01
16 l10n_es.account_tax_template_p_iva21_ibc 01
17 l10n_es.account_tax_template_p_iva21_sc 01
18 l10n_es.account_tax_template_p_iva21_sp_ex 01
19 l10n_es.account_tax_template_p_iva21_bi 01
20 l10n_es.account_tax_template_p_iva21_ibi 01
21 l10n_es.account_tax_template_s_iva4b 01
22 l10n_es.account_tax_template_s_iva0_e 01
23 l10n_es.account_tax_template_s_iva4s 01
24 l10n_es.account_tax_template_s_iva_e 01
25 l10n_es.account_tax_template_s_iva10b 01
26 l10n_es.account_tax_template_s_iva10s 01
27 l10n_es.account_tax_template_s_iva21s 01
28 l10n_es.account_tax_template_s_iva21isp 01
29 l10n_es.account_tax_template_p_iva4_ic_bc 01
30 l10n_es.account_tax_template_p_iva4_sp_in 01
31 l10n_es.account_tax_template_p_iva4_ic_bi 01
32 l10n_es.account_tax_template_p_iva10_ic_bc 01
33 l10n_es.account_tax_template_p_iva10_sp_in 01
34 l10n_es.account_tax_template_p_iva10_ic_bi 01
35 l10n_es.account_tax_template_p_iva21_ic_bc 01
36 l10n_es.account_tax_template_p_iva21_sp_in 01
37 l10n_es.account_tax_template_p_iva21_ic_bi 01
38 l10n_es.account_tax_template_s_iva0_ic 01
39 l10n_es.account_tax_template_s_iva0_sp_i 01
40 l10n_es.account_tax_template_p_iva0_ns_b 01
41 l10n_es.account_tax_template_p_iva0_ns 01
42 l10n_es.account_tax_template_s_iva_ns_b 01
43 l10n_es.account_tax_template_s_iva_ns 01
44 l10n_es.account_tax_template_s_req05 05
45 l10n_es.account_tax_template_s_req014 05
46 l10n_es.account_tax_template_s_req52 05
47 l10n_es.account_tax_template_p_req05 05
48 l10n_es.account_tax_template_p_req014 05
49 l10n_es.account_tax_template_p_req52 05
50 l10n_es.account_tax_template_s_iva0_isp 01
51 l10n_es.account_tax_template_s_irpf19a 04
52 l10n_es.account_tax_template_p_irpf19a 04
53 l10n_es.account_tax_template_s_iva0 01
54 l10n_es.account_tax_template_p_iva0_bc 01
55 l10n_es.account_tax_template_s_irpf195a 04
56 l10n_es.account_tax_template_p_irpf195a 04
57 l10n_es.account_tax_template_s_irpf20a 04
58 l10n_es.account_tax_template_p_irpf20a 04
59 l10n_es.account_tax_template_s_irpf21a 04
60 l10n_es.account_tax_template_p_irpf21a 04
61 l10n_es.account_tax_template_s_irpf21 04
62 l10n_es.account_tax_template_s_iva0_ns 01
63 l10n_es.account_tax_template_p_irpf21p 04
64 l10n_es.account_tax_template_s_irpf20 04
65 l10n_es.account_tax_template_p_irpf20 04
66 l10n_es.account_tax_template_s_irpf24 04
67 l10n_es.account_tax_template_p_irpf24 04
68 l10n_es.account_tax_template_s_irpf15 04
69 l10n_es.account_tax_template_p_irpf15 04
70 l10n_es.account_tax_template_s_irpf18 04
71 l10n_es.account_tax_template_p_irpf18 04
72 l10n_es.account_tax_template_s_irpf19 04
73 l10n_es.account_tax_template_p_irpf19 04
74 l10n_es.account_tax_template_s_irpf9 04
75 l10n_es.account_tax_template_p_irpf9 04
76 l10n_es.account_tax_template_s_irpf7 04
77 l10n_es.account_tax_template_p_irpf7 04
78 l10n_es.account_tax_template_s_irpf2 04
79 l10n_es.account_tax_template_p_irpf2 04
80 l10n_es.account_tax_template_s_irpf1 04
81 l10n_es.account_tax_template_p_irpf1 04
82 l10n_es.account_tax_template_p_iva4_isp 01
83 l10n_es.account_tax_template_p_iva4_isp_bi 01
84 l10n_es.account_tax_template_p_iva10_isp 01
85 l10n_es.account_tax_template_p_iva10_isp_bi 01
86 l10n_es.account_tax_template_p_iva21_isp 01
87 l10n_es.account_tax_template_p_iva21_isp_bi 01
88 l10n_es.account_tax_template_p_iva12_agr 01
89 l10n_es.account_tax_template_p_iva105_gan 01
@@ -0,0 +1,14 @@
"id","l10n_es_edi_facturae_uom_code"
uom.product_uom_unit,01
uom.product_uom_dozen,18
uom.product_uom_hour,02
uom.product_uom_meter,25
uom.product_uom_millimeter,26
uom.product_uom_km,22
uom.product_uom_cm,16
uom.uom_square_meter,05
uom.product_uom_litre,04
uom.product_uom_cubic_meter,33
uom.product_uom_kgm,03
uom.product_uom_gram,21
uom.product_uom_ton,05
1 id l10n_es_edi_facturae_uom_code
2 uom.product_uom_unit 01
3 uom.product_uom_dozen 18
4 uom.product_uom_hour 02
5 uom.product_uom_meter 25
6 uom.product_uom_millimeter 26
7 uom.product_uom_km 22
8 uom.product_uom_cm 16
9 uom.uom_square_meter 05
10 uom.product_uom_litre 04
11 uom.product_uom_cubic_meter 33
12 uom.product_uom_kgm 03
13 uom.product_uom_gram 21
14 uom.product_uom_ton 05
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<record id="face_certificate_demo" model="l10n_es_edi_facturae.certificate">
<field name="password">test</field>
<field name="content" type="base64" file="l10n_es_edi_facturae/demo/certificate_demo.pfx"/>
<field name="company_id" eval="ref('l10n_es.demo_company_es')"/>
</record>
</data>
</odoo>
@@ -0,0 +1,620 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_es_edi_facturae
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.3alpha1+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-05-12 10:00+0000\n"
"PO-Revision-Date: 2023-05-12 10:00+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: l10n_es_edi_facturae
#: model_terms:ir.ui.view,arch_db:l10n_es_edi_facturae.corrective_type
msgid "01"
msgstr ""
#. module: l10n_es_edi_facturae
#: model_terms:ir.ui.view,arch_db:l10n_es_edi_facturae.account_invoice_facturae_export
msgid "3.2.1"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__27
msgid "6-Packs"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model,name:l10n_es_edi_facturae.model_account_move_reversal
msgid "Account Move Reversal"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model,name:l10n_es_edi_facturae.model_account_move_send
msgid "Account Move Send"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__10
msgid "Bags"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__08
msgid "Barrels"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__17
msgid "Bins"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__12
msgid "Bottles, non-protected, cylindrical"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__06
msgid "Boxes"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__24
msgid "Bunches"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__80
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__80
msgid "Calculation of output quotas"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__81
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__81
msgid "Calculation of withholding taxes"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__13
msgid "Canisters"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__23
msgid "Cans, rectangular"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__11
msgid "Carboys, non-protected"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__19
msgid "Cases"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__15
msgid "Centiliters"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__16
msgid "Centimeters"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.ui.menu,name:l10n_es_edi_facturae.menu_l10n_es_edi_facturae_root_certificates
msgid "Certificates"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.actions.act_window,name:l10n_es_edi_facturae.l10n_es_edi_facturae_certificate_action
msgid "Certificates for Facturae EDI invoices on Spain"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model,name:l10n_es_edi_facturae.model_res_company
msgid "Companies"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__company_id
msgid "Company"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model,name:l10n_es_edi_facturae.model_res_partner
msgid "Contact"
msgstr ""
#. module: l10n_es_edi_facturae
#: model_terms:ir.actions.act_window,help:l10n_es_edi_facturae.l10n_es_edi_facturae_certificate_action
msgid "Create the first certificate"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__create_uid
msgid "Created by"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__create_date
msgid "Created on"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__33
msgid "Cubic meter"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__date_end
msgid "Date End"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__date_start
msgid "Date Start"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__03
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__03
msgid "Date of issue"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__13
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__13
msgid "Date/Period to apply"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__20
msgid "Demijohns, non-protected"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__display_name
msgid "Display Name"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__18
msgid "Dozens"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__31
msgid "Envelopes"
msgstr ""
#. module: l10n_es_edi_facturae
#. odoo-python
#: code:addons/l10n_es_edi_facturae/models/account_move_send.py:0
#, python-format
msgid "Errors occured while creating the EDI document (format: %s):"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_account_bank_statement_line__l10n_es_edi_facturae_xml_id
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_account_move__l10n_es_edi_facturae_xml_id
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_account_payment__l10n_es_edi_facturae_xml_id
msgid "Facturae Attachment"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model,name:l10n_es_edi_facturae.model_l10n_es_edi_facturae_certificate
msgid "Facturae Digital Certificate"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_res_company__l10n_es_edi_facturae_residence_type
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_res_partner__l10n_es_edi_facturae_residence_type
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_res_users__l10n_es_edi_facturae_residence_type
msgid "Facturae EDI Residency Type Code"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_res_company__l10n_es_edi_facturae_certificate_id
msgid "Facturae EDI signing certificate"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_account_bank_statement_line__l10n_es_edi_facturae_xml_file
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_account_move__l10n_es_edi_facturae_xml_file
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_account_payment__l10n_es_edi_facturae_xml_file
msgid "Facturae File"
msgstr ""
#. module: l10n_es_edi_facturae
#: model_terms:ir.ui.view,arch_db:l10n_es_edi_facturae.view_company_form_inherit_l10n_es_edi_facturae
msgid "Facturae signature certificate"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_account_move_send__l10n_es_edi_facturae_checkbox_xml
msgid "Generate Facturae edi file"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__21
msgid "Grams"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__02
msgid "Hours"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__id
msgid "ID"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__01
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__01
msgid "Invoice number"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__02
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__02
msgid "Invoice series"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__14
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__14
msgid "Invoice type"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__08
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__08
msgid "Issuer/Oblige Address"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__09
msgid "Jerricans, cylindrical"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model,name:l10n_es_edi_facturae.model_account_move
msgid "Journal Entry"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__03
msgid "Kilograms"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__22
msgid "Kilometers"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_account_move_send__l10n_es_edi_facturae_enable_xml
msgid "L10N Es Edi Facturae Enable Xml"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__write_uid
msgid "Last Updated by"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__write_date
msgid "Last Updated on"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__04
msgid "Liters"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__25
msgid "Meters"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__26
msgid "Millimeters"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__04
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__04
msgid "Name and surname/company name - Issuer"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__05
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__05
msgid "Name and surname/company name - Recipient"
msgstr ""
#. module: l10n_es_edi_facturae
#. odoo-python
#: code:addons/l10n_es_edi_facturae/models/account_move.py:0
#, python-format
msgid ""
"No valid certificate found for this company, Facturae EDI file will not be "
"signed.\n"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__05
msgid "Other"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__content
#: model:ir.model.fields,help:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__content
msgid "PFX Certificate"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__28
msgid "Packages"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,help:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__password
msgid "Passphrase for the PFX certificate"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__password
msgid "Password"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__29
msgid "Portions"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model,name:l10n_es_edi_facturae.model_uom_uom
msgid "Product Unit of Measure"
msgstr ""
#. module: l10n_es_edi_facturae
#: model_terms:ir.ui.view,arch_db:l10n_es_edi_facturae.view_account_move_reversal_inherit_l10n_es_edi_facturae
msgid "Reason"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__09
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__09
msgid "Receiving Address"
msgstr ""
#. module: l10n_es_edi_facturae
#: model_terms:ir.ui.view,arch_db:l10n_es_edi_facturae.corrective_type
msgid "Rectificación modelo íntegro."
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__30
msgid "Rolls"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__34
msgid "Second"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__serial_number
msgid "Serial Number"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.ui.menu,name:l10n_es_edi_facturae.menu_l10n_es_edi_facturae_root
msgid "Spain Facturae EDI"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_uom_uom__l10n_es_edi_facturae_uom_code
msgid "Spanish EDI Units"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_account_bank_statement_line__l10n_es_edi_facturae_reason_code
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_account_move__l10n_es_edi_facturae_reason_code
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_account_move_reversal__l10n_es_edi_facturae_reason_code
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_account_payment__l10n_es_edi_facturae_reason_code
msgid "Spanish Facturae EDI Reason Code"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,field_description:l10n_es_edi_facturae.field_account_tax__l10n_es_edi_facturae_tax_type
msgid "Spanish Facturae EDI Tax Type"
msgstr ""
#. module: l10n_es_edi_facturae
#: model_terms:ir.ui.view,arch_db:l10n_es_edi_facturae.product_uom_categ_form_view_inherit_l10n_es_edi_facturae
#: model_terms:ir.ui.view,arch_db:l10n_es_edi_facturae.product_uom_form_view_inherit_l10n_es_edi_facturae
#: model_terms:ir.ui.view,arch_db:l10n_es_edi_facturae.product_uom_tree_view_inherit_l10n_es_edi_facturae
msgid "Spanish Facturae EDI type"
msgstr ""
#. module: l10n_es_edi_facturae
#: model_terms:ir.ui.view,arch_db:l10n_es_edi_facturae.view_tax_tree_inherit_l10n_es_edi_facturae
msgid "Spanish Tax Type"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__15
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__15
msgid "Statutory letters"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model,name:l10n_es_edi_facturae.model_account_tax
msgid "Tax"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__06
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__06
msgid "Tax identification Issuer/Oblige"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__07
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__07
msgid "Tax identification Receiver"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__11
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__12
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__11
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__12
msgid "Tax rate to be applied"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__16
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__16
msgid "Taxable amount"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__82
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__82
msgid "Taxable amount modified by return of containers/packaging"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__83
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__83
msgid "Taxable income modified by discounts and allowances"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__84
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__84
msgid "Taxable income modified by final, judicial or administrative ruling"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__85
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__85
msgid ""
"Taxable income modified by unpaid tax assessments. Order of declaration of "
"bankruptcy"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__14
msgid "Tetra Briks"
msgstr ""
#. module: l10n_es_edi_facturae
#. odoo-python
#: code:addons/l10n_es_edi_facturae/models/l10n_es_edi_facturae_certificate.py:0
#, python-format
msgid "The certificate is expired since %s"
msgstr ""
#. module: l10n_es_edi_facturae
#. odoo-python
#: code:addons/l10n_es_edi_facturae/models/account_move.py:0
#, python-format
msgid "The company needs a set tax identification number or VAT number"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,help:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__date_end
msgid "The date on which the certificate expires"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,help:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__date_start
msgid "The date on which the certificate starts to be valid"
msgstr ""
#. module: l10n_es_edi_facturae
#. odoo-python
#: code:addons/l10n_es_edi_facturae/models/account_move.py:0
#, python-format
msgid "The partner needs a set tax identification number or VAT number"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields,help:l10n_es_edi_facturae.field_l10n_es_edi_facturae_certificate__serial_number
msgid "The serial number to add to electronic documents"
msgstr ""
#. module: l10n_es_edi_facturae
#. odoo-python
#: code:addons/l10n_es_edi_facturae/models/l10n_es_edi_facturae_certificate.py:0
#, python-format
msgid ""
"There has been a problem with the certificate, some usual problems can be:\n"
"\t- The password given or the certificate are not valid.\n"
"\t- The certificate content is invalid."
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move__l10n_es_edi_facturae_reason_code__10
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__account_move_reversal__l10n_es_edi_facturae_reason_code__10
msgid "Transaction Details"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__07
msgid "Trays, one layer no cover, plastic"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__32
msgid "Tubs"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__01
msgid "Units"
msgstr ""
#. module: l10n_es_edi_facturae
#: model_terms:ir.ui.view,arch_db:l10n_es_edi_facturae.l10n_es_edi_facturae_certificate_form
msgid "Validity"
msgstr ""
#. module: l10n_es_edi_facturae
#: model:ir.model.fields.selection,name:l10n_es_edi_facturae.selection__uom_uom__l10n_es_edi_facturae_uom_code__35
msgid "Watt"
msgstr ""
#. module: l10n_es_edi_facturae
#: model_terms:ir.ui.view,arch_db:l10n_es_edi_facturae.template_xades_signature
msgid "not(ancestor-or-self::ds:Signature)"
msgstr ""
#. module: l10n_es_edi_facturae
#: model_terms:ir.ui.view,arch_db:l10n_es_edi_facturae.template_xades_signature
msgid "text/xml"
msgstr ""
#. module: l10n_es_edi_facturae
#: model_terms:ir.ui.view,arch_db:l10n_es_edi_facturae.template_xades_signature
msgid "urn:oid:1.2.840.10003.5.109.10"
msgstr ""
@@ -0,0 +1,7 @@
from . import account_tax
from . import l10n_es_edi_facturae_certificate
from . import res_company
from . import res_partner
from . import uom_uom
from . import account_move
from . import account_move_send
@@ -0,0 +1,362 @@
from collections import defaultdict
from odoo import fields, models, api, _
from odoo.exceptions import UserError
from odoo.tools import float_repr, date_utils
from odoo.tools.xml_utils import cleanup_xml_node
PHONE_CLEAN_TABLE = str.maketrans({" ": None, "-": None, "(": None, ")": None, "+": None})
COUNTRY_CODE_MAP = {
"BD": "BGD", "BE": "BEL", "BF": "BFA", "BG": "BGR", "BA": "BIH", "BB": "BRB", "WF": "WLF", "BL": "BLM", "BM": "BMU",
"BN": "BRN", "BO": "BOL", "BH": "BHR", "BI": "BDI", "BJ": "BEN", "BT": "BTN", "JM": "JAM", "BV": "BVT", "BW": "BWA",
"WS": "WSM", "BQ": "BES", "BR": "BRA", "BS": "BHS", "JE": "JEY", "BY": "BLR", "BZ": "BLZ", "RU": "RUS", "RW": "RWA",
"RS": "SRB", "TL": "TLS", "RE": "REU", "TM": "TKM", "TJ": "TJK", "RO": "ROU", "TK": "TKL", "GW": "GNB", "GU": "GUM",
"GT": "GTM", "GS": "SGS", "GR": "GRC", "GQ": "GNQ", "GP": "GLP", "JP": "JPN", "GY": "GUY", "GG": "GGY", "GF": "GUF",
"GE": "GEO", "GD": "GRD", "GB": "GBR", "GA": "GAB", "SV": "SLV", "GN": "GIN", "GM": "GMB", "GL": "GRL", "GI": "GIB",
"GH": "GHA", "OM": "OMN", "TN": "TUN", "JO": "JOR", "HR": "HRV", "HT": "HTI", "HU": "HUN", "HK": "HKG", "HN": "HND",
"HM": "HMD", "VE": "VEN", "PR": "PRI", "PS": "PSE", "PW": "PLW", "PT": "PRT", "SJ": "SJM", "PY": "PRY", "IQ": "IRQ",
"PA": "PAN", "PF": "PYF", "PG": "PNG", "PE": "PER", "PK": "PAK", "PH": "PHL", "PN": "PCN", "PL": "POL", "PM": "SPM",
"ZM": "ZMB", "EH": "ESH", "EE": "EST", "EG": "EGY", "ZA": "ZAF", "EC": "ECU", "IT": "ITA", "VN": "VNM", "SB": "SLB",
"ET": "ETH", "SO": "SOM", "ZW": "ZWE", "SA": "SAU", "ES": "ESP", "ER": "ERI", "ME": "MNE", "MD": "MDA", "MG": "MDG",
"MF": "MAF", "MA": "MAR", "MC": "MCO", "UZ": "UZB", "MM": "MMR", "ML": "MLI", "MO": "MAC", "MN": "MNG", "MH": "MHL",
"MK": "MKD", "MU": "MUS", "MT": "MLT", "MW": "MWI", "MV": "MDV", "MQ": "MTQ", "MP": "MNP", "MS": "MSR", "MR": "MRT",
"IM": "IMN", "UG": "UGA", "TZ": "TZA", "MY": "MYS", "MX": "MEX", "IL": "ISR", "FR": "FRA", "IO": "IOT", "SH": "SHN",
"FI": "FIN", "FJ": "FJI", "FK": "FLK", "FM": "FSM", "FO": "FRO", "NI": "NIC", "NL": "NLD", "NO": "NOR", "NA": "NAM",
"VU": "VUT", "NC": "NCL", "NE": "NER", "NF": "NFK", "NG": "NGA", "NZ": "NZL", "NP": "NPL", "NR": "NRU", "NU": "NIU",
"CK": "COK", "XK": "XKX", "CI": "CIV", "CH": "CHE", "CO": "COL", "CN": "CHN", "CM": "CMR", "CL": "CHL", "CC": "CCK",
"CA": "CAN", "CG": "COG", "CF": "CAF", "CD": "COD", "CZ": "CZE", "CY": "CYP", "CX": "CXR", "CR": "CRI", "CW": "CUW",
"CV": "CPV", "CU": "CUB", "SZ": "SWZ", "SY": "SYR", "SX": "SXM", "KG": "KGZ", "KE": "KEN", "SS": "SSD", "SR": "SUR",
"KI": "KIR", "KH": "KHM", "KN": "KNA", "KM": "COM", "ST": "STP", "SK": "SVK", "KR": "KOR", "SI": "SVN", "KP": "PRK",
"KW": "KWT", "SN": "SEN", "SM": "SMR", "SL": "SLE", "SC": "SYC", "KZ": "KAZ", "KY": "CYM", "SG": "SGP", "SE": "SWE",
"SD": "SDN", "DO": "DOM", "DM": "DMA", "DJ": "DJI", "DK": "DNK", "VG": "VGB", "DE": "DEU", "YE": "YEM", "DZ": "DZA",
"US": "USA", "UY": "URY", "YT": "MYT", "UM": "UMI", "LB": "LBN", "LC": "LCA", "LA": "LAO", "TV": "TUV", "TW": "TWN",
"TT": "TTO", "TR": "TUR", "LK": "LKA", "LI": "LIE", "LV": "LVA", "TO": "TON", "LT": "LTU", "LU": "LUX", "LR": "LBR",
"LS": "LSO", "TH": "THA", "TF": "ATF", "TG": "TGO", "TD": "TCD", "TC": "TCA", "LY": "LBY", "VA": "VAT", "VC": "VCT",
"AE": "ARE", "AD": "AND", "AG": "ATG", "AF": "AFG", "AI": "AIA", "VI": "VIR", "IS": "ISL", "IR": "IRN", "AM": "ARM",
"AL": "ALB", "AO": "AGO", "AQ": "ATA", "AS": "ASM", "AR": "ARG", "AU": "AUS", "AT": "AUT", "AW": "ABW", "IN": "IND",
"AX": "ALA", "AZ": "AZE", "IE": "IRL", "ID": "IDN", "UA": "UKR", "QA": "QAT", "MZ": "MOZ"
}
REVERSED_COUNTRY_CODE = {v: k for k, v in COUNTRY_CODE_MAP.items()}
class AccountMove(models.Model):
_inherit = 'account.move'
l10n_es_edi_facturae_xml_id = fields.Many2one(
comodel_name='ir.attachment',
string="Facturae Attachment",
compute=lambda self: self._compute_linked_attachment_id('l10n_es_edi_facturae_xml_id', 'l10n_es_edi_facturae_xml_file'),
depends=['l10n_es_edi_facturae_xml_file']
)
l10n_es_edi_facturae_xml_file = fields.Binary(
attachment=True,
string="Facturae File",
copy=False,
)
l10n_es_edi_facturae_reason_code = fields.Selection(
selection=[
('01', 'Invoice number'),
('02', 'Invoice series'),
('03', 'Date of issue'),
('04', 'Name and surname/company name - Issuer'),
('05', 'Name and surname/company name - Recipient'),
('06', 'Tax identification Issuer/Oblige'),
('07', 'Tax identification Receiver'),
('08', 'Issuer/Oblige Address'),
('09', 'Receiving Address'),
('10', 'Transaction Details'),
('11', 'Tax rate to be applied'),
('12', 'Tax rate to be applied'),
('13', 'Date/Period to apply'),
('14', 'Invoice type'),
('15', 'Statutory letters'),
('16', 'Taxable amount'),
('80', 'Calculation of output quotas'),
('81', 'Calculation of withholding taxes'),
('82', 'Taxable amount modified by return of containers/packaging'),
('83', 'Taxable income modified by discounts and allowances'),
('84', 'Taxable income modified by final, judicial or administrative ruling'),
('85', 'Taxable income modified by unpaid tax assessments. Order of declaration of bankruptcy'),
], string='Spanish Facturae EDI Reason Code', default='10')
def _l10n_es_edi_facturae_get_default_enable(self):
self.ensure_one()
return self.company_id.country_code == 'ES' and self.company_id.currency_id.name == 'EUR'
def _l10n_es_edi_facturae_get_filename(self):
self.ensure_one()
return f'{self.name.replace("/", "_")}_facturae_signed.xml'
def _l10n_es_edi_facturae_get_tax_period(self):
self.ensure_one()
if self.env['res.company'].fields_get(['account_tax_periodicity']):
period_start, period_end = self.company_id._get_tax_closing_period_boundaries(self.date)
else:
period_start = date_utils.start_of(self.date, 'month')
period_end = date_utils.end_of(self.date, 'month')
return {'start':period_start, 'end':period_end}
def _l10n_es_edi_facturae_get_refunded_invoices(self):
self.env['account.partial.reconcile'].flush_model()
invoices_refunded_mapping = {invoice.id: invoice.reversed_entry_id for invoice in self}
queries = []
for source_field, counterpart_field in (('debit', 'credit'), ('credit', 'debit')):
queries.append(f'''
SELECT
source_line.move_id AS source_move_id,
counterpart_line.move_id AS counterpart_move_id
FROM account_partial_reconcile part
JOIN account_move_line source_line ON source_line.id = part.{source_field}_move_id
JOIN account_move_line counterpart_line ON counterpart_line.id = part.{counterpart_field}_move_id
WHERE source_line.move_id IN %s AND counterpart_line.move_id != source_line.move_id
GROUP BY source_move_id, counterpart_move_id
''')
self._cr.execute(' UNION ALL '.join(queries), [tuple(self.ids)] * 2)
payment_data = defaultdict(lambda: [])
for row in self._cr.dictfetchall():
payment_data[row['source_move_id']].append(row)
for invoice in self:
if not invoice.move_type.endswith('refund'):
# We only want to map refunds
continue
for move_id in (record_dict['counterpart_move_id'] for record_dict in payment_data.get(invoice.id, [])):
invoices_refunded_mapping[invoice.id] = move_id
return invoices_refunded_mapping
def _l10n_es_edi_facturae_get_corrective_data(self):
self.ensure_one()
if self.move_type.endswith('refund'):
refunded_invoice = self.env['account.move'].browse(self._l10n_es_edi_facturae_get_refunded_invoices()[self.id])
tax_period = refunded_invoice._l10n_es_edi_facturae_get_tax_period()
reason_code = self.l10n_es_edi_facturae_reason_code or '10'
reason_description = [label for code, label in self._fields['l10n_es_edi_facturae_reason_code'].selection
if code == reason_code][0]
return {
'refunded_invoice_record': refunded_invoice,
'ReasonCode': reason_code,
'Reason': reason_description,
'TaxPeriod': {
'StartDate': tax_period.get('start'),
'EndDate': tax_period.get('end'),
}
}
return {}
@api.model
def _l10n_es_edi_facturae_convert_computed_tax_to_template(self, computed_tax_dict):
""" Helper to convert the tax dict from a _compute_taxes() into a dict usable in the template """
tax = self.env["account.tax"].browse(computed_tax_dict["tax_id"])
return {
"tax_record": tax,
"TaxRate": f'{abs(tax.amount):.3f}',
"TaxableBase": {
'TotalAmount': computed_tax_dict["base_amount_currency"],
'EquivalentInEuros': computed_tax_dict["base_amount"],
},
"TaxAmount": {
"TotalAmount": abs(computed_tax_dict["tax_amount_currency"]),
"EquivalentInEuros": abs(computed_tax_dict["tax_amount"]),
},
}
def _l10n_es_edi_facturae_inv_lines_to_items(self, conversion_rate=None):
"""
Convert the invoice lines to a list of items required for the Facturae xml generation
:param float conversion_rate: Conversion rate of the invoice, if needed
:return: A tuple containing the Face items, the taxes and the invoice totals data.
"""
self.ensure_one()
items = []
totals = {
'total_gross_amount': 0.,
'total_general_discounts': 0.,
'total_general_surcharges': 0.,
'total_taxes_withheld': 0.,
'total_tax_outputs': 0.,
'total_payments_on_account': 0.,
'amounts_withheld': 0.,
}
taxes = []
taxes_withheld = []
for line in self.invoice_line_ids:
if line.display_type in {'line_section', 'line_note'}:
continue
invoice_line_values = {}
price_before_discount = line.currency_id.round(line.price_subtotal / (1 - line.discount / 100.0))
discount = max(0., (price_before_discount - line.price_subtotal))
surcharge = abs(min(0., (price_before_discount - line.price_subtotal)))
totals['total_gross_amount'] += line.price_subtotal
totals['total_general_discounts'] += discount
totals['total_general_surcharges'] += surcharge
base_line = self.env['account.tax']._convert_to_tax_base_line_dict(
line, partner=line.partner_id, currency=line.currency_id, product=line.product_id, taxes=line.tax_ids,
price_unit=line.price_unit, quantity=line.quantity, discount=line.discount, account=line.account_id,
price_subtotal=line.price_subtotal, is_refund=line.is_refund, rate=conversion_rate
)
taxes_computed = self.env['account.tax']._compute_taxes([base_line])
taxes_withheld_computed = [tax for tax in taxes_computed["tax_lines_to_add"] if tax["tax_amount"] < 0]
taxes_normal_computed = [tax for tax in taxes_computed["tax_lines_to_add"] if tax["tax_amount"] >= 0]
taxes_output = [self._l10n_es_edi_facturae_convert_computed_tax_to_template(tax) for tax in taxes_normal_computed]
totals['total_tax_outputs'] += sum((abs(tax["tax_amount"]) for tax in taxes_normal_computed))
tax_withheld_output = [self._l10n_es_edi_facturae_convert_computed_tax_to_template(tax) for tax in taxes_withheld_computed]
totals['total_taxes_withheld'] += sum((abs(tax["tax_amount"]) for tax in taxes_withheld_computed))
invoice_line_values.update({
'ItemDescription': line.name,
'Quantity': line.quantity,
'UnitOfMeasure': line.product_uom_id.l10n_es_edi_facturae_uom_code,
'UnitPriceWithoutTax': line.currency_id.round(price_before_discount / line.quantity if line.quantity else 0.),
'TotalCost': price_before_discount,
'DiscountsAndRebates': [{
'DiscountReason': '',
'DiscountRate': f'{line.discount:.2f}',
'DiscountAmount': discount
}, ] if discount != 0. else [],
'Charges': [{
'ChargeReason': '',
'ChargeRate': f'{max(0, -line.discount):.2f}',
'ChargeAmount': surcharge,
}, ] if surcharge != 0. else [],
'GrossAmount': line.price_subtotal,
'TaxesOutputs': taxes_output,
'TaxesWithheld': tax_withheld_output,
})
items.append(invoice_line_values)
taxes += taxes_output
taxes_withheld += tax_withheld_output
return items, taxes, taxes_withheld, totals
def _l10n_es_edi_facturae_export_facturae(self):
"""
Produce the Facturae XML data for the invoice.
:return: (data needed to render the full template, data needed to render the signature template)
"""
self.ensure_one()
company = self.company_id
partner = self.commercial_partner_id
if not company.vat:
raise UserError(_('The company needs a set tax identification number or VAT number'))
if not partner.vat:
raise UserError(_('The partner needs a set tax identification number or VAT number'))
if self.move_type == "entry":
return False
eur_curr = self.env['res.currency'].search([('name', '=', 'EUR')])
inv_curr = self.currency_id
legal_literals = self.narration.striptags() if self.narration else False
legal_literals = legal_literals.split(";") if legal_literals else False
partner_name = {'firstname': 'UNKNOWN', 'surname': 'UNKNOWN', 'surname2': ''}
if not partner.is_company:
name_split = [part for part in partner.name.replace(', ', ' ').split(' ') if part]
if len(name_split) > 2:
partner_name['firstname'] = ' '.join(name_split[:-2])
partner_name['surname'], partner_name['surname2'] = name_split[-2:]
elif len(name_split) == 2:
partner_name['firstname'] = ' '.join(name_split[:-1])
partner_name['surname'] = name_split[-1]
invoice_issuer_signature_type = 'supplier' if self.move_type == 'out_invoice' else 'customer'
need_conv = bool(inv_curr != eur_curr)
conversion_rate = abs(self.amount_total_in_currency_signed / self.amount_total_signed) if self.amount_total_signed else 0.
total_outst_am_in_currency = abs(self.amount_total_in_currency_signed)
total_outst_am = abs(self.amount_total_signed)
total_exec_am_in_currency = abs(self.amount_total_in_currency_signed)
total_exec_am = abs(self.amount_total_signed)
items, taxes, taxes_withheld, totals = self._l10n_es_edi_facturae_inv_lines_to_items(conversion_rate)
template_values = {
'self_party': company.partner_id,
'self_party_country_code': COUNTRY_CODE_MAP[company.country_id.code],
'other_party': partner,
'other_party_country_code': COUNTRY_CODE_MAP[partner.country_id.code],
'other_party_phone': partner.phone.translate(PHONE_CLEAN_TABLE) if partner.phone else False,
'other_party_name': partner_name,
'is_outstanding': self.move_type.startswith('out_'),
'float_repr': float_repr,
'file_currency': inv_curr,
'eur': eur_curr,
'conversion_needed': need_conv,
'refund_multiplier': -1 if self.move_type.endswith('refund') else 1,
'Modality': 'I',
'BatchIdentifier': self.name,
'InvoicesCount': 1,
'TotalInvoicesAmount': {
'TotalAmount': abs(self.amount_total_in_currency_signed),
'EquivalentInEuros': abs(self.amount_total_signed),
},
'TotalOutstandingAmount': {
'TotalAmount': abs(total_outst_am_in_currency),
'EquivalentInEuros': abs(total_outst_am),
},
'TotalExecutableAmount': {
'TotalAmount': total_exec_am_in_currency,
'EquivalentInEuros': total_exec_am,
},
'InvoiceCurrencyCode': inv_curr.name,
'Invoices': [{
'invoice_record': self,
'invoice_currency': inv_curr,
'InvoiceDocumentType': 'FC',
'InvoiceClass': 'OO',
'Corrective': self._l10n_es_edi_facturae_get_corrective_data(),
'InvoiceIssueData': {
'ExchangeRateDetails': need_conv,
'ExchangeRate': f"{round(conversion_rate, 4):.4f}",
'LanguageName': self._context.get('lang', 'en_US').split('_')[0],
},
'TaxOutputs': taxes,
'TaxesWithheld': taxes_withheld,
'TotalGrossAmount': totals['total_gross_amount'],
'TotalGeneralDiscounts': totals['total_general_discounts'],
'TotalGeneralSurcharges': totals['total_general_surcharges'],
'TotalGrossAmountBeforeTaxes': totals['total_gross_amount'] - totals['total_general_discounts'] + totals['total_general_surcharges'],
'TotalTaxOutputs': totals['total_tax_outputs'],
'TotalTaxesWithheld': totals['total_taxes_withheld'],
'PaymentsOnAccount': [],
'TotalOutstandingAmount': total_outst_am_in_currency,
'InvoiceTotal': abs(self.amount_total_in_currency_signed),
'TotalPaymentsOnAccount': totals['total_payments_on_account'],
'AmountsWithheld': {
'WithholdingReason': '',
'WithholdingRate': False,
'WithholdingAmount': totals['amounts_withheld'],
} if totals['amounts_withheld'] else False,
'TotalExecutableAmount': total_exec_am_in_currency,
'Items': items,
'PaymentDetails': [],
'LegalLiterals': legal_literals,
}],
}
signature_values = {'SigningTime': '', 'SignerRole': invoice_issuer_signature_type, }
return template_values, signature_values
def _l10n_es_edi_facturae_render_facturae(self):
"""
Produce the Facturae XML file for the invoice.
:return: rendered xml file string.
:rtype: str
"""
self.ensure_one()
company = self.company_id
template_values, signature_values = self._l10n_es_edi_facturae_export_facturae()
xml_content = cleanup_xml_node(self.env['ir.qweb']._render('l10n_es_edi_facturae.account_invoice_facturae_export', template_values))
certificate = self.env['l10n_es_edi_facturae.certificate'].search([("company_id", '=', company.id)], limit=1)
try:
xml_content = certificate._sign_xml(xml_content, signature_values)
except ValueError:
raise UserError(_('No valid certificate found for this company, Facturae EDI file will not be signed.\n'))
return xml_content
@@ -0,0 +1,86 @@
from odoo import _, api, fields, models
class AccountMoveSend(models.Model):
_inherit = 'account.move.send'
l10n_es_edi_facturae_enable_xml = fields.Boolean(compute='_compute_send_mail_extra_fields')
l10n_es_edi_facturae_checkbox_xml = fields.Boolean(
string="Generate Facturae edi file",
default=True,
)
# -------------------------------------------------------------------------
# COMPUTE METHODS
# -------------------------------------------------------------------------
def _compute_send_mail_extra_fields(self):
# EXTENDS 'account'
super()._compute_send_mail_extra_fields()
for wizard in self:
wizard.l10n_es_edi_facturae_enable_xml = any(move._l10n_es_edi_facturae_get_default_enable() for move in wizard.move_ids)
@api.depends('l10n_es_edi_facturae_checkbox_xml')
def _compute_mail_attachments_widget(self):
# EXTENDS 'account' - add depends
super()._compute_mail_attachments_widget()
# -------------------------------------------------------------------------
# ATTACHMENTS
# -------------------------------------------------------------------------
@api.model
def _get_linked_attachments(self, move):
# EXTENDS 'account'
return super()._get_linked_attachments(move) + move.l10n_es_edi_facturae_xml_id
def _get_placeholder_mail_attachments_data(self, move):
# EXTENDS 'account'
results = super()._get_placeholder_mail_attachments_data(move)
if self.l10n_es_edi_facturae_enable_xml and self.l10n_es_edi_facturae_checkbox_xml:
filename = f'{move.name.replace("/", "_")}_facturae_signed.xml'
results.append({
'id': f'placeholder_{filename}',
'name': filename,
'mimetype': 'application/xml',
'placeholder': True,
})
return results
# -------------------------------------------------------------------------
# BUSINESS ACTIONS
# -------------------------------------------------------------------------
def _prepare_document(self, invoice):
# EXTENDS 'account'
results = super()._prepare_document(invoice)
if self.l10n_es_edi_facturae_checkbox_xml and invoice._l10n_es_edi_facturae_get_default_enable():
try:
xml_content = invoice._l10n_es_edi_facturae_render_facturae(invoice)
except Exception as error:
return {
'error': "".join((_("Errors occured while creating the EDI document (format: %s):", "Facturae"),
"\n", str(error))),
}
results['l10n_es_edi_facturae_attachment_values'] = {
'name': invoice._l10n_es_edi_facturae_get_filename(),
'raw': xml_content,
'mimetype': 'application/xml',
'res_model': invoice._name,
'res_id': invoice.id,
'res_field': 'l10n_es_edi_facturae_xml_file', # Binary field
}
return results
def _link_document(self, invoice, prepared_data):
# EXTENDS 'account'
super()._link_document(invoice, prepared_data)
attachment_vals = prepared_data.get('l10n_es_edi_facturae_attachment_values')
if attachment_vals:
self.env['ir.attachment'].create(attachment_vals)
invoice.invalidate_model(fnames=['l10n_es_edi_facturae_xml_id', 'l10n_es_edi_facturae_xml_file'])
@@ -0,0 +1,37 @@
from odoo import fields, models
class AccountTax(models.Model):
_inherit = 'account.tax'
l10n_es_edi_facturae_tax_type = fields.Selection((
('01', 'Value-Added Tax'),
('02', 'Taxes on production, services and imports in Ceuta and Melilla'),
('03', 'IGIC: Canaries General Indirect Tax'),
('04', 'IRPF: Personal Income Tax'),
('05', 'Other'),
('06', 'ITPAJD: Tax on wealth transfers and stamp duty'),
('07', 'IE: Excise duties and consumption taxes'),
('08', 'RA: Customs duties'),
('09', 'IGTECM: Sales tax in Ceuta and Melilla'),
('10', 'IECDPCAC: Excise duties on oil derivates in Canaries'),
('11', 'IIIMAB: Tax on premises that affect the environment in the Balearic Islands'),
('12', 'ICIO: Tax on construction, installation and works'),
('13', 'IMVDN: Local tax on unoccupied homes in Navarre'),
('14', 'IMSN: Local tax on building plots in Navarre'),
('15', 'IMGSN: Local sumptuary tax in Navarre'),
('16', 'IMPN: Local tax on advertising in Navarre'),
('17', 'REIVA: Special VAT for travel agencies'),
('18', 'REIGIC: Special IGIC: for travel agencies'),
('19', 'REIPSI: Special IPSI for travel agencies'),
('20', 'IPS: Insurance premiums Tax'),
('21', 'SWUA: Surcharge for Winding Up Activity'),
('22', 'IVPEE: Tax on the value of electricity generation'),
('23', 'Tax on the production of spent nuclear fuel and radioactive waste from the generation of nuclear electric power'),
('24', 'Tax on the storage of spent nuclear energy and radioactive waste in centralised facilities'),
('25', 'IDEC: Tax on bank deposits'),
('26', 'Excise duty applied to manufactured tobacco in Canaries'),
('27', 'IGFEI: Tax on Fluorinated Greenhouse Gases'),
('28', 'IRNR: Non-resident Income Tax'),
('29', 'Corporation Tax'),
), string='Spanish Facturae EDI Tax Type', default='01')
@@ -0,0 +1,109 @@
import re
from base64 import b64decode, b64encode, encodebytes
from copy import deepcopy
from hashlib import sha1
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.serialization import pkcs12
from lxml import etree
from odoo import _, api, fields, models
from odoo.addons.l10n_es_edi_facturae import xml_utils
from odoo.exceptions import UserError
from odoo.tools import cleanup_xml_node
class Certificate(models.Model):
_name = 'l10n_es_edi_facturae.certificate'
_description = 'Facturae Digital Certificate'
_order = 'date_start desc, id desc'
_rec_name = 'serial_number'
content = fields.Binary(string="PFX Certificate", required=True, help="PFX Certificate")
password = fields.Char(help="Passphrase for the PFX certificate")
serial_number = fields.Char(readonly=True, index=True, help="The serial number to add to electronic documents")
date_start = fields.Datetime(readonly=True, help="The date on which the certificate starts to be valid")
date_end = fields.Datetime(readonly=True, help="The date on which the certificate expires")
company_id = fields.Many2one(comodel_name='res.company', default=lambda self: self.env.company, required=True, readonly=True)
def _decode_certificate(self):
"""
Return certificate data
:return tuple: private_key, certificate
"""
self.ensure_one()
content, password = b64decode(self.with_context(bin_size=False).content), self.password.encode()
private_key, certificate, *_dummy = pkcs12.load_key_and_certificates(content, password, backend=default_backend())
return private_key, certificate
# -------------------------------------------------------------------------
# LOW-LEVEL METHODS
# -------------------------------------------------------------------------
@api.model_create_multi
def create(self, vals_list):
certificates = super().create(vals_list)
for certificate in certificates:
try:
_key, certif = certificate._decode_certificate()
except ValueError:
raise UserError(_('There has been a problem with the certificate, some usual problems can be:\n'
'\t- The password given or the certificate are not valid.\n'
'\t- The certificate content is invalid.'))
if fields.datetime.now() > certif.not_valid_after:
raise UserError(_('The certificate is expired since %s', certif.not_valid_after))
# Assign extracted values from the certificate
certificate.write({'serial_number': certif.serial_number, 'date_start': certif.not_valid_before, 'date_end': certif.not_valid_after})
return certificates
# -------------------------------------------------------------------------
# BUSINESS METHODS #
# -------------------------------------------------------------------------
def _sign_xml(self, edi_data, signature_data):
"""
Signs the given XML data with the certificate and private key.
:param etree._Element edi_data: The XML data to sign.
:param dict signature_data: The signature data to use.
:return: The signed XML data string.
:rtype: str
"""
self.ensure_one()
if not (self.date_start < fields.Datetime.now() < self.date_end):
raise UserError('Facturae certificate date is not valid, its validity has probably expired')
root = deepcopy(edi_data)
cert_private, cert_public = self._decode_certificate()
public_key_numbers = cert_public.public_key().public_numbers()
rdns_pattern = re.compile(r'(?<=\()([A-Z]{1,2}=.*)(?=\))')
issuer = ', '.join(sorted(match.group() for match in (rdns_pattern.search(str(element)) for element in cert_public.issuer.rdns) if match))
# Identifiers
document_id = f"Document-{sha1(etree.tostring(edi_data)).hexdigest()}"
signature_id = f"Signature-{document_id}"
keyinfo_id = f"KeyInfo-{document_id}"
sigproperties_id = f"SignatureProperties-{document_id}"
signature_data.update({
'document_id': document_id,
'x509_certificate': encodebytes(cert_public.public_bytes(encoding=serialization.Encoding.DER)).decode(),
'public_modulus': encodebytes(xml_utils._int_to_bytes(public_key_numbers.n)).decode(),
'public_exponent': encodebytes(xml_utils._int_to_bytes(public_key_numbers.e)).decode(),
'iso_now': fields.datetime.now().isoformat(),
'keyinfo_id': keyinfo_id,
'signature_id': signature_id,
'sigproperties_id': sigproperties_id,
'reference_uri': "Reference-" + document_id,
'sigpolicy_url': "http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf",
'sigpolicy_description': "Política de firma electrónica para facturación electrónica con formato Facturae",
'sigcertif_digest': b64encode(cert_public.fingerprint(hashes.SHA256())).decode(),
'x509_issuer_description': issuer,
'x509_serial_number': cert_public.serial_number,
})
signature = self.env['ir.qweb']._render('l10n_es_edi_facturae.template_xades_signature', signature_data)
signature = cleanup_xml_node(signature, remove_blank_nodes=False)
root.append(signature)
xml_utils._reference_digests(signature.find("ds:SignedInfo", namespaces=xml_utils.NS_MAP))
xml_utils._fill_signature(signature, cert_private)
return etree.tostring(root, xml_declaration=True, encoding='UTF-8', standalone=True)
@@ -0,0 +1,9 @@
from odoo import fields, models
class Company(models.Model):
_inherit = 'res.company'
l10n_es_edi_facturae_residence_type = fields.Char(string='Facturae EDI Residency Type Code', related='partner_id.l10n_es_edi_facturae_residence_type')
l10n_es_edi_facturae_certificate_id = fields.One2many(string='Facturae EDI signing certificate',
comodel_name='l10n_es_edi_facturae.certificate', inverse_name='company_id')
@@ -0,0 +1,20 @@
from odoo import api, fields, models
class Partner(models.Model):
_inherit = 'res.partner'
l10n_es_edi_facturae_residence_type = fields.Char(string='Facturae EDI Residency Type Code',
compute='_compute_l10n_es_edi_facturae_residence_type', store=False, readonly=True,)
@api.depends('country_id')
def _compute_l10n_es_edi_facturae_residence_type(self):
eu_country_ids = self.env.ref('base.europe').country_ids.ids
for partner in self:
country = partner.country_id
if country.code == 'ES':
partner.l10n_es_edi_facturae_residence_type = 'R'
elif country.id in eu_country_ids:
partner.l10n_es_edi_facturae_residence_type = 'U'
else:
partner.l10n_es_edi_facturae_residence_type = 'E'
@@ -0,0 +1,44 @@
from odoo import fields, models
class UoM(models.Model):
_inherit = 'uom.uom'
l10n_es_edi_facturae_uom_code = fields.Selection(
selection=[
('01', 'Units'),
('02', 'Hours'),
('03', 'Kilograms'),
('04', 'Liters'),
('05', 'Other'),
('06', 'Boxes'),
('07', 'Trays, one layer no cover, plastic'),
('08', 'Barrels'),
('09', 'Jerricans, cylindrical'),
('10', 'Bags'),
('11', 'Carboys, non-protected'),
('12', 'Bottles, non-protected, cylindrical'),
('13', 'Canisters'),
('14', 'Tetra Briks'),
('15', 'Centiliters'),
('16', 'Centimeters'),
('17', 'Bins'),
('18', 'Dozens'),
('19', 'Cases'),
('20', 'Demijohns, non-protected'),
('21', 'Grams'),
('22', 'Kilometers'),
('23', 'Cans, rectangular'),
('24', 'Bunches'),
('25', 'Meters'),
('26', 'Millimeters'),
('27', '6-Packs'),
('28', 'Packages'),
('29', 'Portions'),
('30', 'Rolls'),
('31', 'Envelopes'),
('32', 'Tubs'),
('33', 'Cubic meter'),
('34', 'Second'),
('35', 'Watt'),
], string='Spanish EDI Units', default="05", required=True)
@@ -0,0 +1,3 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
l10n_es_edi_facturae.access_l10n_es_edi_facturae_certificate_manager,access_l10n_es_edi_facturae_certificate,l10n_es_edi_facturae.model_l10n_es_edi_facturae_certificate,account.group_account_manager,1,0,0,0
l10n_es_edi_facturae.access_l10n_es_edi_facturae_certificate_system,access_l10n_es_edi_facturae_certificate,l10n_es_edi_facturae.model_l10n_es_edi_facturae_certificate,base.group_system,1,1,1,1
1 id name model_id/id group_id/id perm_read perm_write perm_create perm_unlink
2 l10n_es_edi_facturae.access_l10n_es_edi_facturae_certificate_manager access_l10n_es_edi_facturae_certificate l10n_es_edi_facturae.model_l10n_es_edi_facturae_certificate account.group_account_manager 1 0 0 0
3 l10n_es_edi_facturae.access_l10n_es_edi_facturae_certificate_system access_l10n_es_edi_facturae_certificate l10n_es_edi_facturae.model_l10n_es_edi_facturae_certificate base.group_system 1 1 1 1
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<!-- Allow read certificates of my company-->
<record id="facturae_digital_certificate" model="ir.rule">
<field name="name">Facturae Digital Certificate</field>
<field name="model_id" ref="l10n_es_edi_facturae.model_l10n_es_edi_facturae_certificate"/>
<field name="groups" eval="[Command.link(ref('account.group_account_user')), Command.link(ref('account.group_account_manager'))]"/>
<field name="domain_force">[('company_id', 'in', user.company_ids.ids)]</field>
</record>
</odoo>
@@ -0,0 +1 @@
from . import test_edi_xml
@@ -0,0 +1,358 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<fac:Facturae xmlns:fac="http://www.facturae.es/Facturae/2014/v3.2.1/Facturae">
<FileHeader>
<SchemaVersion>3.2.1</SchemaVersion>
<Modality>I</Modality>
<InvoiceIssuerType>RE</InvoiceIssuerType>
<Batch>
<BatchIdentifier>BILL/2023/01/0001</BatchIdentifier>
<InvoicesCount>1</InvoicesCount>
<TotalInvoicesAmount>
<TotalAmount>2904.00</TotalAmount>
</TotalInvoicesAmount>
<TotalOutstandingAmount>
<TotalAmount>2904.00</TotalAmount>
</TotalOutstandingAmount>
<TotalExecutableAmount>
<TotalAmount>2904.00</TotalAmount>
</TotalExecutableAmount>
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
</Batch>
</FileHeader>
<Parties>
<SellerParty>
<TaxIdentification>
<PersonTypeCode>F</PersonTypeCode>
<ResidenceTypeCode>U</ResidenceTypeCode>
<TaxIdentificationNumber>BE0477472701</TaxIdentificationNumber>
</TaxIdentification>
<Individual>
<Name>UNKNOWN</Name>
<FirstSurname>UNKNOWN</FirstSurname>
<OverseasAddress>
<Address>Rue de Bruxelles, 15000</Address>
<PostCodeAndTown>5000 Namur</PostCodeAndTown>
<Province>Belgium</Province>
<CountryCode>BEL</CountryCode>
</OverseasAddress>
</Individual>
</SellerParty>
<BuyerParty>
<TaxIdentification>
<PersonTypeCode>J</PersonTypeCode>
<ResidenceTypeCode>R</ResidenceTypeCode>
<TaxIdentificationNumber>ES59962470K</TaxIdentificationNumber>
</TaxIdentification>
<LegalEntity>
<CorporateName>company_1_data</CorporateName>
<TradeName>company_1_data</TradeName>
<AddressInSpain>
<Address>C. de Embajadores, 68-116</Address>
<PostCode>12345</PostCode>
<Town>Madrid</Town>
<Province>Madrid</Province>
<CountryCode>ESP</CountryCode>
</AddressInSpain>
</LegalEntity>
</BuyerParty>
</Parties>
<Invoices>
<Invoice>
<InvoiceHeader>
<InvoiceNumber>BILL/2023/01/0001</InvoiceNumber>
<InvoiceDocumentType>FC</InvoiceDocumentType>
<InvoiceClass>OO</InvoiceClass>
</InvoiceHeader>
<InvoiceIssueData>
<IssueDate>2023-01-01</IssueDate>
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
<TaxCurrencyCode>EUR</TaxCurrencyCode>
<LanguageName>en</LanguageName>
</InvoiceIssueData>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>200.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>42.00</TotalAmount>
</TaxAmount>
</Tax>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>900.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>189.00</TotalAmount>
</TaxAmount>
</Tax>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>1100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>231.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
<InvoiceTotals>
<TotalGrossAmount>2400.00</TotalGrossAmount>
<TotalGeneralDiscounts>100.00</TotalGeneralDiscounts>
<TotalGeneralSurcharges>100.00</TotalGeneralSurcharges>
<TotalGrossAmountBeforeTaxes>2400.00</TotalGrossAmountBeforeTaxes>
<TotalTaxOutputs>504.00</TotalTaxOutputs>
<TotalTaxesWithheld>0.00</TotalTaxesWithheld>
<InvoiceTotal>2904.00</InvoiceTotal>
<TotalOutstandingAmount>2904.00</TotalOutstandingAmount>
<TotalExecutableAmount>2904.00</TotalExecutableAmount>
</InvoiceTotals>
<Items>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>100.00</UnitPriceWithoutTax>
<TotalCost>100.00</TotalCost>
<GrossAmount>100.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
</InvoiceLine>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>100.00</UnitPriceWithoutTax>
<TotalCost>100.00</TotalCost>
<GrossAmount>100.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
</InvoiceLine>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>200.00</UnitPriceWithoutTax>
<TotalCost>200.00</TotalCost>
<GrossAmount>200.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>200.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>42.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
</InvoiceLine>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>1000.00</UnitPriceWithoutTax>
<TotalCost>1000.00</TotalCost>
<DiscountsAndRebates>
<Discount>
<DiscountRate>10.00</DiscountRate>
<DiscountAmount>100.00</DiscountAmount>
</Discount>
</DiscountsAndRebates>
<GrossAmount>900.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>900.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>189.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
</InvoiceLine>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>1000.00</UnitPriceWithoutTax>
<TotalCost>1000.00</TotalCost>
<Charges>
<Charge>
<ChargeRate>10.00</ChargeRate>
<ChargeAmount>100.00</ChargeAmount>
</Charge>
</Charges>
<GrossAmount>1100.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>1100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>231.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
</InvoiceLine>
</Items>
</Invoice>
</Invoices>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="Signature-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference Type="http://www.w3.org/2000/09/xmldsig#Object" URI="" Id="Reference-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
<ds:XPath>not(ancestor-or-self::ds:Signature)</ds:XPath>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>___ignore___</ds:DigestValue>
</ds:Reference>
<ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#SignatureProperties-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>UQH7ea0mtLoqLN/aM5mWssQ38fOK1bxnWT4fKQBYiSI=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#KeyInfo-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>wwDtGZ3EpRH8tRKQ8lKgbpfDkE1Zmza9QHkjJDN1Nmw=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>___ignore___</ds:SignatureValue>
<ds:KeyInfo Id="KeyInfo-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:X509Data>
<ds:X509Certificate>MIID8jCCAtqgAwIBAgIUYBl+61DsTkNyyUFGveskbaGP/SkwDQYJKoZIhvcNAQELBQAwgYoxCzAJ
BgNVBAYTAkJFMRIwEAYDVQQIDAlXYWxsb25uaWExEjAQBgNVBAcMCVJhbWlsbGllczENMAsGA1UE
CgwET2RvbzEMMAoGA1UECwwDUiZEMRgwFgYDVQQDDA9ydW5ib3Qub2Rvby5jb20xHDAaBgkqhkiG
9w0BCQEWDWluZm9Ab2Rvby5jb20wHhcNMjIwNDI2MDczMTUwWhcNMjMwNDI2MDczMTUwWjCBlTEL
MAkGA1UEBhMCQkUxEjAQBgNVBAgMCVdhbGxvbm5pYTESMBAGA1UEBwwJUmFtaWxsaWVzMRIwEAYD
VQQKDAlPZG9vIFMuQS4xGTAXBgNVBAsMEFImRCAtIEFjY291bnRpbmcxETAPBgNVBAMMCG9kb28u
Y29tMRwwGgYJKoZIhvcNAQkBFg1pbmZvQG9kb28uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAtGUbyNJrA3yyOC01gIMR1A1rWIHCHD+afLCLCddBDZwC4Q034yFBuMqQFNWaxxLc
9s+wFbNZqu5D7rynN3qXvWJj40VZxygoV/09y71IhaspuAZ0rdzYsqjDmTJiPSrEl6iydF2eq6sJ
4J0ItCUFc2e88B25wz7esslAN249C20oQj5ZiGifR4P0CRQSmDy0SPYZGxTurVwtq6IsYbOqEw8/
Wqx6ApOltperFX88DZCdbY59ZedqQrGQh2Z5Au7h/8nhmdbpz7Ixw2zocxVigR0ClNDpKKxvolX1
virQpICkfWcAaWAYAMCL4KhvISclysD+5juDLpGCLHPtKxBXTQIDAQABo0MwQTAfBgNVHSMEGDAW
gBTmxEG4Ub7b5wRXqWea94Vs1hB13zAJBgNVHRMEAjAAMBMGA1UdEQQMMAqCCG9kb28uY29tMA0G
CSqGSIb3DQEBCwUAA4IBAQAFLvTlSELTVWRfIlGthoFOmkv8MrwJuo8y7qTmvsFXeA2hZtsPdNxx
iwAGPobiUO0GCEPUFKZ7NksQa7DAKGXfb5RSwfujDfEMAhgi00+wF1VIKeKqnGt4idzCr+2NtiQY
e9oK+Ee7o82llnNFwbWvRKx55lhHW3bwN6Tl31qUYbxacJyA9z5sQpe2zImWMVsm4g7OAjzg3Cn2
0TFu+0XekB892n7DYh4gHiMWaMOVYmi/aEWLSqxMlbiHQr75FmPftozFvTxW26jU3ALhq+yXPVa4
lg4r/h6HH5TbtSjYzTSJ4MvMyrECgIQYf0zsS4D6K/GvCZbu7LKHRwuC0fis</ds:X509Certificate>
</ds:X509Data>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>tGUbyNJrA3yyOC01gIMR1A1rWIHCHD+afLCLCddBDZwC4Q034yFBuMqQFNWaxxLc9s+wFbNZqu5D
7rynN3qXvWJj40VZxygoV/09y71IhaspuAZ0rdzYsqjDmTJiPSrEl6iydF2eq6sJ4J0ItCUFc2e8
8B25wz7esslAN249C20oQj5ZiGifR4P0CRQSmDy0SPYZGxTurVwtq6IsYbOqEw8/Wqx6ApOltper
FX88DZCdbY59ZedqQrGQh2Z5Au7h/8nhmdbpz7Ixw2zocxVigR0ClNDpKKxvolX1virQpICkfWcA
aWAYAMCL4KhvISclysD+5juDLpGCLHPtKxBXTQ==</ds:Modulus>
<ds:Exponent>AQAB</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
<ds:Object>
<xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Target="#Signature-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<xades:SignedProperties Id="SignatureProperties-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<xades:SignedSignatureProperties>
<xades:SigningTime>2023-01-01T00:00:00</xades:SigningTime>
<xades:SigningCertificateV2>
<xades:Cert>
<xades:CertDigest>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>FHf2lN+5FALNa7JXV2+pByL92/dr8LeCVhpItYic5oA=</ds:DigestValue>
</xades:CertDigest>
<xades:IssuerSerial>
<ds:X509IssuerName>C=BE, CN=runbot.odoo.com, L=Ramillies, O=Odoo, OU=R&amp;D, ST=Wallonnia</ds:X509IssuerName>
<ds:X509SerialNumber>548631688851000697209704649636588277530075594025</ds:X509SerialNumber>
</xades:IssuerSerial>
</xades:Cert>
</xades:SigningCertificateV2>
<xades:SignaturePolicyIdentifier>
<xades:SignaturePolicyId>
<xades:SigPolicyId>
<xades:Identifier>http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf</xades:Identifier>
<xades:Description>Política de firma electrónica para facturación electrónica con formato Facturae</xades:Description>
</xades:SigPolicyId>
<xades:SigPolicyHash>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>a/vDoueJq9zxWGCFwdsXdlXtkFSEek4XVtmlQCEDaE8=</ds:DigestValue>
</xades:SigPolicyHash>
<xades:SigPolicyQualifiers>
<xades:SigPolicyQualifier>
<xades:SPURI>http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf</xades:SPURI>
</xades:SigPolicyQualifier>
</xades:SigPolicyQualifiers>
</xades:SignaturePolicyId>
</xades:SignaturePolicyIdentifier>
</xades:SignedSignatureProperties>
<xades:SignedDataObjectProperties>
<xades:DataObjectFormat ObjectReference="#Reference-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<xades:Description/>
<xades:ObjectIdentifier>
<xades:Identifier Qualifier="OIDAsURN">urn:oid:1.2.840.10003.5.109.10</xades:Identifier>
<xades:Description/>
</xades:ObjectIdentifier>
<xades:MimeType>text/xml</xades:MimeType>
<xades:Encoding/>
</xades:DataObjectFormat>
</xades:SignedDataObjectProperties>
</xades:SignedProperties>
</xades:QualifyingProperties>
</ds:Object>
</ds:Signature>
</fac:Facturae>
@@ -0,0 +1,265 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<fac:Facturae xmlns:fac="http://www.facturae.es/Facturae/2014/v3.2.1/Facturae">
<FileHeader>
<SchemaVersion>3.2.1</SchemaVersion>
<Modality>I</Modality>
<InvoiceIssuerType>EM</InvoiceIssuerType>
<Batch>
<BatchIdentifier>RINV/2023/00001</BatchIdentifier>
<InvoicesCount>1</InvoicesCount>
<TotalInvoicesAmount>
<TotalAmount>-242.00</TotalAmount>
</TotalInvoicesAmount>
<TotalOutstandingAmount>
<TotalAmount>-242.00</TotalAmount>
</TotalOutstandingAmount>
<TotalExecutableAmount>
<TotalAmount>-242.00</TotalAmount>
</TotalExecutableAmount>
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
</Batch>
</FileHeader>
<Parties>
<SellerParty>
<TaxIdentification>
<PersonTypeCode>J</PersonTypeCode>
<ResidenceTypeCode>R</ResidenceTypeCode>
<TaxIdentificationNumber>ES59962470K</TaxIdentificationNumber>
</TaxIdentification>
<LegalEntity>
<CorporateName>company_1_data</CorporateName>
<TradeName>company_1_data</TradeName>
<AddressInSpain>
<Address>C. de Embajadores, 68-116</Address>
<PostCode>12345</PostCode>
<Town>Madrid</Town>
<Province>Madrid</Province>
<CountryCode>ESP</CountryCode>
</AddressInSpain>
</LegalEntity>
</SellerParty>
<BuyerParty>
<TaxIdentification>
<PersonTypeCode>F</PersonTypeCode>
<ResidenceTypeCode>U</ResidenceTypeCode>
<TaxIdentificationNumber>BE0477472701</TaxIdentificationNumber>
</TaxIdentification>
<Individual>
<Name>UNKNOWN</Name>
<FirstSurname>UNKNOWN</FirstSurname>
<OverseasAddress>
<Address>Rue de Bruxelles, 15000</Address>
<PostCodeAndTown>5000 Namur</PostCodeAndTown>
<Province>Belgium</Province>
<CountryCode>BEL</CountryCode>
</OverseasAddress>
</Individual>
</BuyerParty>
</Parties>
<Invoices>
<Invoice>
<InvoiceHeader>
<InvoiceNumber>RINV/2023/00001</InvoiceNumber>
<InvoiceDocumentType>FC</InvoiceDocumentType>
<InvoiceClass>OO</InvoiceClass>
<Corrective>
<InvoiceNumber>INV/2023/00001</InvoiceNumber>
<ReasonCode>01</ReasonCode>
<ReasonDescription>Invoice number</ReasonDescription>
<TaxPeriod>
<StartDate>2023-01-01</StartDate>
<EndDate>2023-01-31</EndDate>
</TaxPeriod>
<CorrectionMethod>01</CorrectionMethod>
<CorrectionMethodDescription>Rectificación modelo íntegro.</CorrectionMethodDescription>
</Corrective>
</InvoiceHeader>
<InvoiceIssueData>
<IssueDate>2023-01-01</IssueDate>
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
<TaxCurrencyCode>EUR</TaxCurrencyCode>
<LanguageName>en</LanguageName>
</InvoiceIssueData>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>-100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>-21.00</TotalAmount>
</TaxAmount>
</Tax>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>-100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>-21.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
<InvoiceTotals>
<TotalGrossAmount>-200.00</TotalGrossAmount>
<TotalGrossAmountBeforeTaxes>-200.00</TotalGrossAmountBeforeTaxes>
<TotalTaxOutputs>-42.00</TotalTaxOutputs>
<TotalTaxesWithheld>-0.00</TotalTaxesWithheld>
<InvoiceTotal>-242.00</InvoiceTotal>
<TotalOutstandingAmount>-242.00</TotalOutstandingAmount>
<TotalExecutableAmount>-242.00</TotalExecutableAmount>
</InvoiceTotals>
<Items>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>-100.00</UnitPriceWithoutTax>
<TotalCost>-100.00</TotalCost>
<GrossAmount>-100.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>-100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>-21.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
</InvoiceLine>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>-100.00</UnitPriceWithoutTax>
<TotalCost>-100.00</TotalCost>
<GrossAmount>-100.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>-100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>-21.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
</InvoiceLine>
</Items>
</Invoice>
</Invoices>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="Signature-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference Type="http://www.w3.org/2000/09/xmldsig#Object" URI="" Id="Reference-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
<ds:XPath>not(ancestor-or-self::ds:Signature)</ds:XPath>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>___ignore___</ds:DigestValue>
</ds:Reference>
<ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#SignatureProperties-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>UQH7ea0mtLoqLN/aM5mWssQ38fOK1bxnWT4fKQBYiSI=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#KeyInfo-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>wwDtGZ3EpRH8tRKQ8lKgbpfDkE1Zmza9QHkjJDN1Nmw=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>___ignore___</ds:SignatureValue>
<ds:KeyInfo Id="KeyInfo-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:X509Data>
<ds:X509Certificate>MIID8jCCAtqgAwIBAgIUYBl+61DsTkNyyUFGveskbaGP/SkwDQYJKoZIhvcNAQELBQAwgYoxCzAJ
BgNVBAYTAkJFMRIwEAYDVQQIDAlXYWxsb25uaWExEjAQBgNVBAcMCVJhbWlsbGllczENMAsGA1UE
CgwET2RvbzEMMAoGA1UECwwDUiZEMRgwFgYDVQQDDA9ydW5ib3Qub2Rvby5jb20xHDAaBgkqhkiG
9w0BCQEWDWluZm9Ab2Rvby5jb20wHhcNMjIwNDI2MDczMTUwWhcNMjMwNDI2MDczMTUwWjCBlTEL
MAkGA1UEBhMCQkUxEjAQBgNVBAgMCVdhbGxvbm5pYTESMBAGA1UEBwwJUmFtaWxsaWVzMRIwEAYD
VQQKDAlPZG9vIFMuQS4xGTAXBgNVBAsMEFImRCAtIEFjY291bnRpbmcxETAPBgNVBAMMCG9kb28u
Y29tMRwwGgYJKoZIhvcNAQkBFg1pbmZvQG9kb28uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAtGUbyNJrA3yyOC01gIMR1A1rWIHCHD+afLCLCddBDZwC4Q034yFBuMqQFNWaxxLc
9s+wFbNZqu5D7rynN3qXvWJj40VZxygoV/09y71IhaspuAZ0rdzYsqjDmTJiPSrEl6iydF2eq6sJ
4J0ItCUFc2e88B25wz7esslAN249C20oQj5ZiGifR4P0CRQSmDy0SPYZGxTurVwtq6IsYbOqEw8/
Wqx6ApOltperFX88DZCdbY59ZedqQrGQh2Z5Au7h/8nhmdbpz7Ixw2zocxVigR0ClNDpKKxvolX1
virQpICkfWcAaWAYAMCL4KhvISclysD+5juDLpGCLHPtKxBXTQIDAQABo0MwQTAfBgNVHSMEGDAW
gBTmxEG4Ub7b5wRXqWea94Vs1hB13zAJBgNVHRMEAjAAMBMGA1UdEQQMMAqCCG9kb28uY29tMA0G
CSqGSIb3DQEBCwUAA4IBAQAFLvTlSELTVWRfIlGthoFOmkv8MrwJuo8y7qTmvsFXeA2hZtsPdNxx
iwAGPobiUO0GCEPUFKZ7NksQa7DAKGXfb5RSwfujDfEMAhgi00+wF1VIKeKqnGt4idzCr+2NtiQY
e9oK+Ee7o82llnNFwbWvRKx55lhHW3bwN6Tl31qUYbxacJyA9z5sQpe2zImWMVsm4g7OAjzg3Cn2
0TFu+0XekB892n7DYh4gHiMWaMOVYmi/aEWLSqxMlbiHQr75FmPftozFvTxW26jU3ALhq+yXPVa4
lg4r/h6HH5TbtSjYzTSJ4MvMyrECgIQYf0zsS4D6K/GvCZbu7LKHRwuC0fis</ds:X509Certificate>
</ds:X509Data>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>tGUbyNJrA3yyOC01gIMR1A1rWIHCHD+afLCLCddBDZwC4Q034yFBuMqQFNWaxxLc9s+wFbNZqu5D
7rynN3qXvWJj40VZxygoV/09y71IhaspuAZ0rdzYsqjDmTJiPSrEl6iydF2eq6sJ4J0ItCUFc2e8
8B25wz7esslAN249C20oQj5ZiGifR4P0CRQSmDy0SPYZGxTurVwtq6IsYbOqEw8/Wqx6ApOltper
FX88DZCdbY59ZedqQrGQh2Z5Au7h/8nhmdbpz7Ixw2zocxVigR0ClNDpKKxvolX1virQpICkfWcA
aWAYAMCL4KhvISclysD+5juDLpGCLHPtKxBXTQ==</ds:Modulus>
<ds:Exponent>AQAB</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
<ds:Object>
<xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Target="#Signature-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<xades:SignedProperties Id="SignatureProperties-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<xades:SignedSignatureProperties>
<xades:SigningTime>2023-01-01T00:00:00</xades:SigningTime>
<xades:SigningCertificateV2>
<xades:Cert>
<xades:CertDigest>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>FHf2lN+5FALNa7JXV2+pByL92/dr8LeCVhpItYic5oA=</ds:DigestValue>
</xades:CertDigest>
<xades:IssuerSerial>
<ds:X509IssuerName>C=BE, CN=runbot.odoo.com, L=Ramillies, O=Odoo, OU=R&amp;D, ST=Wallonnia</ds:X509IssuerName>
<ds:X509SerialNumber>548631688851000697209704649636588277530075594025</ds:X509SerialNumber>
</xades:IssuerSerial>
</xades:Cert>
</xades:SigningCertificateV2>
<xades:SignaturePolicyIdentifier>
<xades:SignaturePolicyId>
<xades:SigPolicyId>
<xades:Identifier>http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf</xades:Identifier>
<xades:Description>Política de firma electrónica para facturación electrónica con formato Facturae</xades:Description>
</xades:SigPolicyId>
<xades:SigPolicyHash>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>a/vDoueJq9zxWGCFwdsXdlXtkFSEek4XVtmlQCEDaE8=</ds:DigestValue>
</xades:SigPolicyHash>
<xades:SigPolicyQualifiers>
<xades:SigPolicyQualifier>
<xades:SPURI>http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf</xades:SPURI>
</xades:SigPolicyQualifier>
</xades:SigPolicyQualifiers>
</xades:SignaturePolicyId>
</xades:SignaturePolicyIdentifier>
</xades:SignedSignatureProperties>
<xades:SignedDataObjectProperties>
<xades:DataObjectFormat ObjectReference="#Reference-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<xades:Description/>
<xades:ObjectIdentifier>
<xades:Identifier Qualifier="OIDAsURN">urn:oid:1.2.840.10003.5.109.10</xades:Identifier>
<xades:Description/>
</xades:ObjectIdentifier>
<xades:MimeType>text/xml</xades:MimeType>
<xades:Encoding/>
</xades:DataObjectFormat>
</xades:SignedDataObjectProperties>
</xades:SignedProperties>
</xades:QualifyingProperties>
</ds:Object>
</ds:Signature>
</fac:Facturae>
@@ -0,0 +1,358 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<fac:Facturae xmlns:fac="http://www.facturae.es/Facturae/2014/v3.2.1/Facturae">
<FileHeader>
<SchemaVersion>3.2.1</SchemaVersion>
<Modality>I</Modality>
<InvoiceIssuerType>EM</InvoiceIssuerType>
<Batch>
<BatchIdentifier>INV/2023/00001</BatchIdentifier>
<InvoicesCount>1</InvoicesCount>
<TotalInvoicesAmount>
<TotalAmount>2904.00</TotalAmount>
</TotalInvoicesAmount>
<TotalOutstandingAmount>
<TotalAmount>2904.00</TotalAmount>
</TotalOutstandingAmount>
<TotalExecutableAmount>
<TotalAmount>2904.00</TotalAmount>
</TotalExecutableAmount>
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
</Batch>
</FileHeader>
<Parties>
<SellerParty>
<TaxIdentification>
<PersonTypeCode>J</PersonTypeCode>
<ResidenceTypeCode>R</ResidenceTypeCode>
<TaxIdentificationNumber>ES59962470K</TaxIdentificationNumber>
</TaxIdentification>
<LegalEntity>
<CorporateName>company_1_data</CorporateName>
<TradeName>company_1_data</TradeName>
<AddressInSpain>
<Address>C. de Embajadores, 68-116</Address>
<PostCode>12345</PostCode>
<Town>Madrid</Town>
<Province>Madrid</Province>
<CountryCode>ESP</CountryCode>
</AddressInSpain>
</LegalEntity>
</SellerParty>
<BuyerParty>
<TaxIdentification>
<PersonTypeCode>F</PersonTypeCode>
<ResidenceTypeCode>U</ResidenceTypeCode>
<TaxIdentificationNumber>BE0477472701</TaxIdentificationNumber>
</TaxIdentification>
<Individual>
<Name>UNKNOWN</Name>
<FirstSurname>UNKNOWN</FirstSurname>
<OverseasAddress>
<Address>Rue de Bruxelles, 15000</Address>
<PostCodeAndTown>5000 Namur</PostCodeAndTown>
<Province>Belgium</Province>
<CountryCode>BEL</CountryCode>
</OverseasAddress>
</Individual>
</BuyerParty>
</Parties>
<Invoices>
<Invoice>
<InvoiceHeader>
<InvoiceNumber>INV/2023/00001</InvoiceNumber>
<InvoiceDocumentType>FC</InvoiceDocumentType>
<InvoiceClass>OO</InvoiceClass>
</InvoiceHeader>
<InvoiceIssueData>
<IssueDate>2023-01-01</IssueDate>
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
<TaxCurrencyCode>EUR</TaxCurrencyCode>
<LanguageName>en</LanguageName>
</InvoiceIssueData>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>200.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>42.00</TotalAmount>
</TaxAmount>
</Tax>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>900.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>189.00</TotalAmount>
</TaxAmount>
</Tax>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>1100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>231.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
<InvoiceTotals>
<TotalGrossAmount>2400.00</TotalGrossAmount>
<TotalGeneralDiscounts>100.00</TotalGeneralDiscounts>
<TotalGeneralSurcharges>100.00</TotalGeneralSurcharges>
<TotalGrossAmountBeforeTaxes>2400.00</TotalGrossAmountBeforeTaxes>
<TotalTaxOutputs>504.00</TotalTaxOutputs>
<TotalTaxesWithheld>0.00</TotalTaxesWithheld>
<InvoiceTotal>2904.00</InvoiceTotal>
<TotalOutstandingAmount>2904.00</TotalOutstandingAmount>
<TotalExecutableAmount>2904.00</TotalExecutableAmount>
</InvoiceTotals>
<Items>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>100.00</UnitPriceWithoutTax>
<TotalCost>100.00</TotalCost>
<GrossAmount>100.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
</InvoiceLine>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>100.00</UnitPriceWithoutTax>
<TotalCost>100.00</TotalCost>
<GrossAmount>100.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
</InvoiceLine>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>200.00</UnitPriceWithoutTax>
<TotalCost>200.00</TotalCost>
<GrossAmount>200.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>200.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>42.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
</InvoiceLine>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>1000.00</UnitPriceWithoutTax>
<TotalCost>1000.00</TotalCost>
<DiscountsAndRebates>
<Discount>
<DiscountRate>10.00</DiscountRate>
<DiscountAmount>100.00</DiscountAmount>
</Discount>
</DiscountsAndRebates>
<GrossAmount>900.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>900.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>189.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
</InvoiceLine>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>1000.00</UnitPriceWithoutTax>
<TotalCost>1000.00</TotalCost>
<Charges>
<Charge>
<ChargeRate>10.00</ChargeRate>
<ChargeAmount>100.00</ChargeAmount>
</Charge>
</Charges>
<GrossAmount>1100.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>1100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>231.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
</InvoiceLine>
</Items>
</Invoice>
</Invoices>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="Signature-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference Type="http://www.w3.org/2000/09/xmldsig#Object" URI="" Id="Reference-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
<ds:XPath>not(ancestor-or-self::ds:Signature)</ds:XPath>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>___ignore___</ds:DigestValue>
</ds:Reference>
<ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#SignatureProperties-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>UQH7ea0mtLoqLN/aM5mWssQ38fOK1bxnWT4fKQBYiSI=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#KeyInfo-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>wwDtGZ3EpRH8tRKQ8lKgbpfDkE1Zmza9QHkjJDN1Nmw=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>___ignore___</ds:SignatureValue>
<ds:KeyInfo Id="KeyInfo-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<ds:X509Data>
<ds:X509Certificate>MIID8jCCAtqgAwIBAgIUYBl+61DsTkNyyUFGveskbaGP/SkwDQYJKoZIhvcNAQELBQAwgYoxCzAJ
BgNVBAYTAkJFMRIwEAYDVQQIDAlXYWxsb25uaWExEjAQBgNVBAcMCVJhbWlsbGllczENMAsGA1UE
CgwET2RvbzEMMAoGA1UECwwDUiZEMRgwFgYDVQQDDA9ydW5ib3Qub2Rvby5jb20xHDAaBgkqhkiG
9w0BCQEWDWluZm9Ab2Rvby5jb20wHhcNMjIwNDI2MDczMTUwWhcNMjMwNDI2MDczMTUwWjCBlTEL
MAkGA1UEBhMCQkUxEjAQBgNVBAgMCVdhbGxvbm5pYTESMBAGA1UEBwwJUmFtaWxsaWVzMRIwEAYD
VQQKDAlPZG9vIFMuQS4xGTAXBgNVBAsMEFImRCAtIEFjY291bnRpbmcxETAPBgNVBAMMCG9kb28u
Y29tMRwwGgYJKoZIhvcNAQkBFg1pbmZvQG9kb28uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAtGUbyNJrA3yyOC01gIMR1A1rWIHCHD+afLCLCddBDZwC4Q034yFBuMqQFNWaxxLc
9s+wFbNZqu5D7rynN3qXvWJj40VZxygoV/09y71IhaspuAZ0rdzYsqjDmTJiPSrEl6iydF2eq6sJ
4J0ItCUFc2e88B25wz7esslAN249C20oQj5ZiGifR4P0CRQSmDy0SPYZGxTurVwtq6IsYbOqEw8/
Wqx6ApOltperFX88DZCdbY59ZedqQrGQh2Z5Au7h/8nhmdbpz7Ixw2zocxVigR0ClNDpKKxvolX1
virQpICkfWcAaWAYAMCL4KhvISclysD+5juDLpGCLHPtKxBXTQIDAQABo0MwQTAfBgNVHSMEGDAW
gBTmxEG4Ub7b5wRXqWea94Vs1hB13zAJBgNVHRMEAjAAMBMGA1UdEQQMMAqCCG9kb28uY29tMA0G
CSqGSIb3DQEBCwUAA4IBAQAFLvTlSELTVWRfIlGthoFOmkv8MrwJuo8y7qTmvsFXeA2hZtsPdNxx
iwAGPobiUO0GCEPUFKZ7NksQa7DAKGXfb5RSwfujDfEMAhgi00+wF1VIKeKqnGt4idzCr+2NtiQY
e9oK+Ee7o82llnNFwbWvRKx55lhHW3bwN6Tl31qUYbxacJyA9z5sQpe2zImWMVsm4g7OAjzg3Cn2
0TFu+0XekB892n7DYh4gHiMWaMOVYmi/aEWLSqxMlbiHQr75FmPftozFvTxW26jU3ALhq+yXPVa4
lg4r/h6HH5TbtSjYzTSJ4MvMyrECgIQYf0zsS4D6K/GvCZbu7LKHRwuC0fis</ds:X509Certificate>
</ds:X509Data>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>tGUbyNJrA3yyOC01gIMR1A1rWIHCHD+afLCLCddBDZwC4Q034yFBuMqQFNWaxxLc9s+wFbNZqu5D
7rynN3qXvWJj40VZxygoV/09y71IhaspuAZ0rdzYsqjDmTJiPSrEl6iydF2eq6sJ4J0ItCUFc2e8
8B25wz7esslAN249C20oQj5ZiGifR4P0CRQSmDy0SPYZGxTurVwtq6IsYbOqEw8/Wqx6ApOltper
FX88DZCdbY59ZedqQrGQh2Z5Au7h/8nhmdbpz7Ixw2zocxVigR0ClNDpKKxvolX1virQpICkfWcA
aWAYAMCL4KhvISclysD+5juDLpGCLHPtKxBXTQ==</ds:Modulus>
<ds:Exponent>AQAB</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
<ds:Object>
<xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Target="#Signature-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<xades:SignedProperties Id="SignatureProperties-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<xades:SignedSignatureProperties>
<xades:SigningTime>2023-01-01T00:00:00</xades:SigningTime>
<xades:SigningCertificateV2>
<xades:Cert>
<xades:CertDigest>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>FHf2lN+5FALNa7JXV2+pByL92/dr8LeCVhpItYic5oA=</ds:DigestValue>
</xades:CertDigest>
<xades:IssuerSerial>
<ds:X509IssuerName>C=BE, CN=runbot.odoo.com, L=Ramillies, O=Odoo, OU=R&amp;D, ST=Wallonnia</ds:X509IssuerName>
<ds:X509SerialNumber>548631688851000697209704649636588277530075594025</ds:X509SerialNumber>
</xades:IssuerSerial>
</xades:Cert>
</xades:SigningCertificateV2>
<xades:SignaturePolicyIdentifier>
<xades:SignaturePolicyId>
<xades:SigPolicyId>
<xades:Identifier>http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf</xades:Identifier>
<xades:Description>Política de firma electrónica para facturación electrónica con formato Facturae</xades:Description>
</xades:SigPolicyId>
<xades:SigPolicyHash>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>a/vDoueJq9zxWGCFwdsXdlXtkFSEek4XVtmlQCEDaE8=</ds:DigestValue>
</xades:SigPolicyHash>
<xades:SigPolicyQualifiers>
<xades:SigPolicyQualifier>
<xades:SPURI>http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf</xades:SPURI>
</xades:SigPolicyQualifier>
</xades:SigPolicyQualifiers>
</xades:SignaturePolicyId>
</xades:SignaturePolicyIdentifier>
</xades:SignedSignatureProperties>
<xades:SignedDataObjectProperties>
<xades:DataObjectFormat ObjectReference="#Reference-Document-da39a3ee5e6b4b0d3255bfef95601890afd80709">
<xades:Description/>
<xades:ObjectIdentifier>
<xades:Identifier Qualifier="OIDAsURN">urn:oid:1.2.840.10003.5.109.10</xades:Identifier>
<xades:Description/>
</xades:ObjectIdentifier>
<xades:MimeType>text/xml</xades:MimeType>
<xades:Encoding/>
</xades:DataObjectFormat>
</xades:SignedDataObjectProperties>
</xades:SignedProperties>
</xades:QualifyingProperties>
</ds:Object>
</ds:Signature>
</fac:Facturae>
@@ -0,0 +1,331 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<fac:Facturae xmlns:fac="http://www.facturae.es/Facturae/2014/v3.2.1/Facturae">
<FileHeader>
<SchemaVersion>3.2.1</SchemaVersion>
<Modality>I</Modality>
<InvoiceIssuerType>EM</InvoiceIssuerType>
<Batch>
<BatchIdentifier>INV/2023/00001</BatchIdentifier>
<InvoicesCount>1</InvoicesCount>
<TotalInvoicesAmount>
<TotalAmount>400.00</TotalAmount>
</TotalInvoicesAmount>
<TotalOutstandingAmount>
<TotalAmount>400.00</TotalAmount>
</TotalOutstandingAmount>
<TotalExecutableAmount>
<TotalAmount>400.00</TotalAmount>
</TotalExecutableAmount>
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
</Batch>
</FileHeader>
<Parties>
<SellerParty>
<TaxIdentification>
<PersonTypeCode>J</PersonTypeCode>
<ResidenceTypeCode>R</ResidenceTypeCode>
<TaxIdentificationNumber>ES59962470K</TaxIdentificationNumber>
</TaxIdentification>
<LegalEntity>
<CorporateName>company_1_data</CorporateName>
<TradeName>company_1_data</TradeName>
<AddressInSpain>
<Address>C. de Embajadores, 68-116</Address>
<PostCode>12345</PostCode>
<Town>Madrid</Town>
<Province>Madrid</Province>
<CountryCode>ESP</CountryCode>
</AddressInSpain>
</LegalEntity>
</SellerParty>
<BuyerParty>
<TaxIdentification>
<PersonTypeCode>F</PersonTypeCode>
<ResidenceTypeCode>U</ResidenceTypeCode>
<TaxIdentificationNumber>BE0477472701</TaxIdentificationNumber>
</TaxIdentification>
<Individual>
<Name>UNKNOWN</Name>
<FirstSurname>UNKNOWN</FirstSurname>
<OverseasAddress>
<Address>Rue de Bruxelles, 15000</Address>
<PostCodeAndTown>5000 Namur</PostCodeAndTown>
<Province>Belgium</Province>
<CountryCode>BEL</CountryCode>
</OverseasAddress>
</Individual>
</BuyerParty>
</Parties>
<Invoices>
<Invoice>
<InvoiceHeader>
<InvoiceNumber>INV/2023/00001</InvoiceNumber>
<InvoiceDocumentType>FC</InvoiceDocumentType>
<InvoiceClass>OO</InvoiceClass>
</InvoiceHeader>
<InvoiceIssueData>
<IssueDate>2023-01-01</IssueDate>
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
<TaxCurrencyCode>EUR</TaxCurrencyCode>
<LanguageName>en</LanguageName>
</InvoiceIssueData>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>200.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>42.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
<TaxesWithheld>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>200.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>42.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesWithheld>
<InvoiceTotals>
<TotalGrossAmount>400.00</TotalGrossAmount>
<TotalGrossAmountBeforeTaxes>400.00</TotalGrossAmountBeforeTaxes>
<TotalTaxOutputs>84.00</TotalTaxOutputs>
<TotalTaxesWithheld>84.00</TotalTaxesWithheld>
<InvoiceTotal>400.00</InvoiceTotal>
<TotalOutstandingAmount>400.00</TotalOutstandingAmount>
<TotalExecutableAmount>400.00</TotalExecutableAmount>
</InvoiceTotals>
<Items>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>100.00</UnitPriceWithoutTax>
<TotalCost>100.00</TotalCost>
<GrossAmount>100.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
<TaxesWithheld>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesWithheld>
</InvoiceLine>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>100.00</UnitPriceWithoutTax>
<TotalCost>100.00</TotalCost>
<GrossAmount>100.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
<TaxesWithheld>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>100.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>21.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesWithheld>
</InvoiceLine>
<InvoiceLine>
<ItemDescription>product_a</ItemDescription>
<Quantity>1.0</Quantity>
<UnitOfMeasure>01</UnitOfMeasure>
<UnitPriceWithoutTax>200.00</UnitPriceWithoutTax>
<TotalCost>200.00</TotalCost>
<GrossAmount>200.00</GrossAmount>
<TaxesOutputs>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>200.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>42.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesOutputs>
<TaxesWithheld>
<Tax>
<TaxTypeCode>01</TaxTypeCode>
<TaxRate>21.000</TaxRate>
<TaxableBase>
<TotalAmount>200.00</TotalAmount>
</TaxableBase>
<TaxAmount>
<TotalAmount>42.00</TotalAmount>
</TaxAmount>
</Tax>
</TaxesWithheld>
</InvoiceLine>
</Items>
</Invoice>
</Invoices>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xd="http://uri.etsi.org/01903/v1.3.2#" Id="___ignore___">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="___ignore___"/>
<ds:SignatureMethod Algorithm="___ignore___"/>
<ds:Reference Id="___ignore___" Type="___ignore___" URI="___ignore___">
<ds:Transforms>
<ds:Transform Algorithm="___ignore___"/>
<ds:Transform Algorithm="___ignore___"/>
<ds:Transform Algorithm="___ignore___">
<ds:XPath>___ignore___</ds:XPath>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="___ignore___"/>
<ds:DigestValue>___ignore___</ds:DigestValue>
</ds:Reference>
<ds:Reference Type="___ignore___" URI="___ignore___">
<ds:DigestMethod Algorithm="___ignore___"/>
<ds:DigestValue>___ignore___</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="___ignore___">
<ds:DigestMethod Algorithm="___ignore___"/>
<ds:DigestValue>___ignore___</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>___ignore___</ds:SignatureValue>
<ds:KeyInfo Id="___ignore___">
<ds:X509Data>
<ds:X509Certificate>___ignore___</ds:X509Certificate>
</ds:X509Data>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>___ignore___</ds:Modulus>
<ds:Exponent>___ignore___</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
<ds:Object>
<xd:QualifyingProperties Target="___ignore___">
<xd:SignedProperties Id="___ignore___">
<xd:SignedSignatureProperties>
<xd:SigningTime>___ignore___</xd:SigningTime>
<xd:SigningCertificateV2>
<xd:Cert>
<xd:CertDigest>
<ds:DigestMethod Algorithm="___ignore___"/>
<ds:DigestValue>___ignore___</ds:DigestValue>
</xd:CertDigest>
<xd:IssuerSerial>
<ds:X509IssuerName>___ignore___</ds:X509IssuerName>
<ds:X509SerialNumber>___ignore___</ds:X509SerialNumber>
</xd:IssuerSerial>
</xd:Cert>
</xd:SigningCertificateV2>
<xd:SignaturePolicyIdentifier>
<xd:SignaturePolicyId>
<xd:SigPolicyId>
<xd:Identifier>___ignore___</xd:Identifier>
<xd:Description>___ignore___</xd:Description>
</xd:SigPolicyId>
<xd:SigPolicyHash>
<ds:DigestMethod Algorithm="___ignore___"/>
<ds:DigestValue>___ignore___</ds:DigestValue>
</xd:SigPolicyHash>
<xd:SigPolicyQualifiers>
<xd:SigPolicyQualifier>
<xd:SPURI>___ignore___</xd:SPURI>
</xd:SigPolicyQualifier>
</xd:SigPolicyQualifiers>
</xd:SignaturePolicyId>
</xd:SignaturePolicyIdentifier>
</xd:SignedSignatureProperties>
<xd:SignedDataObjectProperties>
<xd:DataObjectFormat ObjectReference="___ignore___">
<xd:Description>___ignore___</xd:Description>
<xd:ObjectIdentifier>
<xd:Identifier Qualifier="OIDAsURN">___ignore___</xd:Identifier>
<xd:Description>___ignore___</xd:Description>
</xd:ObjectIdentifier>
<xd:MimeType>___ignore___</xd:MimeType>
<xd:Encoding>___ignore___</xd:Encoding>
</xd:DataObjectFormat>
</xd:SignedDataObjectProperties>
</xd:SignedProperties>
</xd:QualifyingProperties>
</ds:Object>
</ds:Signature>
</fac:Facturae>
@@ -0,0 +1,222 @@
import logging
import random
from base64 import b64encode
from datetime import datetime
from hashlib import sha1
from unittest.mock import patch
import lxml
from freezegun import freeze_time
from odoo import Command
from odoo.addons.account_edi.tests.common import AccountEdiTestCommon
from odoo.exceptions import UserError
from odoo.tests import tagged
from odoo.tools import file_open
_logger = logging.getLogger(__name__)
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestEdiFacturaeXmls(AccountEdiTestCommon):
@classmethod
def setUpClass(cls, chart_template_ref='es_full', edi_format_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
cls.frozen_today = datetime(year=2023, month=1, day=1, hour=0, minute=0, second=0)
# ==== Companies ====
cls.company_data['company'].write({ # -> PersonTypeCode 'J'
'country_id': cls.env.ref('base.es').id, # -> ResidenceTypeCode 'R'
'street': "C. de Embajadores, 68-116",
'state_id': cls.env.ref('base.state_es_m').id,
'city': "Madrid",
'zip': "12345",
'vat': 'ES59962470K',
})
cls.company_data_2['company'].write({ # -> PersonTypeCode 'J'
'country_id': cls.env.ref('base.us').id, # -> ResidenceTypeCode 'R'
})
# ==== Business ====
cls.partner_a.write({ # -> PersonTypeCode 'F'
'country_id': cls.env.ref('base.be').id, # -> ResidenceTypeCode 'U'
'vat': 'BE0477472701',
'city': "Namur",
'street': "Rue de Bruxelles, 15000",
'zip': "5000",
})
cls.password = "test"
cls.certificate_module = "odoo.addons.l10n_es_edi_facturae.models.l10n_es_edi_facturae_certificate"
with freeze_time(cls.frozen_today), patch(f"{cls.certificate_module}.fields.datetime.now", lambda x=None: cls.frozen_today):
cls.certificate = cls.env["l10n_es_edi_facturae.certificate"].sudo().create({
"content": b64encode(file_open('l10n_es_edi_facturae/tests/data/certificate_test.pfx', 'rb').read()),
"password": "test",
'company_id': cls.company_data['company'].id,
})
cls.tax, cls.tax_2 = cls.env['account.tax'].create([{
'name': "IVA 21% (Bienes)",
'company_id': cls.company_data['company'].id,
'amount': 21.0,
'price_include': False,
'l10n_es_edi_facturae_tax_type': '01'
}, {
'name': "IVA 21% (Bienes) Included",
'company_id': cls.company_data['company'].id,
'amount': 21.0,
'price_include': True,
'l10n_es_edi_facturae_tax_type': '01'
}
])
cls.nsmap = {
'ds': "http://www.w3.org/2000/09/xmldsig#", 'fac': "http://www.facturae.es/Facturae/2007/v3.1/Facturae",
'xades': "http://uri.etsi.org/01903/v1.3.2#", 'xd': "http://www.w3.org/2000/09/xmldsig#",
}
cls.maxDiff = None
@classmethod
def create_invoice(cls, **kwargs):
return cls.env['account.move'].with_context(edi_test_mode=True).create({
'partner_id': cls.partner_a.id,
'invoice_date': cls.frozen_today.isoformat(),
'date': cls.frozen_today.isoformat(),
**kwargs,
'invoice_line_ids': [
Command.create({'product_id': cls.product_a.id, 'price_unit': 1000.0, **line_vals, })
for line_vals in kwargs.get('invoice_line_ids', [])
],
})
def test_generate_signed_xml(self, date=None):
random.seed(42)
date = date or self.frozen_today
# We need to patch dates and uuid to ensure the signature's consistency
with freeze_time(date), \
patch(f"{self.certificate_module}.fields.datetime.now", lambda x=None: date), \
patch(f"{self.certificate_module}.sha1", lambda x: sha1()):
invoice = self.create_invoice(
partner_id=self.partner_a.id,
move_type='out_invoice',
invoice_line_ids=[
{'price_unit': 100.0, 'tax_ids': [self.tax.id]},
{'price_unit': 100.0, 'tax_ids': [self.tax.id]},
{'price_unit': 242.0, 'tax_ids': [self.tax_2.id]},
{'price_unit': 1000.0, "discount": 10, "tax_ids": [self.tax.id]},
{'price_unit': 1210.0, "discount": -10, "tax_ids": [self.tax_2.id]},
],
)
invoice.action_post()
generated_file = invoice._l10n_es_edi_facturae_render_facturae()
self.assertTrue(generated_file)
with file_open("l10n_es_edi_facturae/tests/data/expected_signed_document.xml", "rt") as f:
expected_xml = lxml.etree.fromstring(f.read().encode())
self.assertXmlTreeEqual(lxml.etree.fromstring(generated_file), expected_xml)
def test_cannot_generate_unsigned_xml(self):
""" Test that no valid certificate prevents a xml generation"""
self.certificate.unlink()
random.seed(42)
with freeze_time(self.frozen_today), \
patch(f"{self.certificate_module}.fields.datetime.now", lambda x=None: self.frozen_today), \
patch(f"{self.certificate_module}.sha1", lambda x: sha1()):
invoice = self.create_invoice(partner_id=self.partner_a.id, move_type='out_invoice', invoice_line_ids=[{'price_unit': 100.0, 'tax_ids': [self.tax.id]},],)
invoice.action_post()
self.assertRaises(UserError, invoice._l10n_es_edi_facturae_render_facturae)
def test_tax_withheld(self):
with freeze_time(self.frozen_today), \
patch(f"{self.certificate_module}.fields.datetime.now", lambda x=None: self.frozen_today), \
patch(f"{self.certificate_module}.sha1", lambda x: sha1()):
witholding_taxes = self.env["account.tax"].create([{
'name': "IVA 21%",
'company_id': self.company_data['company'].id,
'amount': 21.0,
'price_include': False,
'l10n_es_edi_facturae_tax_type': '01'
}, {
'name': "IVA 21% withholding",
'company_id': self.company_data['company'].id,
'amount': -21.0,
'price_include': False,
'l10n_es_edi_facturae_tax_type': '01'
}])
invoice = self.create_invoice(
partner_id=self.partner_a.id,
move_type='out_invoice',
invoice_line_ids=[
{'price_unit': 100.0, 'tax_ids': witholding_taxes.ids},
{'price_unit': 100.0, 'tax_ids': witholding_taxes.ids},
{'price_unit': 200.0, 'tax_ids': witholding_taxes.ids},
],
)
invoice.action_post()
generated_file = invoice._l10n_es_edi_facturae_render_facturae()
self.assertTrue(generated_file)
with file_open("l10n_es_edi_facturae/tests/data/expected_tax_withholding.xml", "rt") as f:
expected_xml = lxml.etree.fromstring(f.read().encode())
self.assertXmlTreeEqual(lxml.etree.fromstring(generated_file), expected_xml)
def test_in_invoice(self):
random.seed(42)
# We need to patch dates and uuid to ensure the signature's consistency
with freeze_time(self.frozen_today), \
patch(f"{self.certificate_module}.fields.datetime.now", lambda x=None: self.frozen_today), \
patch(f"{self.certificate_module}.sha1", lambda x: sha1()):
invoice = self.create_invoice(
partner_id=self.partner_a.id,
move_type='in_invoice',
invoice_line_ids=[
{'price_unit': 100.0, 'tax_ids': [self.tax.id]},
{'price_unit': 100.0, 'tax_ids': [self.tax.id]},
{'price_unit': 242.0, 'tax_ids': [self.tax_2.id]},
{'price_unit': 1000.0, "discount": 10, "tax_ids": [self.tax.id]},
{'price_unit': 1000.0, "discount": -10, "tax_ids": [self.tax.id]},
],
)
invoice.action_post()
generated_file = invoice._l10n_es_edi_facturae_render_facturae()
self.assertTrue(generated_file)
with file_open("l10n_es_edi_facturae/tests/data/expected_in_invoice_document.xml", "rt") as f:
expected_xml = lxml.etree.fromstring(f.read().encode())
self.assertXmlTreeEqual(lxml.etree.fromstring(generated_file), expected_xml)
def test_refund_invoice(self):
random.seed(42)
# We need to patch dates and uuid to ensure the signature's consistency
with freeze_time(self.frozen_today), \
patch(f"{self.certificate_module}.fields.datetime.now", lambda x=None: self.frozen_today), \
patch(f"{self.certificate_module}.fields.datetime.now", lambda x=None: self.frozen_today), \
patch(f"{self.certificate_module}.sha1", lambda x: sha1()):
invoice = self.create_invoice(
partner_id=self.partner_a.id,
move_type='out_invoice',
invoice_line_ids=[
{'price_unit': 100.0, 'tax_ids': [self.tax.id]},
{'price_unit': 100.0, 'tax_ids': [self.tax.id]},
],
)
invoice.action_post()
reversal_wizard = self.env['account.move.reversal'].create({
'move_ids': invoice.ids,
'date_mode': 'custom',
'journal_id': invoice.journal_id.id,
'date': self.frozen_today,
'refund_method': 'cancel',
'company_id': self.company_data['company'].id,
'l10n_es_edi_facturae_reason_code': '01'
})
reversal_wizard.reverse_moves()
refund = reversal_wizard.new_move_ids
generated_file = refund._l10n_es_edi_facturae_render_facturae()
self.assertTrue(generated_file)
with file_open("l10n_es_edi_facturae/tests/data/expected_refund_document.xml", "rt") as f:
expected_xml = lxml.etree.fromstring(f.read().encode())
self.assertXmlTreeEqual(lxml.etree.fromstring(generated_file), expected_xml)
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<menuitem id="menu_l10n_es_edi_facturae_root" name="Spain Facturae EDI" sequence="110" groups="base.group_system"
parent="account.menu_finance_configuration">
<menuitem id="menu_l10n_es_edi_facturae_root_certificates" name="Certificates" action="l10n_es_edi_facturae_certificate_action"
sequence="100" groups="base.group_system"/>
</menuitem>
</data>
</odoo>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_move_send_form" model="ir.ui.view">
<field name="name">account.move.send.form</field>
<field name="model">account.move.send</field>
<field name="inherit_id" ref="account.account_move_send_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='advanced_options']" position="inside">
<field name="l10n_es_edi_facturae_enable_xml" invisible="1"/>
<div name="option_xml"
attrs="{'invisible': [('l10n_es_edi_facturae_enable_xml', '=', False)]}">
<!-- Use one field as the label for another -->
<field name="l10n_es_edi_facturae_checkbox_xml" style="display: inline-block"/>
<b><label for="l10n_es_edi_facturae_checkbox_xml"></label></b>
</div>
</xpath>
</field>
</record>
</odoo>
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_tax_tree_inherit_l10n_es_edi_facturae" model="ir.ui.view">
<field name="name">account.tax.tree.inherit.l10n_es_edi_facturae</field>
<field name="inherit_id" ref="account.view_tax_tree"/>
<field name="model">account.tax</field>
<field name="arch" type="xml">
<field name="country_id" position="after">
<field name="country_code" invisible="1"/>
<field name="l10n_es_edi_facturae_tax_type" string="Spanish Tax Type" optional="hide"
attrs="{'invisible': [('country_code', '!=', 'ES')]}"/>
</field>
</field>
</record>
<record id="view_tax_form_inherit_l10n_es_edi_facturae" model="ir.ui.view">
<field name="name">account.tax.form.inherit.l10n_es_edi_facturae</field>
<field name="model">account.tax</field>
<field name="inherit_id" ref="account.view_tax_form"/>
<field name="arch" type="xml">
<field name="country_id" position="after">
<field name="l10n_es_edi_facturae_tax_type" attrs="{'invisible': [('country_code', '!=', 'ES')]}"/>
</field>
</field>
</record>
</data>
</odoo>
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="l10n_es_edi_facturae_certificate_form" model="ir.ui.view">
<field name="name">l10n_es_edi_facturae.certificate.form</field>
<field name="model">l10n_es_edi_facturae.certificate</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="content"/>
<field name="password" password="True"/>
<label for="date_start" string="Validity"/>
<div>
<field name="date_start"/> -
<field name="date_end"/>
</div>
<field name="company_id" groups="base.group_multi_company" readonly="True"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="l10n_es_edi_facturae_certificate_tree" model="ir.ui.view">
<field name="name">l10n_es_edi_facturae.certificate.tree</field>
<field name="model">l10n_es_edi_facturae.certificate</field>
<field name="arch" type="xml">
<tree>
<field name="date_start"/>
<field name="date_end"/>
<field name="company_id" groups="base.group_multi_company" readonly="True"/>
</tree>
</field>
</record>
<record id="l10n_es_edi_facturae_certificate_action" model="ir.actions.act_window">
<field name="name">Certificates for Facturae EDI invoices on Spain</field>
<field name="res_model">l10n_es_edi_facturae.certificate</field>
<field name="view_mode">tree,form</field>
<field name="context" eval="{'search_default_my_certificates': 1}"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">Create the first certificate</p>
</field>
</record>
</data>
</odoo>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_company_form_inherit_l10n_es_edi_facturae" model="ir.ui.view">
<field name="name">res.company.form.inherit.l10n_es_edi_facturae</field>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="model">res.company</field>
<field name="arch" type="xml">
<field name="currency_id" position="after">
<field name="l10n_es_edi_facturae_certificate_id" string="Facturae signature certificate"
attrs="{'invisible': [('country_code', '!=', 'ES')]}"/>
</field>
</field>
</record>
</odoo>
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="product_uom_tree_view_inherit_l10n_es_edi_facturae" model="ir.ui.view">
<field name="name">uom.uom.tree.inherit.l10n_es_edi_facturae</field>
<field name="model">uom.uom</field>
<field name="inherit_id" ref="uom.product_uom_tree_view"/>
<field name="arch" type="xml">
<field name="category_id" position="after">
<field name="l10n_es_edi_facturae_uom_code" string="Spanish Facturae EDI type"/>
</field>
</field>
</record>
<record id="product_uom_form_view_inherit_l10n_es_edi_facturae" model="ir.ui.view">
<field name="name">uom.uom.form.inherit.l10n_es_edi_facturae</field>
<field name="model">uom.uom</field>
<field name="inherit_id" ref="uom.product_uom_form_view"/>
<field name="arch" type="xml">
<field name="category_id" position="after">
<field name="l10n_es_edi_facturae_uom_code" string="Spanish Facturae EDI type"/>
</field>
</field>
</record>
<record id="product_uom_categ_form_view_inherit_l10n_es_edi_facturae" model="ir.ui.view">
<field name="name">uom.category.form.inherit.l10n_es_edi_facturae</field>
<field name="model">uom.category</field>
<field name="inherit_id" ref="uom.product_uom_categ_form_view"/>
<field name="arch" type="xml">
<tree position="inside">
<field name="l10n_es_edi_facturae_uom_code" string="Spanish Facturae EDI type"/>
</tree>
</field>
</record>
</odoo>
@@ -0,0 +1 @@
from . import account_move_reversal
@@ -0,0 +1,37 @@
from odoo import models, fields
class AccountMoveReversal(models.TransientModel):
_inherit = 'account.move.reversal'
l10n_es_edi_facturae_reason_code = fields.Selection(
selection=[
('01', 'Invoice number'),
('02', 'Invoice series'),
('03', 'Date of issue'),
('04', 'Name and surname/company name - Issuer'),
('05', 'Name and surname/company name - Recipient'),
('06', 'Tax identification Issuer/Oblige'),
('07', 'Tax identification Receiver'),
('08', 'Issuer/Oblige Address'),
('09', 'Receiving Address'),
('10', 'Transaction Details'),
('11', 'Tax rate to be applied'),
('12', 'Tax rate to be applied'),
('13', 'Date/Period to apply'),
('14', 'Invoice type'),
('15', 'Statutory letters'),
('16', 'Taxable amount'),
('80', 'Calculation of output quotas'),
('81', 'Calculation of withholding taxes'),
('82', 'Taxable amount modified by return of containers/packaging'),
('83', 'Taxable income modified by discounts and allowances'),
('84', 'Taxable income modified by final, judicial or administrative ruling'),
('85', 'Taxable income modified by unpaid tax assessments. Order of declaration of bankruptcy'),
], string='Spanish Facturae EDI Reason Code', default='10')
def reverse_moves(self):
# Extends account_account
res = super(AccountMoveReversal, self).reverse_moves()
self.new_move_ids.l10n_es_edi_facturae_reason_code = self.l10n_es_edi_facturae_reason_code
return res
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_tax_tree_inherit_l10n_es_edi_facturae" model="ir.ui.view">
<field name="name">account.tax.tree.inherit.l10n_es_edi_facturae</field>
<field name="inherit_id" ref="account.view_tax_tree"/>
<field name="model">account.tax</field>
<field name="arch" type="xml">
<field name="country_id" position="after">
<field name="country_code" invisible="1"/>
<field name="l10n_es_edi_facturae_tax_type" string="Spanish Tax Type" optional="hide"
attrs="{'invisible': [('country_code', '!=', 'ES')]}"/>
</field>
</field>
</record>
<record id="view_account_move_reversal_inherit_l10n_es_edi_facturae" model="ir.ui.view">
<field name="name">account.move.reversal.form.inherit.l10n_es_edi_facturae</field>
<field name="inherit_id" ref="account.view_account_move_reversal"/>
<field name="model">account.move.reversal</field>
<field name="arch" type="xml">
<field name="reason" position="replace">
<field name="country_code" invisible="1"/>
<field name="l10n_es_edi_facturae_reason_code" string="Reason" attrs="{'invisible':[('country_code', '!=', 'ES')]}"/>
<field name="reason" string="Reason" attrs="{'invisible': [('move_type', '=', 'entry'), ('country_code', '=', 'ES')]}"/>
</field>
</field>
</record>
</data>
</odoo>
+82
View File
@@ -0,0 +1,82 @@
import base64
import hashlib
from base64 import b64encode
from copy import deepcopy
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from lxml import etree
from odoo.exceptions import UserError
NS_MAP = {'ds': "http://www.w3.org/2000/09/xmldsig#"}
def _canonicalize_node(node):
"""
Returns the canonical (C14N 1.0, without comments, non exclusive) representation of node.
Speficied in: https://www.w3.org/TR/2001/REC-xml-c14n-20010315
Required for computing digests and signatures.
Returns an UTF-8 encoded bytes string.
"""
return etree.tostring(node, method="c14n", with_comments=False, exclusive=False)
def _get_uri(uri, reference, base_uri=""):
"""
Returns the content within `reference` that is identified by `uri`.
Canonicalization is used to convert node reference to an octet stream.
- URIs starting with # are same-document references
https://www.w3.org/TR/xmldsig-core/#sec-URI
- Empty URIs point to the whole document tree, without the signature
https://www.w3.org/TR/xmldsig-core/#sec-EnvelopedSignature
Returns an UTF-8 encoded bytes string.
"""
node = deepcopy(reference.getroottree())
if uri == base_uri:
# Base URI: whole document, without signature (default is empty URI)
for signature in node.xpath('/ds:Signature', namespaces=NS_MAP):
node = signature.getparent()
node.remove(signature)
return _canonicalize_node(node)
if uri.startswith("#"):
path = "//*[@*[local-name() = '{}' ]=$uri]"
results = node.xpath(path.format("Id"), uri=uri.lstrip("#")) # case-sensitive 'Id'
if len(results) == 1:
return _canonicalize_node(results[0])
if len(results) > 1:
raise UserError(f"Ambiguous reference URI {uri} resolved to {len(results)} nodes")
raise UserError(f'URI {uri} not found')
def _reference_digests(node, base_uri=""):
"""
Processes the references from node and computes their digest values as specified in
https://www.w3.org/TR/xmldsig-core/#sec-DigestMethod
https://www.w3.org/TR/xmldsig-core/#sec-DigestValue
"""
for reference in node.findall("ds:Reference", namespaces=NS_MAP):
ref_node = _get_uri(reference.get("URI", ""), reference, base_uri=base_uri)
lib = hashlib.new("sha256", ref_node)
reference.find("ds:DigestValue", namespaces=NS_MAP).text = b64encode(lib.digest())
def _fill_signature(node, private_key):
"""
Uses private_key to sign the SignedInfo sub-node of `node`, as specified in:
https://www.w3.org/TR/xmldsig-core/#sec-SignatureValue
https://www.w3.org/TR/xmldsig-core/#sec-SignedInfo
"""
signed_info_xml = node.find("ds:SignedInfo", namespaces=NS_MAP)
# During signature generation, the digest is computed over the canonical form of the document
signature = private_key.sign(_canonicalize_node(signed_info_xml), padding.PKCS1v15(), hashes.SHA256())
node.find("ds:SignatureValue", namespaces=NS_MAP).text = base64.encodebytes(signature)
def _int_to_bytes(number):
""" Converts an integer to a byte string (in smallest big-endian form). """
return number.to_bytes((number.bit_length() + 7) // 8, byteorder='big')