...
 
Commits (119)
<a name="3.4.3"></a>
### 3.4.3 (2023-01-17)
#### Bug Fixes
* **account.invoice:** corrije el proceso de cancelación ([dc2ca0a7](dc2ca0a7))
<a name="3.4.2"></a>
### 3.4.2 (2022-11-16)
#### Bug Fixes
* **account.voucher:** se cambia el total de la factura por el total de su póliza relacionada ([0616d412](0616d412))
<a name="3.4.1"></a>
### 3.4.1 (2022-10-27)
#### Bug Fixes
* **account.invoice:** se agrega for a la función _compute_export ([c8853260](c8853260))
<a name="3.4.0"></a>
## 3.4.0 (2022-10-21)
#### Features
* **account.invoic3:** se cambia el comportamiento sobre el campo exportación ([b1e36a49](b1e36a49))
<a name="3.3.8"></a>
### 3.3.8 (2022-10-17)
#### Bug Fixes
* **account.invoice:** se añadió la condición para no visualizar el botón retry ([402b4869](402b4869))
<a name="3.3.7"></a>
### 3.3.7 (2022-10-04)
#### Features
* **account.invoice:** se agrega funcionalidad para relacionar factura de un cliente con su contacto ([22e67231](22e67231))
<a name="3.3.6"></a>
### 3.3.6 (2022-10-03)
#### Bug Fixes
* **account.invoice:** considera el caso de que los impuestos estén incluidos en el precio ([8c8a6359](8c8a6359))
<a name="3.3.5"></a>
### 3.3.5 (2022-09-27)
#### Bug Fixes
* **account.invoice:** se corrige funcionalidad para poder cancelar las facturas ([b616a0ab](b616a0ab))
<a name="3.3.4"></a>
### 3.3.4 (2022-09-14)
#### Bug Fixes
* **account.invoice:** se cambia referencia del campo unidades en el xml de facturas ([6d17dbdd](6d17dbdd))
<a name="3.3.3"></a>
### 3.3.3 (2022-09-06)
#### Bug Fixes
* **account.voucher:** Se corrige la precisión decimal sobre la función currency_rate ([71427561](71427561))
<a name="3.3.2"></a>
### 3.3.2 (2022-09-02)
#### Bug Fixes
* **account.invoice.line:** altera el precio únicamente en caso de que el producto lleve IEPS ([a5de2df5](a5de2df5))
<a name="3.3.1"></a>
### 3.3.1 (2022-08-25)
#### Bug Fixes
* **account.invoice:** corrije el total con letra para que sea consistente con los datos del PDF ([bad9bf0a](bad9bf0a))
<a name="3.3.0"></a>
## 3.3.0 (2022-08-23)
#### Features
* **account.invoice:** se agrega el campo cfdi_periodicity ([fd2ab837](fd2ab837))
<a name="3.2.2"></a>
### 3.2.2 (2022-08-20)
#### Bug Fixes
* **account.voucher:**
* se mejora la plantilla de pagos agregando los impuestos y retenciones ([625a4016](625a4016))
* se corrigen escenarios para emitir pagos en pesos con factura en dólar ([0d93e4b9](0d93e4b9))
<a name="3.2.1"></a>
### 3.2.1 (2022-08-13)
#### Bug Fixes
* **account.invoice:** se corrige funcionalidad para poder cancelar facturas con CFDI no timbrados ([acf9db68](acf9db68))
* **account.voucher:** se detecto que marca un error al crear pago en dolares ([b45c7f0b](b45c7f0b))
<a name="3.2.0"></a>
## 3.2.0 (2022-07-21)
#### Features
* **account.invoice:** se modifica la función onchange_partner_id ([9b3ccaf4](9b3ccaf4))
<a name="3.1.4"></a>
### 3.1.4 (2022-07-04)
#### Bug Fixes
* **account.invoice:**
* guarda correctamente la información del uso del CFDI cuando se crea un factura ([2946eeb4](2946eeb4))
* mejora la condición para poner el atributo TotalImpuestosTrasladados ([978186dc](978186dc))
<a name="3.1.3"></a>
### 3.1.3 (2022-06-30)
#### Bug Fixes
* **account.invoice:**
* corrije el timbrado de una factura solo con impuestos excentos ([4aefc3cf](4aefc3cf))
* permite editar el uso del CFDI si no está timbrada la factura ([85e15396](85e15396))
* utiliza directamente el país para validar el código postal ([9bb8e0c9](9bb8e0c9))
<a name="3.1.2"></a>
### 3.1.2 (2022-06-29)
#### Bug Fixes
* **account.voucher:** se corrige redondeo del IVA ([094c8a72](094c8a72))
<a name="3.1.1"></a>
### 3.1.1 (2022-06-28)
#### Bug Fixes
* **account.voucher:** se agrega formato de 4 decimales para impuestos ([8ab9b94c](8ab9b94c))
<a name="3.1.0"></a>
## 3.1.0 (2022-06-23)
#### Bug Fixes
* **account.invoice:** mejora las validaciones de datos requeridos ([165ca42b](165ca42b))
#### Features
* **account.invoice:** se agrega cambio del campo cfdi_adenda_ids ([1f21e7f7](1f21e7f7))
<a name="3.0.3"></a>
### 3.0.3 (2022-06-15)
#### Bug Fixes
* **account.voucher:** se agrega redondeo para el campo TotalTrasladosBaseIVA ([22aca5e5](22aca5e5))
<a name="3.0.2"></a>
### 3.0.2 (2022-06-03)
#### Bug Fixes
* **account.voucher:** se elimina validación ya que la cuenta bancaria del partner no es requerida ([d6f1762c](d6f1762c))
#### Features
* **account.voucher:** se quitan validaciones para el UsoCFDI y se pone el valor por default ([db16767c](db16767c))
<a name="3.0.0"></a>
## 3.0.0 (2022-05-20)
#### Features
* **account.invoice:** se agrega soporte para CFDI 4.0 ([5c600213](5c600213))
* **account.voucher:**
* se módifica archivo account_voucher.odt ([90613844](90613844))
* se agrega cfdi 4.0 para pagos ([a8713a79](a8713a79))
<a name="2.19.1"></a>
## 2.19.1 (2022-03-18)
#### Bug Fixes
* **account.invoice:** corrección definitiva al cálculo de la fecha de emisión ([7ac5a50d](7ac5a50d))
<a name="2.19.0"></a>
## 2.19.0 (2022-02-21)
#### Features
* **account.invoice:** se agrega complemento de impuesto local ([4bac106a](4bac106a))
<a name="2.18.1"></a>
## 2.18.1 (2022-01-27)
#### Bug Fixes
* **account.invoice:** calcula el campo is_cfdi_candidate con base en el campo company_id ([7b5b3a01](7b5b3a01))
<a name="2.18.0"></a>
## 2.18.0 (2022-01-17)
#### Bug Fixes
* **account.voucher:** corrije el comportamiento de la substitución de recibos de pagos ([a8ce8e19](a8ce8e19))
#### Features
* **account.invoice:** adapta el funcionamiento al nuevo esquema de cancelación ([57fc8876](57fc8876))
<a name="2.17.3"></a>
## 2.17.3 (2021-12-16)
#### Bug Fixes
* **account.invoice:** elimina campos que ya están depreciados ([6e06332f](6e06332f))
<a name="2.17.2"></a>
## 2.17.2 (2021-12-10)
#### Bug Fixes
* **account.invoice:** resta 1 minuto a la hora actual para la creación del XML de la factura ([6bde1f97](6bde1f97))
* **account.voucher:** elimina la llamada a internal_number ([4b81d884](4b81d884))
<a name="2.17.1"></a>
## 2.17.1 (2021-12-09)
#### Bug Fixes
* **account.invoice:** elimina la llamada a internal_number ([8649981f](8649981f))
<a name="2.17.0"></a>
## 2.17.0 (2021-11-29)
#### Code refactor
* **account.invoice:** mueve el formulario de factura de proveedor al módulo de validación ([0b210f33](0b210f33))
#### Feature
* **account.invoice:**
* actualiza para funcionar con las modificaciones a la clase `base.cfdi.mixin` ([920504b4](920504b4))
* oculta los campos relacionados con el CFDI cuando la factura no es candidata ([3f8f8f5b](3f8f8f5b))
* **manifiesto:** dependencia del módulo account_cancel ([b8f6292e](b8f6292e))
* **wizard.multi.charts.accounts:** marcar automáticamente casillas de Firmar y Todas las entradas canceladas ([3552ab73](3552ab73))
#### Bug Fixes
* **account.invoice:**
* reemplaza el uso del campo `number` ([04dfd082](04dfd082))
* solución de error en vista from ([f9554638](f9554638))
<a name="2.16.3"></a>
## 2.16.3 (2020-08-01)
......
......@@ -2,5 +2,5 @@
Mexican electronic invoice
==========================
This module creates e-invoice files from invoices with standard CFDI 3.2
This module creates e-invoice files from invoices with standard CFDI 4.0
according to specifications published by SAT.
......@@ -2,4 +2,3 @@
from . import models
from . import wizard
from . import report
......@@ -2,17 +2,16 @@
{
"name": "Factura Electronica CFDI",
"version": "2.17.0",
"version": "3.5.0",
"author": "OpenPyme",
"category": "Localization/Mexico",
"website": "http://www.openpyme.mx/",
"license": "AGPL-3",
"depends": [
"account",
# TODO: Deprecate this module and include into account base
"account_cancel",
"account_invoice_discount",
"base_vat",
"l10n_mx",
"base_iso3166",
"l10n_mx_account_tax_category",
"l10n_mx_ir_attachment_facturae",
"l10n_mx_res_partner_bank",
......@@ -24,8 +23,8 @@
"views/account_voucher.xml",
"views/res_partner.xml",
"wizard/account_invoice_refund.xml",
"report/account_invoice.xml",
"report/account_voucher.xml",
"data/account_invoice.xml",
"data/account_voucher.xml",
"data/ir_cron.xml",
"data/email_template.xml",
"data/ir_attachment_facturae_config.xml",
......
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<!-- XML invoice report definition -->
<data>
<record id="l10n_mx_facturae_template_xml" model="report.templates.aeroo">
<field name="name">Defaut XML Invoice Report teplate</field>
<field name="model">account.invoice</field>
<field name="report_name">invoice.report.aaero.xml</field>
<field name="report_rml">l10n_mx_facturae/templates/account_invoice_32.txt</field>
</record>
<record id="l10n_mx_facturae_report_aeroo_xml" model="ir.actions.report.xml">
<field name="name">Invoice XML Report aero</field>
<field name="type">ir.actions.report.xml</field>
<field name="model">account.invoice</field>
<field name="report_name">invoice.report.aaero.xml</field>
<field name="report_type">aeroo</field>
<field name="in_format">genshi-raw</field>
<field name="out_format" ref="report_aeroo.report_mimetypes_raw" />
<field name="aeroo_templates_ids"
eval="[(6, 0, [l10n_mx_facturae_template_xml])]" />
<field name="parser_state">default</field>
</record>
<record id="report_templates_aeroo_account_invoice_cfdi_33" model="report.templates.aeroo">
<field name="name">Account Invoice XML CFDI 3.3</field>
<!-- XML invoice report definition -->
<record id="report_templates_aeroo_account_invoice_cfdi"
model="report.templates.aeroo">
<field name="name">Account Invoice CFDI XML</field>
<field name="model">account.invoice</field>
<field name="report_name">account.invoice.cfdi.33</field>
<field name="report_rml">l10n_mx_facturae/templates/account_invoice_33.txt</field>
<field name="report_name">account.invoice.cfdi.xml</field>
<field
name="report_rml">l10n_mx_facturae/templates/account_invoice.txt</field>
</record>
<record id="ir_actions_report_xml_account_invioice_cfdi_33" model="ir.actions.report.xml">
<field name="name">Account Invoice XML CFDI 3.3</field>
<record id="ir_actions_report_xml_account_invioice_cfdi"
model="ir.actions.report.xml">
<field name="name">Account Invoice CFDI XML</field>
<field name="type">ir.actions.report.xml</field>
<field name="model">account.invoice</field>
<field name="report_name">account.invoice.cfdi.xml.33</field>
<field name="report_name">account.invoice.cfdi.xml</field>
<field name="report_type">aeroo</field>
<field name="in_format">genshi-raw</field>
<field name="out_format" ref="report_aeroo.report_mimetypes_raw" />
<field name="aeroo_templates_ids"
eval="[(6, 0, [report_templates_aeroo_account_invoice_cfdi_33])]" />
eval="[(6, 0, [report_templates_aeroo_account_invoice_cfdi])]" />
<field name="parser_state">default</field>
</record>
</data>
<!-- PDF invoice report definition -->
<data noupdate="1">
<record id="l10n_mx_facturae_template" model="report.templates.aeroo">
<field name="name">Default Invoice Report template</field>
<!-- PDF invoice report definition -->
<record id="report_templates_aeroo_pdf_account_invoice_cfdi"
model="report.templates.aeroo">
<field name="name">Default Invoice Report PDF</field>
<field name="model">account.invoice</field>
<field name="report_name">invoice.report.aaero</field>
<field name="report_rml">l10n_mx_facturae/report/account_invoice_33.odt</field>
<field name="report_name">invoice.report.aaero.pdf</field>
<field name="report_rml">l10n_mx_facturae/report/account_invoice.odt</field>
</record>
<record id="l10n_mx_facturae_report_aeroo" model="ir.actions.report.xml">
<field name="name">Invoice CFDI</field>
<record id="ir_actions_report_pdf_account_invioice_cfdi"
model="ir.actions.report.xml">
<field name="name">Account Invoice CFDI PDF</field>
<field name="type">ir.actions.report.xml</field>
<field name="model">account.invoice</field>
<field name="report_name">invoice.report.aaero</field>
<field name="report_name">invoice.report.aaero.pdf</field>
<field name="report_type">aeroo</field>
<field name="in_format">oo-odt</field>
<field name="out_format" ref="report_aeroo.report_mimetypes_pdf_odt" />
<field name="aeroo_templates_ids" eval="[(6, 0, [l10n_mx_facturae_template])]" />
<field name="parser_loc">l10n_mx_facturae/report/account_invoice.py</field>
<field name="aeroo_templates_ids"
eval="[(6, 0, [report_templates_aeroo_pdf_account_invoice_cfdi])]" />
<field name="parser_loc">l10n_mx_ir_attachment_facturae/report/generate_qr.py</field>
<field name="parser_state">loc</field>
</record>
</data>
</openerp>
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- XML invoice report definition -->
<record id="report_templates_aeroo_account_voucher_cfdi_33" model="report.templates.aeroo">
<field name="name">Account Invoice XML CFDI 3.3</field>
<!-- XML voucher report definition -->
<record id="report_templates_aeroo_account_voucher_cfdi"
model="report.templates.aeroo">
<field name="name">Account Voucher CFDI XML</field>
<field name="model">account.voucher</field>
<field name="report_rml">l10n_mx_facturae/templates/account_voucher_33.txt</field>
<field name="report_name">account.voucher.cfdi.xml</field>
<field
name="report_rml">l10n_mx_facturae/templates/account_voucher.txt</field>
</record>
<record id="ir_actions_report_xml_account_voucher_cfdi_33" model="ir.actions.report.xml">
<field name="name">Account Voucher XML CFDI 3.3</field>
<record id="ir_actions_report_xml_account_voucher_cfdi"
model="ir.actions.report.xml">
<field name="name">Account Voucher CFDI XML</field>
<field name="type">ir.actions.report.xml</field>
<field name="model">account.voucher</field>
<field name="report_name">account.voucher.cfdi.xml.33</field>
<field name="report_name">account.voucher.cfdi.xml</field>
<field name="report_type">aeroo</field>
<field name="in_format">genshi-raw</field>
<field name="out_format" ref="report_aeroo.report_mimetypes_raw" />
<field name="aeroo_templates_ids"
eval="[(6, 0, [report_templates_aeroo_account_voucher_cfdi_33])]" />
eval="[(6, 0, [report_templates_aeroo_account_voucher_cfdi])]" />
<field name="parser_state">default</field>
</record>
<!-- PDF invoice report definition -->
<record id="report_templates_aeroo_account_voucher_pdf_cfdi_33" model="report.templates.aeroo">
<field name="name">Default Voucher PDF</field>
</data>
<data noupdate="1">
<!-- PDF voucher report definition -->
<record id="report_templates_aeroo_account_voucher_pdf_cfdi"
model="report.templates.aeroo">
<field name="name">Default Voucher CFDI PDF</field>
<field name="model">account.voucher</field>
<field name="report_name">account.voucher.cfdi.pdf</field>
<field name="report_rml">l10n_mx_facturae/report/account_voucher.odt</field>
</record>
<record id="ir_actions_report_pdf_account_voucher_cfdi_33" model="ir.actions.report.xml">
<record id="ir_actions_report_pdf_account_voucher_cfdi"
model="ir.actions.report.xml">
<field name="name">Account Voucher CFDI PDF</field>
<field name="type">ir.actions.report.xml</field>
<field name="model">account.voucher</field>
......@@ -34,9 +48,11 @@
<field name="report_type">aeroo</field>
<field name="in_format">oo-odt</field>
<field name="out_format" ref="report_aeroo.report_mimetypes_pdf_odt" />
<field name="aeroo_templates_ids" eval="[(6, 0, [report_templates_aeroo_account_voucher_pdf_cfdi_33])]" />
<field name="parser_loc">l10n_mx_facturae/report/account_voucher.py</field>
<field name="aeroo_templates_ids"
eval="[(6, 0, [report_templates_aeroo_account_voucher_pdf_cfdi])]" />
<field name="parser_loc">l10n_mx_ir_attachment_facturae/report/generate_qr.py</field>
<field name="parser_state">loc</field>
</record>
</record>
</data>
</openerp>
......@@ -2,29 +2,22 @@
<openerp>
<data noupdate="1">
<record id="ir_attachment_facturae_mx_config_account_invoice_32" model="ir.attachment.facturae.mx.config">
<record id="ir_attachment_facturae_mx_config_account_invoice"
model="ir.attachment.facturae.mx.config">
<field name="model">account.invoice</field>
<field name="active" eval="False" />
<field name="version">3.2</field>
<field name="template_xml_sign">invoice.report.aaero.xml</field>
<field name="version">4.0</field>
<field name="template_xml_sign">account.invoice.cfdi.xml</field>
<field name="template_xml_cancel">Aun.no.hay.uno</field>
<field name="template_pdf_sign">invoice.report.aaero</field>
<field name="template_pdf_cancel">invoice.report.aaero</field>
<field name="template_pdf_sign">invoice.report.aaero.pdf</field>
<field name="template_pdf_cancel">invoice.report.aaero.pdf</field>
<field name="email_template_id" ref="account.email_template_edi_invoice"/>
</record>
<record id="ir_attachment_facturae_mx_config_account_invoice_33" model="ir.attachment.facturae.mx.config">
<field name="model">account.invoice</field>
<field name="version">3.3</field>
<field name="template_xml_sign">account.invoice.cfdi.xml.33</field>
<field name="template_xml_cancel">Aun.no.hay.uno</field>
<field name="template_pdf_sign">invoice.report.aaero</field>
<field name="template_pdf_cancel">invoice.report.aaero</field>
<field name="email_template_id" ref="account.email_template_edi_invoice"/>
</record>
<record id="ir_attachment_facturae_mx_config_account_voucher_33" model="ir.attachment.facturae.mx.config">
<record id="ir_attachment_facturae_mx_config_account_voucher"
model="ir.attachment.facturae.mx.config">
<field name="model">account.voucher</field>
<field name="version">3.3</field>
<field name="template_xml_sign">account.voucher.cfdi.xml.33</field>
<field name="version">4.0</field>
<field name="template_xml_sign">account.voucher.cfdi.xml</field>
<field name="template_xml_cancel">Aun.no.hay.uno</field>
<field name="template_pdf_sign">account.voucher.cfdi.pdf</field>
<field name="template_pdf_cancel">account.voucher.cfdi.pdf</field>
......
# -*- coding: utf-8 -*-
from openupgradelib import openupgrade as tools
from openerp import SUPERUSER_ID
@tools.migrate(use_env=True, uid=SUPERUSER_ID)
def migrate(env, installed_version):
# DELETE RECORDS DIFFERENT FROM VERSION 4.0 CFDI
tools.logged_query(
env.cr,
"""
DELETE FROM ir_attachment_facturae_mx_config
WHERE version != '4.0'
""",
)
# Disable records different from version 4.0 CFDI
tools.logged_query(
env.cr,
"""
UPDATE ir_act_report_xml
SET active='f'
WHERE report_name in (
'account.invoice.cfdi.xml.33',
'account.voucher.cfdi.pdf',
'account.voucher.cfdi.xml.33',
'invoice.report.aaero'
)
""",
)
# -*- coding: utf-8 -*-"""
from openupgradelib import openupgrade as tools
from openerp import SUPERUSER_ID
def migrate_res_partner(env):
# Migrate information from cfdi_adenda field to the new cfdi_adenda_ids
legacy_name = tools.get_legacy_name("cfdi_adenda")
if tools.column_exists(env.cr, "res_partner", legacy_name):
tools.m2o_to_x2m(
env.cr,
env["res.partner"],
"res_partner",
"cfdi_adenda_ids",
legacy_name,
)
def migrate_account_invoice(env):
# Migrate information from cfdi_adenda field to the new cfdi_adenda_ids
legacy_name = tools.get_legacy_name("cfdi_adenda_id")
if tools.column_exists(env.cr, "account_invoice", legacy_name):
tools.m2o_to_x2m(
env.cr,
env["account.invoice"],
"account_invoice",
"cfdi_adenda_ids",
legacy_name,
)
@tools.migrate(use_env=True, uid=SUPERUSER_ID)
def migrate(env, installed_version):
migrate_res_partner(env)
migrate_account_invoice(env)
# Clear helper fields used for migrate data
tools.drop_columns(
env.cr,
[
("res_partner", tools.get_legacy_name("cfdi_adenda")),
("account_invoice", tools.get_legacy_name("cfdi_adenda_id")),
],
)
# -*- coding: utf-8 -*-
from openupgradelib import openupgrade as tools
from openerp import SUPERUSER_ID
column_renames = {
"res_partner": [("cfdi_adenda", None)],
"account_invoice": [("cfdi_adenda_id", None)],
}
@tools.migrate(use_env=True, uid=SUPERUSER_ID)
def migrate(env, installed_version):
if column_renames:
tools.rename_columns(env.cr, column_renames)
This diff is collapsed.
......@@ -26,6 +26,11 @@ class AccountVoucher(models.Model):
else:
voucher.show_unreconcile = True
@api.multi
def _compute_sequence_id(self):
for record in self:
record.sequence_id = record.journal_id.sequence_id
@api.multi
def sign_voucher(self):
"""Create CFDI for selected vouchers"""
......@@ -47,12 +52,12 @@ class AccountVoucher(models.Model):
@api.multi
def substitute_voucher(self):
"""Create new voucher for substitute this one"""
self.cancel_cfdi()
self._cancel_cfdi()
# After cancel voucher we must re open it
self.action_cancel_draft()
@api.multi
def _cancel_cfdi_related_document(self):
self.ensure_one()
self.cancel_voucher()
@api.multi
......@@ -63,21 +68,28 @@ class AccountVoucher(models.Model):
@api.multi
def currency_rate(self):
"""Compute currency rate used for current voucher to display on XML
This calls curreny.compute() with the right context,
This calls currency.compute() with the right context,
so that it will take either the rate on the voucher if it is relevant
or will use the default behavior"""
self.ensure_one()
# We need to give the date in the context to get proper rate
voucher_currency = self.currency_id.with_context(date=self.date)
voucher_currency = self.journal_id.currency or self.company_id.currency_id
voucher_currency = voucher_currency.with_context(date=self.date)
voucher_currency = voucher_currency.with_context(
voucher_special_currency_rate=(voucher_currency.rate * self.payment_rate),
voucher_special_currency=(
special_currency_rate=(
voucher_currency.rate * self.payment_rate
),
special_currency=(
self.payment_rate_currency_id
and self.payment_rate_currency_id.id
or False
),
)
return voucher_currency.compute(1.0, self.company_id.currency_id)
res = float_round(
voucher_currency.compute(1.0, self.company_id.currency_id, round=False),
precision_digits=6,
)
return res
@property
def doctos_relacionados(self):
......@@ -120,9 +132,22 @@ class AccountVoucher(models.Model):
def impsaldoant(self, invoice):
"""Computes amount_residual for invoice before current voucher"""
self.ensure_one()
# Original residual amount for given invoice
amount_residual = invoice.amount_total
# It is verified that the invoice is in another currency
is_multi_currency = (
invoice.currency_id != self.company_id.currency_id
)
# Search the lines of the policy where the account is receivable or payable
move_line_ids = invoice.move_id.line_id.filtered(
lambda x: x.account_id.user_type.type in ("receivable", "payable")
)
if is_multi_currency:
amount_residual = sum(
move_line.amount_currency for move_line in move_line_ids
)
else:
amount_residual = sum(
move_line.balance for move_line in move_line_ids
)
# Get all payments done to given invoice
payments = invoice.mapped("payment_ids") - self.move_ids
payment_date = self.move_ids[0].create_date
......@@ -161,7 +186,11 @@ class AccountVoucher(models.Model):
# amount_paid instead of normal debit - credit
# TODO: Relay on currency_id could result in wrong results when
# transactions done in three different currencies or more
if payment.currency_id:
is_multi_currency = (
payment.currency_id
and invoice.currency_id != self.company_id.currency_id
)
if is_multi_currency:
amount_paid += payment.amount_currency * -1
else:
amount_paid += (payment.debit - payment.credit) * -1
......@@ -170,6 +199,10 @@ class AccountVoucher(models.Model):
if self.currency_id == invoice.currency_id and self.writeoff_amount < 0.0:
amount_paid += self.writeoff_amount / len(self.doctos_relacionados)
amount_paid = self.currency_id.cfdi_round(amount_paid)
# Ensure amount paid is always equal or less than amount residual
amount_residual = self.impsaldoant(invoice)
amount_paid = min(amount_paid, amount_residual)
return amount_paid
@api.multi
......@@ -179,15 +212,203 @@ class AccountVoucher(models.Model):
so that it will take either the rate on the voucher if it is relevant
or will use the default behavior"""
self.ensure_one()
company_currency = self.company_id.currency_id
voucher_currency = self.currency_id.with_context(date=self.date)
voucher_currency = voucher_currency.with_context(
special_currency_rate=(voucher_currency.rate * self.payment_rate),
special_currency=(
self.payment_rate_currency_id
and self.payment_rate_currency_id.id
or False
),
if invoice.currency_id == company_currency:
# When invoice is in company currency we are going to use payment rate
voucher_currency = voucher_currency.with_context(
special_currency_rate=(1 / self.payment_rate),
special_currency=voucher_currency.id,
)
else:
voucher_currency = voucher_currency.with_context(
special_currency_rate=(1 / invoice.currency_rate),
special_currency=invoice.currency_id.id,
)
value = float_round(
voucher_currency.compute(1.0, invoice.currency_id, round=False),
precision_digits=6
)
value = voucher_currency.compute(1.0, invoice.currency_id, round=False)
# For now we force the precision_digits
return float_round(value, precision_digits=6)
if (
voucher_currency == company_currency
and invoice.currency_id != company_currency
):
# Hack to bypass PAC validation, suggested by PAC
value += 0.000001
return value
@api.multi
def impuestos_dr(self, invoice, tax_type="traslados"):
"""Computes move lines for on payment taxes related to given invoice"""
self.ensure_one()
# Get payments done in this voucher for given invoice
payments = invoice.mapped("payment_ids") & self.move_ids
tax_reconciles = payments.mapped("tax_reconcile_id")
domain = self._l10n_mx_get_taxes_domain(tax_reconciles, tax_type=tax_type)
tax_moves = self.env["account.move.line"].search(domain)
return tax_moves
@api.multi
def totales_p(self, tax_type="traslados"):
"""Computes move lines for on payment taxes related to given invoice"""
self.ensure_one()
tax_totals = []
company_currency = self.company_id.currency_id
voucher_currency = self.currency_id.with_context(date=self.date)
impuestos = self.impuestos_p(tax_type=tax_type)
# Load tax record for being able to use on template to fill XML
for line in impuestos:
if company_currency != voucher_currency:
company_currency = company_currency.with_context(
special_currency_rate=self.currency_rate(),
special_currency=voucher_currency.id,
)
line["importe"] = company_currency.compute(
line["importe"], voucher_currency,
)
line["tax2_base"] = company_currency.compute(
line["tax2_base"], voucher_currency,
)
else:
line["importe"] = float_round(
line["importe"], precision_digits=2,
)
line["tax2_base"] = float_round(
line["tax2_base"], precision_digits=2,
)
tax_totals.append(line)
return tax_totals
@api.multi
def impuestos_p(self, tax_type="traslados"):
"""Computes move lines for on payment taxes related to given invoice"""
self.ensure_one()
company_currency = self.company_id.currency_id
voucher_currency = self.currency_id.with_context(date=self.date)
tax_totals = dict()
for invoice in self.doctos_relacionados:
for tax_move in self.impuestos_dr(invoice, tax_type=tax_type):
if tax_move.tax2_id.id not in tax_totals:
tax_totals[tax_move.tax2_id.id] = dict(
tax2_id=tax_move.tax2_id,
tax2_base=0.0,
importe=0.0,
)
if tax_move.currency_id != voucher_currency:
invoice_currency = (
tax_move.currency_id
if tax_move.currency_id else company_currency
)
invoice_currency = invoice_currency.with_context(date=self.date)
if invoice_currency != company_currency:
invoice_currency = invoice_currency.with_context(
special_currency_rate=self.tipocambiodr(invoice),
special_currency=invoice_currency.id,
)
else:
invoice_currency = invoice_currency.with_context(
special_currency_rate=(1 / self.tipocambiodr(invoice)),
special_currency=voucher_currency.id,
)
tax2_base = invoice_currency.compute(
tax_move.tax2_base, voucher_currency, round=False,
)
# Force rounding 6 decimals to use as many decimal as possible and
# avoid rounding errors when validating XML
tax2_base = float_round(
tax2_base, precision_digits=6, rounding_method="DOWN",
)
else:
tax2_base = tax_move.tax2_base
tax_totals[tax_move.tax2_id.id]["tax2_base"] += tax2_base
tax_totals[tax_move.tax2_id.id]["importe"] += float_round(
tax2_base * tax_move.tax2_id.amount,
precision_digits=6,
rounding_method="DOWN",
)
return [line for line in tax_totals.values()]
@api.multi
def _l10n_mx_get_taxes_domain(self, tax_reconciles, tax_type="traslados"):
self.ensure_one()
# Get tax moves done for this payment and this invoice
domain = [("tax_reconcile_id", "in", tax_reconciles._ids)]
# Exclude IEPS if partner not IEPS subjected
if not self.partner_id.ieps_subjected:
domain.append(
("tax2_id.tax_category_id.code", "!=", "IEPS")
)
if tax_type == "traslados":
domain.append("|")
domain.append(("tax2_id.type", "=", "none"))
domain.append("&")
domain.append(("tax2_id.amount", ">=", 0))
domain.append(("tax2_id.type", "=", "percent"))
else:
domain.append(("tax2_id.amount", "<", 0))
domain.append(("tax2_id.type", "=", "percent"))
return domain
@api.multi
def _validate_cfdi_data(self):
self._validate_account_voucher_vat()
self._validate_account_voucher_payment_type()
@api.multi
def _validate_account_voucher_vat(self):
""" This is function is to validate that the
partner has a vat, name and property account position """
required = [
"vat",
"name",
"property_account_position"
]
for record in self:
# Validate required fields on document
self._cfdi_validate_required_fields(
record,
[
"partner_id",
"amount",
"journal_id",
"payment_type_id",
"company_id"
],
)
self._cfdi_validate_required_fields(
record.company_id.partner_id,
required,
)
self._cfdi_validate_required_fields(
record.partner_id,
required,
)
@api.multi
def _validate_account_voucher_payment_type(self):
for record in self:
# It is validated that there is a payment method or that the
# selected payment method has code 99
if not record.payment_type_id or record.payment_type_id.code == "99":
raise ValidationError(
_("The payment method is missing or the payment method has code 99")
)
......@@ -40,7 +40,7 @@ class EmailTemplate(models.Model):
}
# Look for possible templates for invoice and override
for module, name in data.iteritems():
for module, name in data.items():
try:
reference_ids.append(
ir_model_data.get_object_reference(cr, uid, module, name)[1]
......
......@@ -12,14 +12,11 @@ class ResPartner(models.Model):
help="Cfdi usage that will be used by default on this customer "
"invoices and credit notes",
)
cfdi_adenda = fields.Many2one(
cfdi_adenda_ids = fields.Many2many(
"cfdi.adenda",
"CFDI Adendas",
string="CFDI Adendas",
help="This field allows adding a node or addendum to the invoice",
)
cfdi_adenda_code = fields.Char(
related="cfdi_adenda.code", help="Helper field to improve view management"
)
payment_method_id = fields.Many2one(
"cfdi.payment.method",
string="Payment Method",
......@@ -36,9 +33,15 @@ class ResPartner(models.Model):
show_edi = fields.Boolean(compute="_compute_show_number")
@api.multi
@api.depends("cfdi_adenda")
@api.depends("cfdi_adenda_ids")
def _compute_show_number(self):
for record in self:
record.show_glnnumber = record.cfdi_adenda.require_glnnumber
record.show_suppliernumber = record.cfdi_adenda.require_suppliernumber
record.show_edi = record.cfdi_adenda.require_edi
record.show_glnnumber = any(
[a.require_glnnumber for a in record.cfdi_adenda_ids]
)
record.show_suppliernumber = any(
[a.require_suppliernumber for a in record.cfdi_adenda_ids]
)
record.show_edi = any(
[a.require_edi for a in record.cfdi_adenda_ids]
)
# -*- coding: utf-8 -*-
from . import account_invoice
File added
# -*- coding: utf-8 -*-
from openerp.report import report_sxw
class Parser(report_sxw.rml_parse):
def __init__(self, cr, uid, name, context):
super(Parser, self).__init__(cr, uid, name, context)
self.localcontext.update({"qrcode_string": self._qrcode_string})
def _qrcode_string(self, invoice):
if invoice.cfdi_id.sello:
sello = invoice.cfdi_id.sello[-8: len(invoice.cfdi_id.sello)]
else:
sello = ""
return "".join(
[
"https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx"
"?id=",
invoice.cfdi_id.uuid and invoice.cfdi_id.uuid or "N/A",
"&re=",
invoice.company_id.partner_id.vat_split,
"&rr=",
invoice.partner_id.vat_split,
"&tt=",
str(invoice.total),
"&fe=",
sello,
]
)
This diff is collapsed.
File deleted
No preview for this file type
# -*- coding: utf-8 -*-
from openerp.report import report_sxw
class Parser(report_sxw.rml_parse):
"""Parser para generar etiquetas con codigo qr"""
def __init__(self, cr, uid, name, context):
# pylint: disable=E1003
super(self.__class__, self).__init__(cr, uid, name, context)
self.localcontext.update({"datosqr": self._datosqr})
def _datosqr(self, o):
"""Genera el string con info para la elaboración del codigo qr"""
def xstr(s):
if s:
return str(s)
return str("N/A")
msg = "https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx"
if o.cfdi_id.sello:
msg = msg + "?id=" + xstr(o.cfdi_id.uuid)
msg = msg + "&re=" + xstr(o.company_id.partner_id.vat_split)
msg = msg + "&rr=" + xstr(o.partner_id.vat_split)
msg = msg + "&tt=" + xstr(str(o.amount))
msg = msg + "&fe=" + xstr(o.cfdi_id.sello[-8: len(o.cfdi_id.sello)])
return msg
<?xml version="1.0" encoding="UTF-8"?>
{% python setLang(lang or 'en_US') %}
{% with
taxes=o.impuestos
show_total_traslados=any(tax.type != "none" for tax in taxes["traslados"])
is_local_taxes=bool(taxes["total_locales"])
%}
<cfdi:Comprobante
xsi:schemaLocation="http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv33.xsd"
xmlns:cfdi="http://www.sat.gob.mx/cfd/3"
xsi:schemaLocation="http://www.sat.gob.mx/cfd/4
http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd
{% if is_local_taxes %}
http://www.sat.gob.mx/implocal
http://www.sat.gob.mx/sitio_internet/cfd/implocal/implocal.xsd
{% end %} "
{% if is_local_taxes %}
xmlns:implocal="http://www.sat.gob.mx/implocal"
{% end %}
xmlns:cfdi="http://www.sat.gob.mx/cfd/4"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
Version="3.3"
Version="4.0"
{% if o.journal_id.sequence_id.prefix %}
Serie="$o.serie"
{% end %}
{% if not o.l10n_mx_export%}
Exportacion="01"
{% end %}
{% if o.l10n_mx_export %}
Exportacion="02"
{% end %}
Folio="$o.folio"
Fecha="${ format_datetime(o.cfdi_datetime, '%Y-%m-%dT%H:%M:%S', tz=user.partner_id.tz) }"
Sello="@"
......@@ -30,13 +49,9 @@
{% when False %}MetodoPago="PUE"{% end %}
{% otherwise %}MetodoPago="$o.payment_method_id.code"{% end %}
{% end %}
{% choose o.address_issued_id.zip %}
{% when False %}${ validationerror(_('Missing ZIP on issued address.')) }{% end %}
{% when '' %}${ validationerror(_('Missing ZIP on issued address.')) }{% end %}
{% otherwise %}LugarExpedicion="$o.address_issued_id.zip"{% end %}
{% end %}
LugarExpedicion="${ o.address_issued_id.zip }"
>
{% if o.origin_invoice_ids.exists() %}
{% if o.origin_invoice_ids %}
<cfdi:CfdiRelacionados TipoRelacion="${ o.cfdi_relation_type.code }">
{% for cfdi in o.origin_invoice_ids %}
<cfdi:CfdiRelacionado UUID="${ cfdi.cfdi_folio_fiscal }"/>
......@@ -50,38 +65,46 @@
{% end %}
</cfdi:CfdiRelacionados>
{% end %}
{% if o.commercial_partner_id.name.upper() == "PUBLICO EN GENERAL" and o.commercial_partner_id.vat_split == "XAXX010101000" %}
<cfdi:InformacionGlobal
Año="${ format_datetime(o.cfdi_datetime, '%Y', tz=user.partner_id.tz) }"
Meses="${ format_datetime(o.cfdi_datetime, '%m', tz=user.partner_id.tz) }"
Periodicidad="${ o.cfdi_periodicity }" />
{% end %}
<cfdi:Emisor
{% choose o.company_id.partner_id.vat_split %}
{% when '' %}${ validationerror(_('Missing VAT number for company')) }{% end %}
{% otherwise %}Rfc="${ html_escape(o.company_id.partner_id.vat_split) }"{% end %}
{% end %}
Nombre="${ html_escape(o.company_id.partner_id.name) }"
Rfc="${ html_escape(o.company_id.partner_id.vat_split) }"
Nombre="${ html_escape(o.company_id.partner_id.name.upper()) }"
RegimenFiscal="${ o.company_id.partner_id.property_account_position.code }"/>
<cfdi:Receptor
Nombre="${ html_escape(o.commercial_partner_id.name) }"
Nombre="${ html_escape(o.commercial_partner_id.name.upper()) }"
{% choose o.commercial_partner_id.country_id and o.commercial_partner_id.country_id.code_alpha3 %}
{% when False %}
${ validationerror(_('Missing country for partner')) }
{% end %}
{% when 'MEX' %}
{% choose o.commercial_partner_id.vat_split %}
{% when '' %}${ validationerror(_('Missing VAT number for receiver')) }{% end %}
{% otherwise %}Rfc="${ html_escape(o.commercial_partner_id.vat_split) }"{% end %}
Rfc="${ html_escape(o.commercial_partner_id.vat_split) }"
UsoCFDI="$o.cfdi_use.code"
{% choose html_escape(o.commercial_partner_id.vat_split) == "XAXX010101000" %}
{% when True %}DomicilioFiscalReceptor="${ o.address_issued_id.zip }"{% end %}
{% otherwise %}
DomicilioFiscalReceptor="${ o.commercial_partner_id.zip }"
{% end %}
{% end %}
RegimenFiscalReceptor="${ o.commercial_partner_id.property_account_position.code }"
{% end %}
{% otherwise %}
Rfc="XEXX010101000"
DomicilioFiscalReceptor="${ o.address_issued_id.zip }"
ResidenciaFiscal="$o.commercial_partner_id.country_id.code_alpha3"
RegimenFiscalReceptor="616"
UsoCFDI="S01"
{% choose o.commercial_partner_id.vat_split %}
{% when '' %}${ validationerror(_('Missing Tax Identification Number for receiver')) }{% end %}
{% otherwise %}NumRegIdTrib="${ html_escape(o.commercial_partner_id.vat_split) }"{% end %}
{% end %}
{% end %}
{% end %}
{% choose o.cfdi_use.code %}
{% when False %}${ validationerror(_('Please select the use for this CFDI')) }{% end %}
{% otherwise %}UsoCFDI="$o.cfdi_use.code"{% end %}
{% end %} />
/>
<cfdi:Conceptos>
{% for line in o.invoice_line %}
<cfdi:Concepto
......@@ -102,10 +125,12 @@
{% end %}
{% end %}
{% end %}
Unidad="${ line.uos_id.name[:17].upper() + '...' if len(line.uos_id.name) > 20 else line.uos_id.name.upper() }"
Descripcion="${ html_escape(line.name) }"
ValorUnitario="${ line.valorunitario }"
Importe="${ '{0:.2f}'.format(line.importe) }"
Descuento="${ '{0:.2f}'.format(line.descuento) }" >
Descuento="${ '{0:.2f}'.format(line.descuento) }"
ObjetoImp="02" >
{% with taxes=line.impuestos %}
{% if sum_field(taxes['traslados'], 'base') or sum_field(taxes['retenciones'], 'base') %}
<cfdi:Impuestos>
......@@ -140,6 +165,7 @@
{% choose tax.group %}
{% when 'ISR' %}Impuesto="001"{% end %}
{% when 'IVA' %}Impuesto="002"{% end %}
{% when 'IEPS' %}Impuesto="003"{% end %}
{% otherwise %}${ validationerror(_('Missing SAT code for tax: {t}').format(t=tax.name)) }{% end %}
{% end %}
{% choose tax.type %}
......@@ -165,10 +191,11 @@
</cfdi:Concepto>
{% end %}
</cfdi:Conceptos>
{% with taxes=o.impuestos %}
{% if sum_field(taxes['traslados'], 'base') or sum_field(taxes['retenciones'], 'base') %}
<cfdi:Impuestos
TotalImpuestosTrasladados="${ '{0:.2f}'.format(taxes.total_traslados) }"
{% if show_total_traslados %}
TotalImpuestosTrasladados="${ '{0:.2f}'.format(taxes.total_traslados) }"
{% end %}
{% if taxes.total_retenciones %}
TotalImpuestosRetenidos="${ '{0:.2f}'.format(abs(taxes.total_retenciones)) }"
{% end %}>
......@@ -179,6 +206,7 @@
{% choose tax.group %}
{% when 'ISR' %}Impuesto="001"{% end %}
{% when 'IVA' %}Impuesto="002"{% end %}
{% when 'IEPS' %}Impuesto="003"{% end %}
{% otherwise %}${ validationerror(_('Missing SAT code for tax: {t}').format(t=tax.name)) }{% end %}
{% end %}
Importe="${ '{0:.2f}'.format(abs(tax.amount)) }" />
......@@ -189,6 +217,7 @@
<cfdi:Traslados>
{% for tax in taxes.traslados %}
<cfdi:Traslado
Base="${ '{0:.2f}'.format(tax.base) }"
{% choose tax.group %}
{% when 'IVA' %}Impuesto="002"{% end %}
{% when 'IEPS' %}Impuesto="003"{% end %}
......@@ -197,13 +226,40 @@
{% choose tax.type %}
{% when 'percent' %}TipoFactor="Tasa"{% end %}
{% when 'fixed' %}TipoFactor="Cuota"{% end %}
{% when 'none' %}TipoFactor="Exento"{% end %}
{% end %}
TasaOCuota="${ '{0:.6f}'.format(tax.TasaOCuota) }"
Importe="${ '{0:.2f}'.format(tax.amount) }" />
{% if tax.type != 'none' %}
TasaOCuota="${ '{0:.6f}'.format(tax.TasaOCuota) }"
Importe="${ '{0:.2f}'.format(tax.amount) }"
{% end %}
/>
{% end %}
</cfdi:Traslados>
{% end %}
</cfdi:Impuestos>
{% end %}
{% if is_local_taxes %}
<cfdi:Complemento>
<implocal:ImpuestosLocales
version="1.0"
TotaldeTraslados="${ '{0:.2f}'.format(sumif(taxes.locales, 'amount', [('amount', '>', 0)])) }"
TotaldeRetenciones="${ '{0:.2f}'.format(abs(sumif(taxes.locales, 'amount', [('amount', '<', 0)]))) }" >
{% for tax_line in taxes.locales %}
{% if tax_line.amount > 0 %}
<implocal:TrasladosLocales
ImpLocTrasladado="${ html_escape(tax_line.name) }"
TasadeTraslado="${ '{0:.2f}'.format(tax_line.TasaOCuota * 100) }"
Importe="${ '{0:.2f}'.format(tax_line.amount) }" />
{% end %}
{% if tax_line.amount < 0 %}
<implocal:RetencionesLocales
ImpLocRetenido="${ html_escape(tax_line.name) }"
TasadeRetencion="${ '{0:.2f}'.format(abs(tax_line.TasaOCuota * 100)) }"
Importe="${ '{0:.2f}'.format(abs(tax_line.amount)) }" />
{% end %}
{% end %}
</implocal:ImpuestosLocales>
</cfdi:Complemento>
{% end %}
</cfdi:Comprobante>
{% end %}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
{% python
"""Only because it is easier to understand invoice is equal to o
"""
invoice = o
emitter = o.company_id and o.company_id.partner_id or False
reciver = o.partner_id
payment_code = (
','.join([payment.code for payment in o.payment_type]) or 'N/A',
)
tTraslados = sum(
line.amount for line in invoice.tax_line if line.amount >= 0
)
tRetenidos = sum(
line.amount for line in invoice.tax_line if line.amount < 0
)
# In PyERP datetime is a string but we need to change the format
# That's why I transform string into a datetime object
from datetime import datetime
invoice_datetime = datetime.strptime(invoice.datetime, '%Y-%m-%d %H:%M:%S')
%}
<cfdi:Comprobante
LugarExpedicion="${emitter.city}, ${emitter.state_id.name} ${emitter.country_id.name}"
Moneda="${invoice.currency_id.name}"
NumCtaPago="${invoice.partner_bank_id.last_acc_number or 'No identificado'}"
TipoCambio="${invoice.rate or 1}" certificado="@" descuento="0"
fecha="${invoice_datetime.strftime('%Y-%m-%dT%H:%M:%S')}" folio="${invoice.internal_number}"
formaDePago="Pago en una sola exhibicion" metodoDePago="${payment_code}"
noCertificado="@" sello="@"
{% if invoice.journal_id.sequence_id.prefix %}
serie="${invoice.journal_id.sequence_id.prefix}"
{% end %}
subTotal="${'{0:.2f}'.format(invoice.amount_untaxed or 0.0)}"
{% if invoice.type == 'out_invoice' %} tipoDeComprobante="ingreso" {% end %}
{% if invoice.type == 'out_refund' %} tipoDeComprobante="egreso" {% end %}
total="${'{0:.2f}'.format(invoice.amount_total or 0.0)}"
version="3.2" xsi:schemaLocation="http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cfdi="http://www.sat.gob.mx/cfd/3">
<cfdi:Emisor nombre="${emitter.name}" rfc="${emitter.vat_split}">
<cfdi:DomicilioFiscal calle="${emitter.street or 'N/A'}"
codigoPostal="${emitter.zip or 'N/A'}"
colonia="${emitter.street2 or 'N/A'}"
estado="${emitter.state_id.name}"
localidad="${emitter.l10n_mx_city2 or 'N/A'}"
municipio="${emitter.city or 'N/A', True}"
noExterior="${emitter.l10n_mx_street3 or 'N/A'}"
noInterior="${emitter.l10n_mx_street4 or 'N/A'}"
pais="${emitter.country_id.name}"/>
<cfdi:ExpedidoEn calle="${emitter.street or 'N/A'}"
codigoPostal="${emitter.zip or 'N/A'}"
colonia="${emitter.street2 or 'N/A'}"
estado="${emitter.state_id.name or 'N/A'}"
localidad="${emitter.l10n_mx_city2 or 'N/A'}"
municipio="${emitter.city or 'N/A'}"
noExterior="${emitter.l10n_mx_street3 or 'N/A'}"
noInterior="${emitter.l10n_mx_street4 or 'N/A'}"
pais="${emitter.country_id.name or 'N/A'}"/>
<cfdi:RegimenFiscal Regimen="${emitter.property_account_position.name}"/>
</cfdi:Emisor>
<cfdi:Receptor nombre="${reciver.name}" rfc="${reciver.vat_split}">
<cfdi:Domicilio calle="${reciver.street}"
codigoPostal="${reciver.zip}"
colonia="${reciver.street2}"
estado="${reciver.state_id.name}"
localidad="${reciver.l10n_mx_city2}"
municipio="${reciver.city}"
noExterior="${reciver.l10n_mx_street3}"
noInterior="${reciver.l10n_mx_street4}"
pais="${reciver.country_id.name}"/>
</cfdi:Receptor>
<cfdi:Conceptos>
{% for line in invoice.invoice_line %}
<cfdi:Concepto cantidad="${'{0:.2f}'.format(line.quantity or 0.0)}"
descripcion="${line.name or ''}"
importe="${'{0:.2f}'.format(line.price_subtotal or 0.0)}"
noIdentificacion="${line.product_id.default_code or '0'}"
unidad="${line.uos_id.name or 'Unit(s)'}"
valorUnitario="${'{0:.2f}'.format(line.price_unit or 0.0)}"/>
{% end %}
</cfdi:Conceptos>
<cfdi:Impuestos totalImpuestosTrasladados="${tTraslados}" totalImpuestosRetenidos="${tRetenidos}">
{% if tTraslados > 0 %}
<cfdi:Traslados>
{% for tax_line in invoice.tax_line %}
{% if tax_line.amount >= 0 %}
<cfdi:Traslado importe="${abs(float('{0:.2f}'.format(tax_line.amount)))}"
impuesto="${tax_line.name2}"
tasa="${abs(float('{0:.2f}'.format(tax_line.tax_percent)))}"/>
{% end %}
{% end %}
</cfdi:Traslados>
{% end %}
{% if tRetenidos > 0 %}
<cfdi:Retenciones>
{% for tax_line in invoice.tax_line %}
{% if tax_line.amount < 0 %}
<cfdi:Retencion importe="${abs(float('{0:.2f}'.format(tax_line.amount)))}"
impuesto="${tax_line.name2}"
tasa="${abs(float('{0:.2f}'.format(tax_line.tax_percent)))}"/>
{% end %}
{% end %}
</cfdi:Retenciones>
{% end %}
</cfdi:Impuestos>
<cfdi:Complemento>
</cfdi:Complemento>
</cfdi:Comprobante>
<?xml version="1.0" encoding="UTF-8"?>
{% python setLang(o.create_uid.lang or 'en_US') %}
<cfdi:Comprobante
xsi:schemaLocation="http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd http://www.sat.gob.mx/Pagos20 http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd"
xmlns:cfdi="http://www.sat.gob.mx/cfd/4"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:pago20="http://www.sat.gob.mx/Pagos20"
Version="4.0"
{% if o.journal_id.sequence_id.prefix %}
Serie="$o.serie"
{% end %}
Exportacion="01"
Folio="$o.folio"
Fecha="${ format_datetime(o.cfdi_datetime, '%Y-%m-%dT%H:%M:%S', tz=user.partner_id.tz) }"
Sello="@"
NoCertificado="@"
Certificado="@"
SubTotal="0"
Moneda="XXX"
Total="0"
TipoDeComprobante="P"
{% choose o.company_id.zip %}
{% when False %}${ validationerror(_('Missing ZIP on issued address.')) }{% end %}
{% when '' %}${ validationerror(_('Missing ZIP on issued address.')) }{% end %}
{% otherwise %}LugarExpedicion="${ o.company_id.zip }"{% end %}
{% end %}
>
{% if o.related_cfdi_ids %}
<cfdi:CfdiRelacionados TipoRelacion="${ o.cfdi_relation_type.code }">
{% for cfdi in o.related_cfdi_ids %}
<cfdi:CfdiRelacionado UUID="${ cfdi.uuid }"/>
{% end %}
</cfdi:CfdiRelacionados>
{% end %}
<cfdi:Emisor
Nombre="${ html_escape(o.company_id.name.upper()) }"
RegimenFiscal="$o.company_id.partner_id.property_account_position.code"
Rfc="${ html_escape(o.company_id.partner_id.vat_split) }" />
<cfdi:Receptor
Nombre="${ html_escape(o.partner_id.name.upper()) }"
UsoCFDI="CP01"
{% choose o.partner_id.country_id and o.partner_id.country_id.code_alpha3 %}
{% when False %}
${ validationerror(_('Missing country for partner')) }
{% end %}
{% when 'MEX' %}
{% choose o.partner_id.vat_split %}
{% when '' %}${ validationerror(_('Missing VAT number for receiver')) }{% end %}
{% otherwise %}Rfc="${ html_escape(o.partner_id.vat_split) }"{% end %}
{% end %}
{% choose html_escape(o.partner_id.vat_split) == "XAXX010101000" %}
{% when %}DomicilioFiscalReceptor="${ o.company_id.zip }"{% end %}
{% otherwise %}
DomicilioFiscalReceptor="${ o.partner_id.zip }"
{% end %}
{% end %}
RegimenFiscalReceptor="${ o.partner_id.property_account_position.code }"
{% end %}
{% otherwise %}
Rfc="XEXX010101000"
DomicilioFiscalReceptor="${ o.company_id.zip }"
ResidenciaFiscal="$o.partner_id.country_id.code_alpha3"
RegimenFiscalReceptor="616"
{% choose o.partner_id.vat_split %}
{% when '' %}${ validationerror(_('Missing Tax Identification Number for receiver')) }{% end %}
{% otherwise %}NumRegIdTrib="${ html_escape(o.partner_id.vat_split) }"{% end %}
{% end %}
{% end %}
{% end %}
/>
<cfdi:Conceptos>
<cfdi:Concepto
Cantidad="1"
ClaveProdServ="84111506"
ClaveUnidad="ACT"
Descripcion="Pago"
Importe="0"
ObjetoImp="01"
ValorUnitario="0" />
</cfdi:Conceptos>
<cfdi:Complemento>
<pago20:Pagos Version="2.0">
<pago20:Totales
{% if o.currency_id.name != 'MXN' %}
MontoTotalPagos="${ abs(o.currency_id.round(o.currency_rate() * o.amount)) }"
{% end %}
{% if o.currency_id.name == 'MXN' %}
MontoTotalPagos="${ o.amount }"
{% end %}
{% for tax_total in o.totales_p() %}
{% if tax_total.tax2_id.tax_category_id.code == "IVA" %}
{% if tax_total.tax2_id.type == "none" %}
TotalTrasladosBaseIVAExento="${ abs(tax_total.tax2_base) }"
{% end %}
{% if tax_total.tax2_id.type != "none" %}
{% with tax_amount = "{0:.0f}".format(tax_total.tax2_id.amount * 100) %}
TotalTrasladosBaseIVA${ tax_amount }="${ abs(tax_total.tax2_base) }"
TotalTrasladosImpuestoIVA${ tax_amount }="${ abs(tax_total.importe) }"
{% end %}
{% end %}
{% end %}
{% end %}
{% with retenciones = o.totales_p(tax_type="retenciones") %}
{% for tax_total in retenciones %}
{% if tax_total.tax2_id.tax_category_id.code == "IVA" %}
TotalRetencionesIVA="${ abs(tax_total.importe) }"
{% end %}
{% if tax_total.tax2_id.tax_category_id.code == "ISR" %}
TotalRetencionesISR="${ abs(tax_total.importe) }"
{% end %}
{% if tax_total.tax2_id.tax_category_id.code == "IEPS" %}
TotalRetencionesIEPS="${ abs(tax_total.importe) }"
{% end %}
{% end %}
{% end %}
/>
<pago20:Pago
FechaPago="${ format_date(o.date, '%Y-%m-%dT12:%M:%S') }"
FormaDePagoP="${ o.payment_type_id.code }"
MonedaP="${ o.currency_id.name }"
TipoCambioP="${ o.currency_rate() if o.currency_id.name != 'MXN' else 1 }"
Monto="${ '{0:.2f}'.format(o.currency_id.cfdi_round(o.amount)) }"
NumOperacion="${ o.reference or o.number }"
{% if o.partner_bank_id and o.partner_bank_id.is_valid_for_payment_form(o.payment_type_id.regex) %}
CtaOrdenante="$o.partner_bank_id.acc_number"
{% if o.partner_bank_id.bank.exists() %}
{% choose o.partner_bank_id.bank.full_name %}
{% when False %}{% end %}
{% otherwise %}NomBancoOrdExt="$o.partner_bank_id.bank.full_name"{% end %}
{% end %}
{% choose o.partner_bank_id.bank.vat_number %}
{% when False %}{% end %}
{% otherwise %}RfcEmisorCtaOrd="$o.partner_bank_id.bank.vat_number"{% end %}
{% end %}
{% end %}
{% end %}
{% if o.payment_type_id.code in ["02", "03", "04", "05", "28" "29"]%}
{% choose o.journal_id.res_partner_bank_id.acc_number %}
{% when None %}{% end %}
{% when False %}{% end %}
{% otherwise %}
CtaBeneficiario="$o.journal_id.res_partner_bank_id.acc_number"
{% if o.journal_id.res_partner_bank_id.bank.vat_number %}
RfcEmisorCtaBen="$o.journal_id.res_partner_bank_id.bank.vat_number"
{% end %}
{% end %}
{% end %}
{% end %}
>
{% for invoice in o.doctos_relacionados %}
<pago20:DoctoRelacionado
{% if invoice.journal_id.sequence_id.prefix %}
Serie="$invoice.serie"
{% end %}
Folio="$invoice.folio"
IdDocumento="${ invoice.cfdi_folio_fiscal }"
MonedaDR="${ invoice.currency_id.name }"
{% if invoice.currency_id == o.currency_id %}
EquivalenciaDR="1"
{% end %}
{% if invoice.currency_id != o.currency_id %}
EquivalenciaDR="${ o.tipocambiodr(invoice) }"
{% end %}
NumParcialidad="${ o.numparcialidad(invoice) }"
ImpSaldoAnt="${ '{0:.2f}'.format(o.impsaldoant(invoice)) }"
ImpSaldoInsoluto="${ '{0:.2f}'.format(o.impsaldoant(invoice) - o.imppagado(invoice)) }"
ImpPagado="${ '{0:.2f}'.format(o.imppagado(invoice)) }"
ObjetoImpDR="02"
>
<pago20:ImpuestosDR>
{% with retenciones = o.impuestos_dr(invoice, tax_type="retenciones") %}
{% if retenciones %}
<pago20:RetencionesDR>
{% for tax_move in retenciones %}
<pago20:RetencionDR
BaseDR="${ abs(invoice.currency_id.round(tax_move.tax2_base)) }"
ImporteDR="${ '{number:.{digits}f}'.format(number=abs(invoice.currency_id.round(tax_move.tax2_base) * tax_move.tax2_id.amount), digits=4) }"
{% choose tax_move.tax2_id.tax_category_id.code %}
{% when 'ISR' %}ImpuestoDR="001"{% end %}
{% when 'IVA' %}ImpuestoDR="002"{% end %}
{% when 'IEPS' %}ImpuestoDR="003"{% end %}
{% end %}
{% if tax_move.tax2_id.type != 'none' %}
TasaOCuotaDR="${ '{0:.6f}'.format(abs(tax_move.tax2_id.amount)) }"
{% end %}
{% choose tax_move.tax2_id.type %}
{% when 'percent' %}TipoFactorDR="Tasa"{% end %}
{% when 'fixed' %}TipoFactorDR="Cuota"{% end %}
{% when 'none' %}TipoFactorDR="Exento"{% end %}
{% end %} />
{% end %}
</pago20:RetencionesDR>
{% end %}
{% end %}
<pago20:TrasladosDR>
{% for tax_move in o.impuestos_dr(invoice) %}
<pago20:TrasladoDR
BaseDR="${ abs(tax_move.tax2_base) }"
{% choose tax_move.tax2_id.tax_category_id.code %}
{% when 'ISR' %}ImpuestoDR="001"{% end %}
{% when 'IVA' %}ImpuestoDR="002"{% end %}
{% when 'IEPS' %}ImpuestoDR="003"{% end %}
{% end %}
{% if tax_move.tax2_id.type != 'none' %}
ImporteDR="${ '{number:.{digits}f}'.format(number=abs(tax_move.tax2_base * tax_move.tax2_id.amount), digits=6) }"
TasaOCuotaDR="${ '{0:.6f}'.format(tax_move.tax2_id.amount) }"
{% end %}
{% choose tax_move.tax2_id.type %}
{% when 'percent' %}TipoFactorDR="Tasa"{% end %}
{% when 'fixed' %}TipoFactorDR="Cuota"{% end %}
{% when 'none' %}TipoFactorDR="Exento"{% end %}
{% end %}
/>
{% end %}
</pago20:TrasladosDR>
</pago20:ImpuestosDR>
</pago20:DoctoRelacionado>
{% end %}
<pago20:ImpuestosP>
{% with retenciones = o.impuestos_p(tax_type="retenciones") %}
{% if retenciones %}
<pago20:RetencionesP>
{% for tax_total in retenciones %}
<pago20:RetencionP
ImporteP="${ abs(tax_total.importe) }"
{% choose tax_total.tax2_id.tax_category_id.code %}
{% when 'ISR' %}ImpuestoP="001"{% end %}
{% when 'IVA' %}ImpuestoP="002"{% end %}
{% when 'IEPS' %}ImpuestoP="003"{% end %}
{% end %}
/>
{% end %}
</pago20:RetencionesP>
{% end %}
{% end %}
<pago20:TrasladosP>
{% for tax_total in o.impuestos_p() %}
<pago20:TrasladoP
BaseP="${ abs(tax_total.tax2_base) }"
{% choose tax_total.tax2_id.tax_category_id.code %}
{% when 'ISR' %}ImpuestoP="001"{% end %}
{% when 'IVA' %}ImpuestoP="002"{% end %}
{% when 'IEPS' %}ImpuestoP="003"{% end %}
{% end %}
{% if tax_total.tax2_id.type != 'none' %}
ImporteP="${ abs(tax_total.importe) }"
TasaOCuotaP="${ '{0:.6f}'.format(tax_total.tax2_id.amount) }"
{% end %}
{% choose tax_total.tax2_id.type %}
{% when 'percent' %}TipoFactorP="Tasa"{% end %}
{% when 'fixed' %}TipoFactorP="Cuota"{% end %}
{% when 'none' %}TipoFactorP="Exento"{% end %}
{% end %}
/>
{% end %}
</pago20:TrasladosP>
</pago20:ImpuestosP>
</pago20:Pago>
</pago20:Pagos>
</cfdi:Complemento>
</cfdi:Comprobante>
<?xml version="1.0" encoding="UTF-8"?>
{% python setLang(o.create_uid.lang or 'en_US') %}
<cfdi:Comprobante
xsi:schemaLocation="http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv33.xsd http://www.sat.gob.mx/Pagos http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos10.xsd"
xmlns:cfdi="http://www.sat.gob.mx/cfd/3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:pago10="http://www.sat.gob.mx/Pagos"
Version="3.3"
{% if o.journal_id.sequence_id.prefix %}
Serie="$o.serie"
{% end %}
Folio="$o.folio"
Fecha="${ format_datetime(o.cfdi_datetime, '%Y-%m-%dT%H:%M:%S', tz=user.partner_id.tz) }"
Sello="@"
NoCertificado="@"
Certificado="@"
SubTotal="0"
Moneda="XXX"
Total="0"
TipoDeComprobante="P"
LugarExpedicion="${ o.company_id.zip }" >
{% if o.related_cfdi_ids %}
<cfdi:CfdiRelacionados TipoRelacion="${ o.cfdi_relation_type.code }">
{% for cfdi in o.related_cfdi_ids %}
<cfdi:CfdiRelacionado UUID="${ cfdi.uuid }"/>
{% end %}
</cfdi:CfdiRelacionados>
{% end %}
<cfdi:Emisor
{% choose o.company_id.partner_id.vat_split %}
{% when False %}${ validationerror(_('Missing VAT number for company')) }{% end %}
{% otherwise %}Rfc="${ html_escape(o.company_id.partner_id.vat_split) }"{% end %}
{% end %}
Nombre="${ html_escape(o.company_id.name) }"
RegimenFiscal="$o.company_id.account_position_id.code"/>
<cfdi:Receptor
Nombre="${ html_escape(o.partner_id.name) }"
{% choose o.partner_id.country_id and o.partner_id.country_id.code_alpha3 %}
{% when 'MEX' %}
{% choose o.partner_id.vat_split %}
{% when '' %}${ validationerror(_('Missing VAT number for receiver')) }{% end %}
{% otherwise %}Rfc="${ html_escape(o.partner_id.vat_split) }"{% end %}
{% end %}
{% end %}
{% otherwise %}
Rfc="XEXX010101000"
ResidenciaFiscal="$o.partner_id.country_id.code_alpha3"
{% choose o.partner_id.vat_split %}
{% when '' %}${ validationerror(_('Missing Tax Identification Number for receiver')) }{% end %}
{% otherwise %}NumRegIdTrib="${ html_escape(o.partner_id.vat_split) }"{% end %}
{% end %}
{% end %}
{% end %}
UsoCFDI="P01" />
<cfdi:Conceptos>
<cfdi:Concepto
ClaveProdServ="84111506"
Cantidad="1"
ClaveUnidad="ACT"
Descripcion="Pago"
ValorUnitario="0"
Importe="0" />
</cfdi:Conceptos>
<cfdi:Complemento>
<pago10:Pagos Version="1.0">
<pago10:Pago
FechaPago="${ format_date(o.date, '%Y-%m-%dT12:%M:%S') }"
{% choose o.payment_type_id.code %}
{% when False %}${ validationerror(_('Missing payment method for voucher')) }{% end %}
{% when '99' %}${ validationerror(_('Payment method must be different than 99.')) }{% end %}
{% otherwise %}FormaDePagoP="${ o.payment_type_id.code }"{% end %}
{% end %}
{% choose o.state %}
{% when 'cancel' %}
MonedaP="MXN"
Monto="1.00"
>
{% end %}
{% otherwise %}
MonedaP="${ o.currency_id.name }"
{% if o.currency_id.name != 'MXN' %}
TipoCambioP="${ o.currency_rate() }"
{% end %}
Monto="${ '{0:.2f}'.format(o.currency_id.cfdi_round(o.amount)) }"
NumOperacion="${ o.reference or o.number }"
{% if o.partner_bank_id and o.partner_bank_id.is_valid_for_payment_form(o.payment_type_id.regex) %}
CtaOrdenante="$o.partner_bank_id.acc_number"
{% if o.partner_bank_id.bank.exists() %}
{% choose o.partner_bank_id.bank.full_name %}
{% when False %}{% end %}
{% otherwise %}NomBancoOrdExt="$o.partner_bank_id.bank.full_name"{% end %}
{% end %}
{% choose o.partner_bank_id.bank.vat_number %}
{% when False %}{% end %}
{% otherwise %}RfcEmisorCtaOrd="$o.partner_bank_id.bank.vat_number"{% end %}
{% end %}
{% end %}
{% end %}
{% if o.payment_type_id.code in ["02", "03", "04", "05", "28" "29"]%}
{% choose o.journal_id.res_partner_bank_id.acc_number %}
{% when None %}{% end %}
{% when False %}{% end %}
{% otherwise %}
CtaBeneficiario="$o.journal_id.res_partner_bank_id.acc_number"
{% if o.journal_id.res_partner_bank_id.bank.vat_number %}
RfcEmisorCtaBen="$o.journal_id.res_partner_bank_id.bank.vat_number"
{% end %}
{% end %}
{% end %}
{% end %}
>
{% for invoice in o.doctos_relacionados %}
<pago10:DoctoRelacionado
{% if invoice.journal_id.sequence_id.prefix %}
Serie="$invoice.serie"
{% end %}
Folio="$invoice.folio"
IdDocumento="${ invoice.cfdi_folio_fiscal }"
MonedaDR="${ invoice.currency_id.name }"
{% if invoice.currency_id != o.currency_id %}
{% choose invoice.currency_id.name %}
{% when 'MXN' %}TipoCambioDR="1"{% end %}
{% otherwise %}TipoCambioDR="${ o.tipocambiodr(invoice) }"{% end %}
{% end %}
{% end %}
{% choose invoice.payment_method_id.code %}
{% when False %}MetodoDePagoDR="PUE"{% end %}
{% otherwise %}
MetodoDePagoDR="$invoice.payment_method_id.code"
{% if invoice.payment_method_id.code == 'PPD' %}
NumParcialidad="${ o.numparcialidad(invoice) }"
ImpSaldoAnt="${ '{0:.2f}'.format(o.impsaldoant(invoice)) }"
ImpSaldoInsoluto="${ '{0:.2f}'.format(o.impsaldoant(invoice) - o.imppagado(invoice)) }"
{% end %}
{% end %}
{% end %}
ImpPagado="${ '{0:.2f}'.format(o.imppagado(invoice)) }" />
{% end %}
{% end %}
{% end %}
</pago10:Pago>
</pago10:Pagos>
</cfdi:Complemento>
</cfdi:Comprobante>
......@@ -18,3 +18,16 @@ Característica: Generación de archivos XML para la emisión de facturas de cli
Ejemplos:
| tiporelacion | Nombre |
| 04 | Substitución de los CFDI previos |
Característica: Cancelar facturas con CFDIS no timbrados.
Esquema del escenario: Cancelación de facturas.
Dado una Factura en MXN por los siguientes conceptos
| Concepto | Precio Unitario | Cantidad | Impuesto |
| ProductoA | 130.00 | 1.0 | IVA 16% |
Y la Factura está en estado Abierto
Y la Factura tiene un CFDI en Borrador
Cuando se cancele la Factura
Entonces la factura cambia a Cancelada
Y el CFDI cambia a Cancelado
......@@ -56,8 +56,8 @@ Característica: Generación de archivos XML para la emisión de recibos de pago
Cuando genero el Recibo electrónico de pagos
Entonces el TipoCambioDR debe aparecer
Y deben existir 1 nodos DoctoRelacionado
Y el ImpSaldoAnt para el nodo 1 debe ser 100.00
Y el ImpSaldoInsoluto para el nodo 1 debe ser 50.00
Y el ImpSaldoAnt para el nodo 1 debe ser 116.00
Y el ImpSaldoInsoluto para el nodo 1 debe ser 66.00
Y el TipoCambioDR no debe aparecer
Escenario: Recibo electrónico de una factura conciliada con una nota de crédito y pagada
......@@ -71,11 +71,14 @@ Característica: Generación de archivos XML para la emisión de recibos de pago
| Concepto | Precio Unitario | Cantidad | Impuesto | Descuento |
| ProductoA | 7.90 | 80.0 | IVA 0% | 7 |
Y la Nota de Crédito está en estado Abierto
Y un Pago por 5806.80 MXN
Y concilio la Factura con la Nota de Crédito y el Pago
Y dejo Abierto el saldo de la Factura
Y un Pago por 5806.80 MXN
Cuando genero el Recibo electrónico de pagos
Entonces debo obtener un Folio Fiscal en el Pago
Entonces deben existir 1 nodos DoctoRelacionado
Y el ImpSaldoAnt para el nodo 1 debe ser 5851.04
Y el ImpPagado para el nodo 1 debe ser 5806.80
Y el ImpSaldoInsoluto para el nodo 1 debe ser 44.24
@Multimoneda
Esquema del escenario: Pago en moneda nacional para facturas en moneda extranjera
......@@ -92,17 +95,17 @@ Característica: Generación de archivos XML para la emisión de recibos de pago
Y el ImpSaldoAnt para el nodo 1 debe ser <impsaldoant>
Y el ImpPagado para el nodo 1 debe ser <imppagado>
Y el ImpSaldoInsoluto para el nodo 1 debe ser <impsaldoinsoluto>
Y el TipoCambioDR para el nodo 1 debe ser <tipo_cambio>
Y el EquivalenciaDR para el nodo 1 debe ser <equivalencia_dr>
Ejemplos:
| monto_pago | tipo_cambio | impsaldoant | imppagado | impsaldoinsoluto |
| 4222.40 | 0.054945 | 232.00 | 232.00 | 0.00 |
| 4640.00 | 0.050000 | 232.00 | 232.00 | 0.00 |
| 4250.00 | 0.054588 | 232.00 | 232.00 | 0.00 |
| 4176.00 | 0.055555 | 232.00 | 232.00 | 0.00 |
| 4630.00 | 0.050000 | 232.00 | 231.50 | 0.50 |
| 4240.86 | 0.054588 | 232.00 | 231.50 | 0.50 |
| 4167.05 | 0.055555 | 232.00 | 231.50 | 0.50 |
| monto_pago | tipo_cambio | equivalencia_dr | impsaldoant | imppagado | impsaldoinsoluto |
| 4222.40 | 0.054945 | 0.054946 | 232.00 | 232.00 | 0.00 |
| 4640.00 | 0.050000 | 0.054946 | 232.00 | 232.00 | 0.00 |
| 4250.00 | 0.054588 | 0.054946 | 232.00 | 232.00 | 0.00 |
| 4176.00 | 0.055555 | 0.054946 | 232.00 | 232.00 | 0.00 |
| 4630.00 | 0.050000 | 0.054946 | 232.00 | 231.50 | 0.50 |
| 4240.86 | 0.054588 | 0.054946 | 232.00 | 231.50 | 0.50 |
| 4167.05 | 0.055555 | 0.054946 | 232.00 | 231.50 | 0.50 |
@Multimoneda
......@@ -144,23 +147,3 @@ Característica: Generación de archivos XML para la emisión de recibos de pago
| FechaPagoAnterior | FechaPagoActual | impsaldoant | imppagado | impsaldoinsoluto |
| 2019-02-01 | 2019-02-01 | 100.00 | 100.00 | 0.00 |
| 2019-02-02 | 2019-02-01 | 232.00 | 0.00 | 132.00 |
Esquema del escenario: Cancelación con substitución.
Dado una Factura en MXN por los siguientes conceptos
| Concepto | Precio Unitario | Cantidad | Impuesto |
| ProductoA | 130.00 | 1.0 | IVA 16% |
Y la Factura está en estado Abierto
Y un Pago por 130 MXN
Y el Pago está Contabilizado
Y el Pago tiene un CFDI en Hecho
Cuando se substituye el pago
Y el Pago está Cancelado
Y el CFDI está Cancelado
Y el Pago cambia a Borrador
Entonces se Válida y Firma el Pago, debe existir 1 nodo CfdiRelacionados
Y el TipoRelacion para el nodo 1 debe ser <tiporelacion>
Y el UUID para el nodo 1 debe ser UUID del CFDI Cancelado
Ejemplos:
| tiporelacion | Nombre |
| 04 | Substitución de los CFDI previos |
# -*- coding: utf-8 -*-
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
from openerp import fields
from openerp.addons.account.tests.common import AccountTestInvoicingCommon
class TestCancelInvoice(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super(TestCancelInvoice, cls).setUpClass()
# Create required fields and assign to customer
cls.cfdi_use = cls.env["cfdi.use"].search([], limit=1)
cls.fiscal_position = cls.env["account.fiscal.position"].search([], limit=1)
cls.payment_type_id = cls.env["payment.type"].search([], limit=1)
cls.customer.write(
{
"property_account_position": cls.fiscal_position.id,
"payment_type_customer": cls.payment_type_id.id,
"cfdi_use": cls.cfdi_use.id,
"zip": "20928",
}
)
cls.invoice = cls.env["account.invoice"].create(
{
"date": fields.Date.context_today(cls.customer),
"partner_id": cls.customer.id,
"journal_id": cls.sale_journal.id,
"type": "out_invoice",
"account_id": cls.customer.property_account_payable.id,
"cfdi_periodicity": "04",
"cfdi_use": cls.customer.cfdi_use.id,
"fiscal_position": cls.customer.property_account_position.id,
"payment_type_ids": [(4, cls.customer.payment_type_customer.id)],
"invoice_line": [
(
0,
0,
{
"name": cls.product.name,
"account_id": cls.product.property_account_income.id,
"price_unit": 100.00,
"quantity": 1.0,
"product_id": cls.product.id,
"uos_id": cls.product.uom_id.id,
"invoice_line_tax_id": [(4, cls.product.taxes_id.id)],
},
),
],
}
)
# Setup a SAT code for the product
cfdi_product_service = cls.env["cfdi.product.service"].search([], limit=1).id
cls.product.cfdi_product_service_id = cfdi_product_service
# Add data to validate the invoice
cls.sale_journal.write({"update_posted": True, "sign_sat": True})
cls.customer.write({"vat": "MXXAXX010101000"})
def test_cancel_invoice_with_out_cfdi(self):
"""This scenario is related to canceling invoices without stamped cfdi"""
# Ensure no CFDI created for invoice
self.sale_journal.sign_sat = False
# Validate invoice
self.invoice.action_invoice_open()
# Check invoice state
self.assertEqual(self.invoice.state, "open")
# Cancel the account move
self.invoice.move_id.button_cancel()
# Cancel the invoice
self.invoice.action_cancel()
# Check invoice state
self.assertEqual(self.invoice.state, "cancel")
......@@ -3,7 +3,10 @@
<data>
<act_window id="account_invoice_action_cfdi_details" name="CFDI Details"
domain="[('res_id', 'in', active_ids), ('type_attachment', '=', 'account.invoice')]"
domain="[
('res_id', 'in', active_ids),
('type_attachment', '=', 'account.invoice')
]"
res_model="ir.attachment.facturae.mx" src_model="account.invoice" />
<record id="account_invoice_view_search" model="ir.ui.view">
......@@ -16,7 +19,16 @@
</field>
<group position="before">
<separator/>
<filter string="To Sign" domain="[('cfdi_state', '=', 'draft')]" help="Invoices to be signed"/>
<filter
string="To sign"
name="to_sign"
domain="[('cfdi_state', '=', 'draft')]"
help="Invoices to be signed" />
<filter
string="To cancel"
name="to_cancel"
domain="[('l10n_mx_edi_to_cancel', '=', True)]"
help="Invoices that being substituted and must be cancelled" />
</group>
</field>
</record>
......@@ -27,53 +39,179 @@
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<button name="invoice_cancel" position="before">
<button name="substitute_invoice" type="object" string="Replace"
attrs="{'invisible': ['|', ('cfdi_state', 'not in', ['signed', 'done']), ('state', '=', 'cancel')]}"/>
<field name="is_cfdi_candidate" invisible="1" />
</button>
<xpath expr="//sheet[@string='Invoice']/h1" position="after">
<h4 collspan="2">
<xpath expr="//header" position="after">
<div
class="alert alert-danger"
attrs="{'invisible':[
'|',
('state', '=', 'cancel'),
(
'cfdi_state',
'in',
['signed', 'cancel', 'done', False]
)
]}"
role="alert"
style="margin-bottom:0px;" >
<field
class="oe_inline"
name="l10n_mx_edi_error"
readonly="1" />
</div>
<div
class="alert alert-warning"
role="alert"
style="margin-bottom:0px;"
attrs="{'invisible': [('l10n_mx_edi_to_cancel', '=', False)]}" >
<field
name="l10n_mx_edi_to_cancel"
readonly="1"
class="oe_inline" />
<field
name="l10n_mx_edi_original_invoice"
readonly="1" class="oe_inline"
context="{'form_view_ref': 'account.invoice_form'}"/>
</div>
</xpath>
<xpath expr="//h1[2]" position="after">
<h4 collspan="2"
attrs="{'invisible': [('is_cfdi_candidate', '=', False)]}">
<field string="Fiscal Number" name="cfdi_id"
placeholder="Fiscal Number" readonly="1"
attrs="{'invisible': ['|', ('cfdi_id', '=', False), ('cfdi_state', '=', 'draft')]}" />
placeholder="Fiscal Number" readonly="1"
attrs="{
'invisible': [
'|',
('cfdi_id', '=', False),
('cfdi_state', '=', 'draft')
]
}" />
</h4>
</xpath>
<xpath expr="//sheet/group//group[last()]" position="inside">
<label for="cfdi_state" string="PAC State" states="open,paid,cancel"/>
<div class="o_row" states="open,paid,cancel">
<label for="cfdi_state" string="PAC State"
attrs="{
'invisible': [
'|',
('is_cfdi_candidate', '=', False),
('state', 'not in', ['open', 'paid', 'cancel'])
]
}"/>
<div class="o_row"
attrs="{
'invisible': [
'|',
('is_cfdi_candidate', '=', False),
('state', 'not in', ['open', 'paid', 'cancel'])
]
}">
<field name="cfdi_state" class="oe_inline"/>
<button name="action_validate_cfdi" string="Retry"
class="oe_link oe_inline" type="object"
groups="account.group_account_invoice"
attrs="{'invisible':[('cfdi_state', 'in', ['signed', 'cancel', 'done', False])]}"/>
attrs="{
'invisible':[
'|',
('state', '=', 'cancel'),
('cfdi_state','in', ['signed', 'cancel', 'done', False])
]
}"/>
</div>
</xpath>
<field name="fiscal_position" position="after">
<field name="cfdi_use"
attrs="{'readonly': [('state', 'in', ('paid', 'open', 'cancel'))]}"
attrs="{
'readonly': [('cfdi_state','in', ['signed', 'cancel', 'done'])],
'invisible': [('is_cfdi_candidate', '=', False)]
}"
options="{'no_create':True}"/>
<field name="cfdi_adenda_ids"
attrs="{
'readonly': [('state','in',('cancel','paid'))],
'invisible': [('is_cfdi_candidate', '=', False)]
}"
options="{'no_create': True,'no_open':True}"
widget="many2many_tags" />
<field name="l10n_mx_export" invisible="1" />
</field>
<xpath expr="//page[@string='Payments']" position='after'>
<page name="Adendas" string="Adendas">
<group>
<field name="cfdi_adenda_id" options="{'no_create': True,'no_open':True}" />
<field name="cfdi_adenda_code" invisible="1"/>
<page name="Adendas" string="Adendas"
attrs="{'invisible': [('cfdi_adenda_ids', '=', False)]}">
<group name="adenda">
</group>
</page>
<page string="Related" attrs="{
'invisible': [
'|', '|',
('is_cfdi_candidate', '=', False),
('type', 'not in', ('out_refund','out_invoice')),
('state', '!=', 'draft'),
('cfdi_relation_type', '=', False),
]
}
">
<group>
<group>
<field name="cfdi_relation_type"
attrs="{'readonly':[('state','!=', 'draft')]}"
options="{'no_create': True, 'no_open':True}"/>
</group>
<group>
<field name="commercial_partner_id" invisible="1"/>
<field name="origin_invoice_ids"
domain="[
('commercial_partner_id', '=', commercial_partner_id),
('state','in', ['open','paid']),
('type','=','out_invoice'),
]"
widget="many2many_tags"
options="{'no_create': True}"
string="Documents"
attrs="{
'readonly': [('state', '!=', 'draft')],
'required': [('cfdi_relation_type', '!=', False)],
}"
context="{
'form_view_ref': 'account.invoice_form',
}" />
</group>
</group>
</page>
</xpath>
<xpath expr="//field[@name='origin']" position="before">
<field name="address_issued_id"/>
</xpath>
<xpath expr="//field[@name='payment_term']" position="after">
<field name="payment_method_id" class="oe_inline" options="{'no_create':True, 'no_open':True}"/>
<field name="payment_method_id" class="oe_inline"
options="{'no_create':True, 'no_open':True}"/>
</xpath>
<xpath expr="//field[@name='partner_id']" position="attributes">
<attribute name="context" translation="off">{'search_default_customer':1, 'show_address': 1, 'default_is_company': 1}</attribute>
<attribute name="domain" translation="off">[('customer', '=', True), '|', ('is_company', '=', True), ('type', '=', 'invoice')]</attribute>
<attribute name="options" translation="off">{'always_reload': True,'no_quick_create': True}</attribute>
<attribute
name="context"
translation="off">{'search_default_customer':1, 'show_address': 1, 'default_is_company': 1}
</attribute>
<attribute
name="domain"
translation="off">[
('customer', '=', True),
'|',
('is_company', '=', True),
('type', '=', 'invoice')]
</attribute>
<attribute
name="options"
translation="off">{'always_reload': True,'no_quick_create': True}
</attribute>
</xpath>
<xpath expr="//field[@name='invoice_line']//tree//field[@name='account_analytic_id']" position="after">
<field name="cfdi_cuentapredial" groups="l10n_mx_facturae.cfdi_cuentapredial" />
</xpath>
<xpath expr="//page//tree//field[@name='invoice_line_tax_id']" position="before">
<field name="cfdi_custom_number"
widget="many2many_tags"
groups="l10n_mx.group_cfdi_custom_number"/>
</xpath>
</field>
</record>
......@@ -82,22 +220,11 @@
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_tree"/>
<field name="arch" type="xml">
<field name="number" position="after">
<field name="name" position="after">
<field string="Fiscal Number" name="cfdi_id" invisible="1" />
</field>
</field>
</record>
<record id="account_invoice_view_form_pediment" model="ir.ui.view">
<field name="name">account.invoice.view.form.pediment</field>
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<xpath expr="//page//tree//field[@name='invoice_line_tax_id']" position="before">
<field name="cfdi_custom_number" widget="many2many_tags" groups="l10n_mx.group_cfdi_custom_number"/>
</xpath>
</field>
</record>
</data>
</openerp>
......@@ -36,6 +36,24 @@
confirm="Are you sure to cancel this voucher?"
attrs="{'invisible': ['|', ('cfdi_state', 'not in', ['signed', 'done']), ('state', '=', 'cancel')]}"/>
</button>
<xpath expr="//header" position="after">
<div
class="alert alert-danger"
attrs="{'invisible':[
(
'cfdi_state',
'in',
['signed', 'cancel', 'done', False]
)
]}"
role="alert"
style="margin-bottom:0px;" >
<field
class="oe_inline"
name="l10n_mx_edi_error"
readonly="1" />
</div>
</xpath>
<xpath expr="//sheet/h1" position="after">
<h4 attrs="{'invisible': [('number','=',False)]}" collspan="2">
<field string="Fiscal Number" name="cfdi_id" readonly="1"/>
......
......@@ -34,8 +34,7 @@
<field name="show_glnnumber" invisible="True"/>
<field name="show_suppliernumber" invisible="True"/>
<field name="show_edi" invisible="True"/>
<field name="cfdi_adenda" options="{'no_create': True,'no_open':True}"/>
<field name="cfdi_adenda_code" invisible="1"/>
<field name="cfdi_adenda_ids" widget="many2many_tags" options="{'no_create': True,'no_open':True}"/>
<field name="gln_number" attrs="{'required':[('show_glnnumber', '=', True)], 'invisible':[('show_glnnumber', '=', False)]}"/>
<field name="supplier_number" attrs="{'required':[('show_suppliernumber', '=', True)], 'invisible':[('show_suppliernumber', '=', False)]}"/>
<field name="edi" attrs="{'required':[('show_edi', '=', True)], 'invisible':[('show_edi', '=', False)]}"/>
......
# -*- coding: utf-8 -*-
from . import account_invoice_refund
from . import wizard_multi_charts_accounts
# -*- coding: utf-8 -*-
from openerp import api, models
class WizardMultiChartsAccounts(models.TransientModel):
_inherit = "wizard.multi.charts.accounts"
@api.model
def _prepare_all_journals(self, chart_template_id, acc_template_ref, company_id):
"""Setup purchase and purchase refund journals as require SAT validation for
mexican companies"""
res = super(WizardMultiChartsAccounts, self)._prepare_all_journals(
chart_template_id, acc_template_ref, company_id,
)
mexico = self.env.ref("base.mx")
company = self.env["res.company"].browse(company_id)
if company.country_id == mexico:
for journal in res:
if journal["type"] in ("sale", "sale_refund"):
journal["update_posted"] = True
journal["sign_sat"] = True
return res