Commit da07e089 authored by agb80's avatar agb80

feat(account.invoice): add template for CFDI 3.3

Several properties added on account.invoice class that allow export
account.invoice information as needed for create CFDI 3.3.

Closes #3
parent 3293fa31
......@@ -2,14 +2,25 @@
<openerp>
<data noupdate="1">
<record id="ir_attachment_facturae_mx_config_invoice" model="ir.attachment.facturae.mx.config">
<record id="ir_attachment_facturae_mx_config_account_invoice_32" 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="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="email_template_template_facturae_mx"/>
</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="email_template_template_facturae_mx"/>
</record>
</data>
</openerp>
# -*- coding: utf-8 -*-
from . import account_invoice
from . import account_invoice_line
from . import email_template
......@@ -34,19 +34,112 @@ class AccountInvoice(models.Model):
])
return fname
@property
def serie(self):
"""Return serie for display on CFDI
"""
self.ensure_one()
# Get interpolated sequence
if self.journal_id.sequence_id.prefix:
d = self.env['ir.sequence']._interpolation_dict_context()
serie = self.journal_id.sequence_id._interpolate(
self.journal_id.sequence_id.prefix, d,
)
return self._sanitize(serie).strip()
@property
def folio(self):
"""Return folio for display on CFDI
"""
self.ensure_one()
folio = self._sanitize(self.internal_number)
return folio.replace(self.serie, '').strip()
@property
def formapago(self):
"""Return payment type for display on CFDI
"""
self.ensure_one()
try:
code = self.payment_type[0].code
except IndexError:
code = '99'
return code
@property
def descuento(self):
self.ensure_one()
discount = 0.0
for line in self.invoice_line:
discount += line.descuento
return discount
@property
def subtotal(self):
self.ensure_one()
subtotal = 0.0
for line in self.invoice_line:
subtotal += line.importe
return subtotal
@property
def impuestos(self):
"""Return computed taxes for display on CFDI
"""
self.ensure_one()
tax_grouped = {}
taxes = {
'traslados': [],
'retenciones': [],
'total_traslados': 0.0,
'total_retenciones': 0.0,
}
for line in self.invoice_line:
for tax in line.export_invoice_line_for_xml()['taxes']:
# Taxes with none type must be excluded from total taxes
if tax['type'] == 'none':
continue
# Mimic logic from compute function in account.invoice.tax
# object to group taxes from invoice lines and be able to get
# the same result for display on CFDI
if self.type in ('out_invoice', 'in_invoice'):
tax['account_id'] = (
tax['account_collected_id'] or line.account_id.id
)
tax['analytic_id'] = tax['account_analytic_collected_id']
else:
tax['account_id'] = (
tax['account_paid_id'] or line.account_id.id
)
tax['analytic_id'] = tax['account_analytic_paid_id']
key = (tax['id'], tax['account_id'], tax['analytic_id'])
if key not in tax_grouped:
tax_grouped[key] = tax
else:
tax_grouped[key]['amount'] += tax['amount']
tax_grouped[key]['base'] += tax['base']
# Classify taxes for CFDI
for dummy, tax in tax_grouped.iteritems():
if tax['amount'] >= 0:
taxes['traslados'].append(tax)
taxes['total_traslados'] += tax['amount']
else:
taxes['retenciones'].append(tax)
taxes['total_retenciones'] += tax['amount']
return taxes
datetime = fields.Datetime(compute='_compute_datetime', store=True)
date_invoice_cancel = fields.Datetime(
'Date Invoice Cancelled', readonly=True, copy=False,
help='If the invoice is cancelled, save the date'
' when was cancel',
)
rate = fields.Float(
'Type of Change', readonly=True, copy=False,
help='Rate used in the date of invoice',
)
cfdi_folio_fiscal = fields.Char(
'CFD-I Folio Fiscal', size=64, copy=False,
compute='_compute_cfdi_folio_fiscal',
'CFD-I Folio Fiscal', compute='_compute_cfdi_folio_fiscal',
help='Folio used in the electronic invoice',
)
cfdi_id = fields.Many2one(
......@@ -113,5 +206,129 @@ class AccountInvoice(models.Model):
# Maybe a third test could review state, but since
# button cancel only is displayed in open state, we decided to not
# used third test
if account_invoice.journal_id.sign_sat and account_invoice.cfdi_folio_fiscal:
if (
account_invoice.journal_id.sign_sat and
account_invoice.cfdi_folio_fiscal
):
account_invoice.cfdi_id.action_cancel()
@staticmethod
def _sanitize(text):
# Chars not allowed on CFDI
chars = '\\`*_{}[]()>#+-.!$/' # noqa
# Replace all characters based on best performance answer as found on:
# https://stackoverflow.com/a/27086669/2817675
for c in chars:
if c in text:
text = text.replace(c, ' ')
return text
class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line'
@property
def importe(self):
"""Return computed total line for display on CFDI
"""
self.ensure_one()
return self.export_invoice_line_for_xml()['importe']
@property
def descuento(self):
"""Property that computes the discount amount in currency for CFDI XML
invoice view
"""
self.ensure_one()
return self.importe - self.export_invoice_line_for_xml()['total']
@property
def impuestos(self):
"""Return computed taxes for display on CFDI
"""
self.ensure_one()
taxes = {
'traslados': [],
'retenciones': [],
}
res = self.export_invoice_line_for_xml()
for tax in res['taxes']:
if tax['amount'] >= 0:
taxes['traslados'].append(tax)
else:
taxes['retenciones'].append(tax)
return taxes
@property
def valorunitario(self):
"""Return computed price unit for display on CFDI
"""
self.ensure_one()
return self.export_invoice_line_for_xml()['price_unit']
def export_invoice_line_for_xml(self):
"""Computes all values needed for export account.invoice.line as CFDI
"""
def process_tax(tax):
"""Helper function to populate extra values needed for display
taxes on CFDI representation from account.invoice
@param tax: tax values computed from original compute_all function
on account.invoice.tax object
@type tax: dictionary
@return: dictionary populated with all values needed for tax
excluding IEPS tax if partner is not IEPS subjected
@rtype: dictionary or None
"""
tax_record = self.env['account.tax'].browse(tax['id'])
tax_group = tax_record.tax_category_id
# IEPS tax only must be included when partner is ieps subjected
if tax_group.name == 'IEPS' and not partner.ieps_subjected:
return
tax['base'] = currency.round(tax['price_unit'] * self.quantity)
tax['group'] = tax_group.name
tax['type'] = tax_record.type
tax['TasaOCuota'] = tax_record.amount
return tax
currency = self.invoice_id.currency_id
price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
partner = self.invoice_id.partner_id
# Compute taxes using original compute_all function from
# account.invoice.tax to get same result for CFDI display
res = self.invoice_line_tax_id.compute_all(
price, self.quantity, product=self.product_id, partner=partner,
)
taxes = []
# Get price_unit with out include taxes
taxes_list = iter(res['taxes'])
tax = next(taxes_list)
price_unit_whitout_taxes = tax['price_unit']
# Iterate taxes and append to the new tax list as needed
while True:
try:
tax = process_tax(tax)
if tax:
taxes.append(tax)
tax = next(taxes_list)
except StopIteration:
base_with_taxes = tax['base']
price_unit_with_taxes = tax['price_unit']
break
# Overrides original taxes with the list computed by us
res['taxes'] = taxes
if partner.ieps_subjected:
res['price_unit'] = (
price_unit_whitout_taxes / (1 - (self.discount or 0.0) / 100)
)
else:
res['price_unit'] = (
price_unit_with_taxes / (1 - (self.discount or 0.0) / 100)
)
res['total'] = currency.round(base_with_taxes)
# Compute subtotal without taxes and discounts
# Delay rounding up to last moment to avoid differences
res['importe'] = currency.round(res['price_unit'] * self.quantity)
res['price_unit'] = currency.round(res['price_unit'])
return res
# -*- coding: utf-8 -*-
from openerp.addons import decimal_precision as dp
from openerp.osv import fields, orm
class account_invoice_line(orm.Model):
_inherit = 'account.invoice.line'
def _get_price_unit_without_taxes(
self, cr, uid, ids, fname, arg, context=None,
):
# TODO: Maybe this field must be in the account module
# as this function is almost copy paste from this one
# addons/account/account_invoice.py#L1455
res = {}
tax_obj = self.pool.get('account.tax')
cur_obj = self.pool.get('res.currency')
for line in self.browse(cr, uid, ids):
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
taxes = tax_obj.compute_all(
cr, uid, line.invoice_line_tax_id, price,
1, product=line.product_id,
partner=line.invoice_id.partner_id,
)
res[line.id] = taxes['total']
if line.invoice_id:
cur = line.invoice_id.currency_id
res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
return res
_columns = {
'price_unit_without_taxes': fields.function(
_get_price_unit_without_taxes,
type='float', string='Unit Price (W/o taxes)',
digits_compute=dp.get_precision('Product Price'), store=True,
),
}
......@@ -29,7 +29,7 @@ class EmailTemplate(models.Model):
data = {
'account': 'email_template_edi_invoice',
'portal_sale': 'email_template_edi_invoice',
'l10n_mx_ir_attachment_facturae': 'email_template_template_facturae_mx',
'l10n_mx_ir_attachment_facturae': 'email_template_template_facturae_mx', # noqa
}
values = super(EmailTemplate, self).generate_email(
cr, uid, template_id, res_id, context=context,
......@@ -41,7 +41,7 @@ class EmailTemplate(models.Model):
reference_ids.append(
ir_model_data.get_object_reference(
cr, uid, module, name,
)[1]
)[1],
)
except ValueError:
# Template not installed so we catch the error
......
<?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/cfdi32.txt</field>
<field name="tml_source">file</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>
<field name="model">account.invoice</field>
<field name="report_name">account.invoice.cfdi.33</field>
<field name="report_rml">l10n_mx_facturae/templates/cfdi33.txt</field>
<field name="tml_source">file</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>
<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_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])]" />
<field name="parser_state">default</field>
</record>
</data>
<!-- PDF invoice report definition -->
<data noupdate="1">
<!-- PDF invoice report definition -->
<record id="l10n_mx_facturae_template" model="report.templates.aeroo">
<field name="name">Defaut Invoice Report teplate</field>
<field name="model">account.invoice</field>
......@@ -22,25 +65,5 @@
<field name="parser_state">loc</field>
<field name="tml_source">file</field>
</record>
<!-- XML invoice report definition -->
<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/invoice.txt</field>
<field name="tml_source">file</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>
</data>
</openerp>
......@@ -58,10 +58,14 @@
<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}"
<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>
......
<?xml version="1.0" encoding="UTF-8"?>
{% python setLang(o.user_id.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"
xmlns:cfdi="http://www.sat.gob.mx/cfd/3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
Version="3.3"
{% if o.journal_id.sequence_id.prefix %}
Serie="$o.serie"
{% end %}
Folio="$o.folio"
Fecha="${ format_datetime(o.datetime, '%Y-%m-%dT%H:%M:%S') }"
Sello="@"
NoCertificado="@"
Certificado="@"
FormaPago="$o.formapago"
{% if o.comment %}CondicionesDePago="$o.comment"{% end %}
SubTotal="${ '{0:.2f}'.format(o.subtotal) }"
{% if countif(o.invoice_line, [('discount', '>', 0.0)]) %}
Descuento="${ '{0:.2f}'.format(o.descuento) }"
{% end %}
Moneda="${ o.currency_id.name }"
{% if o.currency_id.name != 'MXN' and o.currency_id.name != 'XXX'%}
TipoCambio="${ o.currency_rate }"
{% end %}
Total="${ '{0:.2f}'.format(o.amount_total) }"
{% choose o.type %}
{% when 'out_invoice' %}TipoDeComprobante="I"{% end %}
{% when 'out_refund' %}TipoDeComprobante="E"{% end %}
{% end %}
{% choose o.payment_term.payment_method_id.code %}
{% when False %}MetodoPago="PUE"{% end %}
{% otherwise %}MetodoPago="$o.payment_term.payment_method_id.code"{% end %}
{% end %}
LugarExpedicion="${ o.company_id.zip }" >
{#{% if o.cfdi_related_type %}
<cfdi:CfdiRelacionados
TipoRelacion="{{ o.cfdi_related_type }}">
{% for cfdi in o.cfdi_related %}
<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="${ o.company_id.partner_id.vat_split }"{% end %}
{% end %}
Nombre="${ o.company_id.name }"
RegimenFiscal="$o.company_id.account_position_id.code"/>
<cfdi:Receptor
{% choose o.partner_id.vat_split %}
{% when False %}${ validationerror(_('Missing VAT number for receiver')) }{% end %}
{% otherwise %}Rfc="${ o.partner_id.vat_split }"{% end %}
{% end %}
{% if o.partner_id.vat_split not in ('XAXX010101000', 'XEXX010101000') %}
Nombre="$o.partner_id.name"
{% end %}
{% if o.partner_id.country_id and o.partner_id.country_id.code != 'MX' %}
ResidenciaFiscal="$o.partner_id.country_id.code3"
NumRegIdTrib="$o.partner_id.vat"
{% 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
{% choose line.product_id.name %}
{% when False %}ClaveProdServ="01010101"{% end %}
{% otherwise %}
{% choose value_of(line.product_id.cfdi_product_service_id.code) %}
{% when 'None' %}${ validationerror(_('Missing SAT code for product: {p}').format(p=line.product_id.name)) }{% end %}
{% otherwise %}ClaveProdServ="$line.product_id.cfdi_product_service_id.code"{% end %}
{% end %}
{% if line.product_id.code %}NoIdentificacion="$line.product_id.code"{% end %}
{% end %}
{% end %}
Cantidad="${ '{0:.6f}'.format(line.quantity) }"
{% choose line.uos_id.name %}
{% when False %}${ validationerror(_('Please select a unit of measure for line: {l}').format(l=line.name)) }{% end %}
{% otherwise %}
{% choose line.uos_id.cfdi_unit_measure_id.code %}
{% when False %}${ validationerror(_('Missing SAT code for unit: {u}').format(u=line.uos_id.name)) }{% end %}
{% otherwise %}ClaveUnidad="$line.uos_id.cfdi_unit_measure_id.code"{% end %}
{% end %}
{% end %}
{% end %}
Descripcion="$line.name"
ValorUnitario="${ '{0:.2f}'.format(line.valorunitario) }"
Importe="${ '{0:.2f}'.format(line.importe) }"
{% if line.discount %}Descuento="${ '{0:.2f}'.format(line.descuento) }"{% end %} >
{% with taxes=line.impuestos %}
{% if taxes %}
<cfdi:Impuestos>
{% if taxes.traslados %}
<cfdi:Traslados>
{% for tax in taxes.traslados %}
<cfdi:Traslado
Base="${ '{0:.2f}'.format(tax.base) }"
{% 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 %}
{% when 'percent' %}TipoFactor="Tasa"{% end %}
{% when 'fixed' %}TipoFactor="Cuota"{% end %}
{% when 'none' %}TipoFactor="Exento"{% end %}
{% end %}
{% if tax.type != 'none' %}
TasaOCuota="${ '{0:.6f}'.format(tax.TasaOCuota) }"
Importe="${ '{0:.2f}'.format(tax.amount) }"
{% end %}/>
{% end %}
</cfdi:Traslados>
{% end %}
{% if taxes.retenciones %}
<cfdi:Retenciones>
{% for tax in taxes.retenciones %}
<cfdi:Retencion
Base="${ '{0:.2f}'.format(tax.base) }"
{% choose tax.group %}
{% when 'ISR' %}Impuesto="001"{% end %}
{% when 'IVA' %}Impuesto="002"{% end %}
{% otherwise %}${ validationerror(_('Missing SAT code for tax: {t}').format(t=tax.name)) }{% end %}
{% end %}
{% choose tax.type %}
{% when 'percent' %}TipoFactor="Tasa"{% end %}
{% when 'fixed' %}TipoFactor="Cuota"{% end %}
{% end %}
TasaOCuota="${ '{0:.6f}'.format(tax.TasaOCuota) }"
Importe="${ '{0:.2f}'.format(abs(tax.amount)) }" />
{% end %}
</cfdi:Retenciones>
{% end %}
</cfdi:Impuestos>
{% end %}
{% end %}
</cfdi:Concepto>
{% end %}
</cfdi:Conceptos>
{% with taxes=o.impuestos %}
{% if taxes %}
<cfdi:Impuestos
TotalImpuestosTrasladados="${ '{0:.2f}'.format(taxes.total_traslados) }"
TotalImpuestosRetenidos="${ '{0:.2f}'.format(abs(taxes.total_retenciones)) }">
{% if taxes.traslados %}
<cfdi:Traslados>
{% for tax in taxes.traslados %}
<cfdi:Traslado
{% choose tax.group %}
{% 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 %}
{% when 'percent' %}TipoFactor="Tasa"{% end %}
{% when 'fixed' %}TipoFactor="Cuota"{% end %}
{% end %}
TasaOCuota="${ '{0:.6f}'.format(tax.TasaOCuota) }"
Importe="${ '{0:.2f}'.format(tax.amount) }" />
{% end %}
</cfdi:Traslados>
{% end %}
{% if taxes.retenciones %}
<cfdi:Retenciones>
{% for tax in taxes.retenciones %}
<cfdi:Retencion
{% choose tax.group %}
{% when 'ISR' %}Impuesto="001"{% end %}
{% when 'IVA' %}Impuesto="002"{% end %}
{% otherwise %}${ validationerror(_('Missing SAT code for tax: {t}').format(t=tax.name)) }{% end %}
{% end %}
Importe="${ '{0:.2f}'.format(abs(tax.amount)) }" />
{% end %}
</cfdi:Retenciones>
{% end %}
</cfdi:Impuestos>
{% end %}
{% end %}
<cfdi:Complemento/>
</cfdi:Comprobante>
......@@ -21,6 +21,7 @@ def setup(scenario):
# Set user permissions
scenario.context.user = add_user(scenario)
@given('I have a customer')
def have_customer(step):
partner_obj = step.context.env['res.partner']
......@@ -33,7 +34,7 @@ def have_customer(step):
values = {
'name': partner[0],
'street': partner[1],
'10n_mx_street3': partner[2],
'10n_mx_street3': partner[2],
'l10n_mx_city2': partner[3],
'city': partner[4],
'zip': partner[6],
......@@ -44,7 +45,8 @@ def have_customer(step):
# Review if there is a country with the given name
# if not create it
country = country_obj.sudo(user_id).search(
[('name', '=', country_name)], limit=1)
[('name', '=', country_name)], limit=1,
)
if country:
country_id = country[0].id
values.update({'country_id': country_id})
......@@ -55,15 +57,18 @@ def have_customer(step):
# Review if there is a state with the given name and the given country
# if not create it
state = state_obj.sudo(user_id).search(
[('name', '=', state_name),
('country_id', '=', country_id)], limit=1)
[
('name', '=', state_name),
('country_id', '=', country_id),
], limit=1,
)
if state:
values.update({'state_id': state[0].id})
else:
state_values = {
'name': state_name,
'code': state_name,
'country_id': country_id
'country_id': country_id,
}
state_id = state_obj.sudo(user_id).create(state_values).id
values.update({'state_id': state_id})
......@@ -72,13 +77,14 @@ def have_customer(step):
# Add partner id in step context
step.context.partner = partner
@given('customer VAT number is MXAAA010101AAA')
def have_customer_vat(step):
partner = step.context.partner
# Set vat number according to our step
partner.vat = 'MXBBB010101AAA'
@when('I create a sales invoice for customer')
def create_invoice(step):
account_invoice_obj = step.context.env['account.invoice']
......@@ -97,7 +103,8 @@ def create_invoice(step):
}
invoice = account_invoice_obj.sudo(user_id).create(values)
step.context.invoice = invoice
@when('invoice line is')
def have_lines(step):
invoice_line_obj = step.context.env['account.invoice.line']
......@@ -113,13 +120,15 @@ def have_lines(step):
}
# Create lines
invoice_line_obj.sudo(user_id).create(values)
@when('validate invoice')
def validate_invoice(step):
invoice = step.context.invoice
invoice.invoice_open()
@then("I expect to have a XML and a PDF with same name, and a UUID")
@then('I expect to have a XML and a PDF with same name, and a UUID')
def result(step):
attachment_obj = step.context.env['ir.attachment']
invoice = step.context.invoice
......@@ -134,16 +143,19 @@ def result(step):
for index, attachment in enumerate(attachments):
if pattern.match(attachment.name):
break
assert (index + 1) != len(attachments), '{type} file does not exist'.format(
type=re.sub('.*\.', '', file_type).upper())
assert (
(index + 1) != len(attachments),
'{type} file does not exist'.format(
type=re.sub('.*\.', '', file_type).upper(),
),
)
# Test attachments have the same name
names = set([os.path.splitext(x.name)[0] for x in attachments])
assert len(names) == 1, ''.join(
['Name files must be the same ', str(names)])
['Name files must be the same ', str(names)])
assert invoice.cfdi_folio_fiscal, 'Invoice without UUID'
def add_company(scenario):
company_obj = scenario.context.env['res.company']
state_obj = scenario.context.env['res.country.state']
......@@ -160,11 +172,12 @@ def add_company(scenario):
l10n_mx_city2=Hacienda Del Rosario, city=Puebla, state=Puebla,
zip=72000, country=Mexico, vat=MXAAA010101AAA
"""
dict_company_values = dict(x.split('=') for x in company_values.split(', '))
dict_company_values = dict(x.split('=') for x in company_values.split(', ')) # noqa
# Review if there is a country with the given name
# if not create it
country = country_obj.search(
[('name', '=', dict_company_values['country'])], limit=1)
[('name', '=', dict_company_values['country'])], limit=1,
)
if country:
country_id = country[0].id
dict_company_values.update({'country_id': country_id})
......@@ -175,15 +188,18 @@ def add_company(scenario):
# Review if there is a state with the given name and the given country
# if not create it
state = state_obj.search(
[('name', '=', dict_company_values['state']),
('country_id', '=', country_id)], limit=1)
[
('name', '=', dict_company_values['state']),
('country_id', '=', country_id),
], limit=1,
)
if state:
dict_company_values.update({'state_id': state[0].id})
else:
state_values = {
'name': dict_company_values['state'],
'code': dict_company_values['state'],
'country_id': country_id
'country_id': country_id,
}
state_id = state_obj.create(state_values).id
dict_company_values.update({'state_id': state_id})
......@@ -191,6 +207,7 @@ def add_company(scenario):
company = company_obj.create(dict_company_values)
return company
def set_test_certificates(scenario):
certificate_obj = scenario.context.env['res.company.facturae.certificate']
# I hope cer and key files are placed in the same basedir
......@@ -212,36 +229,44 @@ def set_test_certificates(scenario):
'company_id': company_id,
'cetificate_file': cetificate_file,
'certificate_key_file': certificate_key_file,
'certificate_password': certificate_password
'certificate_password': certificate_password,
}
certificate = certificate_obj.create(values)
else:
certificate.certificate_file = cetificate_file
certificate.certificate_key_file = certificate_key_file
certificate.certificate_password = certificate_password
# Generate new pem files
# Generate new pem files
certificate.get_certificate_info()
def add_journal(scenario):
account_journal_obj = scenario.context.env['account.journal']
journal = account_journal_obj.search([('type', '=', 'sale')], limit=1)
return journal
def add_account(scenario):
account_account_obj = scenario.context.env['account.account']
company_id = scenario.context.company.id
account = account_account_obj.search(
[('company_id', '=', company_id), ('type', '=', 'receivable')],
limit=1)[0]
limit=1,
)[0]
return account
def add_user(scenario):
res_users_obj = scenario.context.env['res.users']
group_obj = scenario.context.env['res.groups']
group = group_obj.search(
[('name', '=', 'Invoicing & Payments')], limit=1)
user = res_users_obj.search([
('groups_id', 'in', (group.id)),
('name', '!=', 'admin')], limit=1)
assert user != False, 'No user in Invoicing & Payments group'
[('name', '=', 'Invoicing & Payments')], limit=1,
)
user = res_users_obj.search(
[
('groups_id', 'in', (group.id)),
('name', '!=', 'admin'),
], limit=1,
)
assert user is not False, 'No user in Invoicing & Payments group'
return user
......@@ -19,6 +19,7 @@ def connect_database(scenario):
scenario.context.cr, scenario.context.uid, {},
)
@after.each_scenario
def disconnect_database_and_rollback(scenario):
# Copy from odoo unitest
......
......@@ -17,9 +17,6 @@
placeholder="Fiscal Number" readonly="1"/>
</h4>
</xpath>
<xpath expr="//sheet[@string='Invoice']/notebook/page[@string='Invoice Lines']/group/field[@name='payment_term']" position="after">
<field name="rate"/>
</xpath>
<xpath expr="//field[@name='partner_id']" position="replace">
<field string="Customer" name="partner_id"
on_change="onchange_partner_id(type,partner_id,date_invoice,payment_term, partner_bank_id,company_id)"
......
# -*- coding: utf-8 -*-
from setuptools import find_packages, setup
import ast
import os
from setuptools import find_packages, setup
MANIFEST_NAMES = ('__openerp__.py', '__manifest__.py', '__terp__.py')
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment