Merge lp:~atin81/openerp-mexico-localization/pac-facturalo into lp:openerp-mexico-localization/7.0

Proposed by Agustín
Status: Needs review
Proposed branch: lp:~atin81/openerp-mexico-localization/pac-facturalo
Merge into: lp:openerp-mexico-localization/7.0
Diff against target: 2852 lines (+2763/-0)
15 files modified
l10n_mx_facturae_pac_facturalo/__init__.py (+29/-0)
l10n_mx_facturae_pac_facturalo/__openerp__.py (+77/-0)
l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_pac_facturalo.xml (+24/-0)
l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_pac_facturalo_report.xml (+18/-0)
l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_seq.xml (+37/-0)
l10n_mx_facturae_pac_facturalo/i18n/es_MX.po (+275/-0)
l10n_mx_facturae_pac_facturalo/i18n/l10n_mx_facturae_pac_facturalo.pot (+270/-0)
l10n_mx_facturae_pac_facturalo/invoice.py (+560/-0)
l10n_mx_facturae_pac_facturalo/ir_attachment_facturae.py (+40/-0)
l10n_mx_facturae_pac_facturalo/ir_sequence_approval.py (+43/-0)
l10n_mx_facturae_pac_facturalo/params_pac.py (+45/-0)
l10n_mx_facturae_pac_facturalo/pyxmldsig.py (+578/-0)
l10n_mx_facturae_pac_facturalo/report/__init__.py (+25/-0)
l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py (+266/-0)
l10n_mx_facturae_pac_facturalo/report/account_print_invoice.rml (+476/-0)
To merge this branch: bzr merge lp:~atin81/openerp-mexico-localization/pac-facturalo
Reviewer Review Type Date Requested Status
Nhomar - Vauxoo Needs Fixing
Review via email: mp+198988@code.launchpad.net

Description of the change

Agregamos un módulo para realizar la facturación electrónica con el pac factura-lo.mx.

Falta hacer la cancelación de las facturas, pero subimos por anticipado el código para obtener retroalimentación departe de ustedes sobre lo que estamos desarrollando.

De momento los todo's y preguntas que tenemos son las siguientes:

1.- De que manera podríamos poner a funcionar los dos módulos al mismo tiempo, creo que podría llegar a ser útil en algún momento para las empresas contar con dos servicios de facturación, uno principal y otro de respaldo, pero actualmente no es posible tener los dos módulos funcionando.

2.- Nosotros en el módulo sobreescribimos el reporte de invoices que viene por defecto en OpenERP, de acuerdo a nuestra experiencia, contar con dos reportes es confuso para el usuario, sobre todo porque uno de ellos jamás se usa. Porqué no dejar únicamente un sólo reporte?

To post a comment you must log in.
Revision history for this message
Moisés López - http://www.vauxoo.com (moylop260) wrote :

Hola Agustin,
Te respondo entre líneas.

El 13 de diciembre de 2013 13:05, Agustín <email address hidden> escribió:

> Agustín has proposed merging
> lp:~atin81/openerp-mexico-localization/pac-facturalo into
> lp:openerp-mexico-localization/7.0.
>
> Requested reviews:
> OpenERP Mexico Maintainer (openerp-mexico-maintainer)
>
> For more details, see:
>
> https://code.launchpad.net/~atin81/openerp-mexico-localization/pac-facturalo/+merge/198988
>
> Agregamos un módulo para realizar la facturación electrónica con el pac
> factura-lo.mx.
>

¡Excelente!

> Falta hacer la cancelación de las facturas, pero subimos por anticipado el
> código para obtener retroalimentación departe de ustedes sobre lo que
> estamos desarrollando.
>

Ok, perfecto.

> De momento los todo's y preguntas que tenemos son las siguientes:
>
> 1.- De que manera podríamos poner a funcionar los dos módulos al mismo
> tiempo, creo que podría llegar a ser útil en algún momento para las
> empresas contar con dos servicios de facturación, uno principal y otro de
> respaldo, pero actualmente no es posible tener los dos módulos funcionando.
>

Para hacer funcionar 2 módulos al mismo tiempo de PAC's solo hay que hacer
pruebas, ya que está toda la arquitectura para que así se pueda hacer (pero
no se ha probado, todo es teoría), teniendo cuidado de definir los "type's"
en la secuencia y que coincida (como si fuera un driver, pues).
A lo mejor lo único que faltaría es tener un módulo de PAC dummy, para
agregar los campos que se utilizarán en común (analizando el caso).

>
> 2.- Nosotros en el módulo sobreescribimos el reporte de invoices que viene
> por defecto en OpenERP, de acuerdo a nuestra experiencia, contar con dos
> reportes es confuso para el usuario, sobre todo porque uno de ellos jamás
> se usa. Porqué no dejar únicamente un sólo reporte?
>

Aquí el tema es más profundo, lo que pasa, es que los módulos tienen que
cumplir con:
Multi-Company
Multi-Moneda
Multi-Localización
Multi-PAC
Multi-Usuario
Multi-*

En el caso de Multi-Localización, si todas las localizaciónes hiciéramos lo
que propones, todas sobre-escribirían el mismo reporte, y al final se
corrompería el reporte que se quiere mostrar. ¿Me explico?
Es decir, imagínate que yo uso el sistema en Venezuela y en México en una
misma base de datos multi-company y multi-localización, partiendo del
principio de que Venezuela no imprime facturación electrónica mexicana, no
deberían de verse perjudicados por este tema. Espero haberme explicado.

Pero, lo que tú dices es muy cierto, puede prestarse a confusión tener 2
reportes, aquí lo puedes solucionar, ocultando reportes, por medio de
permisos, a ciertos grupos o usuarios.

--
Moisés López Calderón
Vauxoo - OpenERP's Gold Partner
mobile: (+521) 477-752-22-30
Tel: (+52) 477-773-33-46
skype: moylop260
web: http://www.vauxoo.com
twitter: @moylop260
            @vauxoo

Revision history for this message
Nhomar - Vauxoo (nhomar) wrote :
Download full text (3.5 KiB)

Hola Agustin.

Tengo varias dudas, pero ante todo muchas gracias por el aporte.

Dudas Operativas:

1.- No veo a factura-lo aquí:
 http://www.sat.gob.mx/sitio_internet/asistencia_contribuyente/principiantes/comprobantes_fiscales/66_19264.html

Debido a la apertura del software y su fácil auditoría debemon colocar los nombres "Registrados" en la página del SAT, ¿crees que nos ayudas puedas apoyar con colocar eso por favor?

2.- Al momento de revisar el PAC con los dominios suministrados en los parámetros de conexión tenía un virus la página, ¿Me comentas cómo es eso posible? creo que como usuarios Linux pensar en Virus es nocivo para nuestra Salud </joke>

3.- Por favor los créditos no deben ir en el description del __openerp__.py, para eso es el campo website en el descriptor, lo puedes apuntar a tu página y los correos ya están en los comentarios del código junto con la licencia.

Dudas Técnicas:

3.- l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_pac_facturalo.xml debería estar en "demo" no en "data" en todo caso debería configurarse con un Wizard al momento de colocar la data, no cableada como se encuentra en éste momento. ¿Por qué? sucede que los valores necesarios para la operación en una base de datos para producción por buenas prácticas no deberían pre-configurarse de forma errónea. Te puedes basar en los wizards ir.config para mayor información, o verifica los wizards de configuración del módulo de pacsf.

4.- Lo mismo para l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_seq.xml son datos contables que afectarían en 1 click una base de datos de producción.

5.- Hay código comentado, en python eso no se hace ;-) (no es PHP) puedes removerlo y comentar en el commit el TODO, después puedes volverlo a colocar solo leyendo un bzr diff.

6.- Yo sé que nuestros módulos tienen comentarios en español, pero es debido a que son nuestra evolución y no han sido limpiados, me apoyas colocandolos en Inglés, de ésta manera nos ahorramos muchísimos errores con unicode y podemos usar tu caso de código como ejemplo cuando discutimos propuestas al core oficial.

7.- Sucede lo mismo con las variables (pero éstas déjalas así) las variables en español son inconsistentes es decir, leer browse.algoenespanol.del.ano suena feo verdad? siempre es buena práctica colocar las variables en inglés. NOTA. Si tienes esto en producción éste cambio específico lo puedes hacer en un branch diferente al que uses hasta que quede mergeado.

8.- Hay muchísimo código copiado y pegado, creo que tenemos que hacer una limpieza antes de éste merge para poder unificar los criterios y darle respuesta a tus preguntas.

9.- El módulo no tiene un solo test unitario, te comento que estabilizar los otros nos ha costado un monton que marque verde en los servidores de integración contínua, veo sumamante riesgoso colocar un módulo sin tests "Tienes en planes trabajar en ésto".

10.- Sobre el reporte nosotros hemos hecho varias iteraciones, podemos discutirlas en un bug sobre ese tema específico (sin trancar esta MP por ese concepto esppecífico) ya que tenemos que diseñarlo.

Sobre tus dudas.

11.- Si nos propones alguna clase de diseño podemos hacer un sprint, nostros estamos plan...

Read more...

review: Needs Fixing
Revision history for this message
Agustín (atin81) wrote :
Download full text (6.2 KiB)

Antes que nada muchas gracias por la revisión Nhomar. Trataré de
responder lo mejor que pueda a todas tus dudas.

On 14/12/13 02:47, Nhomar - Vauxoo wrote:
> Review: Needs Fixing
>
> Hola Agustin.
>
> Tengo varias dudas, pero ante todo muchas gracias por el aporte.
>
> Dudas Operativas:
>
> 1.- No veo a factura-lo aquí:
> http://www.sat.gob.mx/sitio_internet/asistencia_contribuyente/principiantes/comprobantes_fiscales/66_19264.html
>
> Debido a la apertura del software y su fácil auditoría debemon colocar los nombres "Registrados" en la página del SAT, ¿crees que nos ayudas puedas apoyar con colocar eso por favor?
Nosotros estamos trabajando en conjunto con la empresa de factura-lo.mx
en la realización de este código y para ofrecer la solución a nuestros
clientes. Voy a ponerme en contacto con ellos para ver cual es el nombre
que tienen registrado.
>
> 2.- Al momento de revisar el PAC con los dominios suministrados en los parámetros de conexión tenía un virus la página, ¿Me comentas cómo es eso posible? creo que como usuarios Linux pensar en Virus es nocivo para nuestra Salud </joke>
Mismo comentario anterior, no se exactamente que problema tuvieron en
días pasados con su web.
>
> 3.- Por favor los créditos no deben ir en el description del __openerp__.py, para eso es el campo website en el descriptor, lo puedes apuntar a tu página y los correos ya están en los comentarios del código junto con la licencia.
No intentaban ser créditos sino más bien lugares donde acudir en caso de
dudas, pero los vamos a retirar para dejar sólo la página web.
>
> Dudas Técnicas:
>
> 3.- l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_pac_facturalo.xml debería estar en "demo" no en "data" en todo caso debería configurarse con un Wizard al momento de colocar la data, no cableada como se encuentra en éste momento. ¿Por qué? sucede que los valores necesarios para la operación en una base de datos para producción por buenas prácticas no deberían pre-configurarse de forma errónea. Te puedes basar en los wizards ir.config para mayor información, o verifica los wizards de configuración del módulo de pacsf.
>
> 4.- Lo mismo para l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_seq.xml son datos contables que afectarían en 1 click una base de datos de producción.
Ok, los moveremos a demo y consideraremos la realización del wizard.
>
> 5.- Hay código comentado, en python eso no se hace ;-) (no es PHP) puedes removerlo y comentar en el commit el TODO, después puedes volverlo a colocar solo leyendo un bzr diff.
Intentamos empezar con un módulo nuevo pero no pudimos hacer que
funcionara por el desconocimiento del trabajo interno del módulo. Había
datos que no se creaban y cosas así. Así que decidimos hacer copy/paste
del módulo de pac_sf y de ahí ir refactorizando las funciones como las
íbamos necesitando. Esa es la razón del código comentado, irá
desapareciendo conforme el módulo vaya siendo pulido.
>
> 6.- Yo sé que nuestros módulos tienen comentarios en español, pero es debido a que son nuestra evolución y no han sido limpiados, me apoyas colocandolos en Inglés, de ésta manera nos ahorramos muchísimos errores con unicode y podemos usar tu caso de códi...

Read more...

Revision history for this message
Agustín (atin81) wrote :

LinkedIn
------------

Me gustaría añadirte a mi red profesional en LinkedIn.

-Agustín

Agustín Cruz
Director de Tecnologías de la Información en Fedrojesa SA de CV
Puebla de Zaragoza y alrededores, México

Confirma que conoces a Agustín Cruz:
https://www.linkedin.com/e/sqgs3h-ht1ot3ke-6z/isd/5852814049212915712/cUZVZuw1/?hs=false&tok=3XAq7FJdGxTS81

--
Estás recibiendo invitaciones a conectar. Haz clic para darte de baja:
http://www.linkedin.com/e/sqgs3h-ht1ot3ke-6z/G033PJVLL4ZD8LelAGsEZFXLYsGx-mf4TSBKG4EWHO7/goo/mp%2B198988%40code%2Elaunchpad%2Enet/20061/I6733902597_1/?hs=false&tok=1mjuzrexuxTS81

(c) 2012 LinkedIn Corporation. 2029 Stierlin Ct, Mountain View, CA 94043, EE.UU.

299. By Agustin Cruz <email address hidden>

Merged changes done on git until commit 87e9a70ac

300. By Agustin Cruz <email address hidden>

Bringing changes from version 0.3.5

Unmerged revisions

300. By Agustin Cruz <email address hidden>

Bringing changes from version 0.3.5

299. By Agustin Cruz <email address hidden>

Merged changes done on git until commit 87e9a70ac

298. By Agustin Cruz <email address hidden>

Agregado módulo para facturación electrónica con factura-lo.mx

297. By Agustin Cruz <email address hidden>

Nuevo módulo de facturación electrónica con factura-lo.mx

296. By Agustin Cruz <email address hidden>

Merged

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'l10n_mx_facturae_pac_facturalo'
2=== added file 'l10n_mx_facturae_pac_facturalo/__init__.py'
3--- l10n_mx_facturae_pac_facturalo/__init__.py 1970-01-01 00:00:00 +0000
4+++ l10n_mx_facturae_pac_facturalo/__init__.py 2014-06-25 00:35:08 +0000
5@@ -0,0 +1,29 @@
6+# -*- encoding: utf-8 -*-
7+###########################################################################
8+# Module Writen to OpenERP, Open Source Management Solution
9+#
10+# Copyright (c) 2013 OpenPyme - http://www.openpyme.mx
11+# All Rights Reserved.
12+# info OpenPyme (info@openpyme.mx)
13+# Coded by: Agustín Cruz (agustin.cruz@openpyme.mx)
14+#
15+# This program is free software: you can redistribute it and/or modify
16+# it under the terms of the GNU Affero General Public License as
17+# published by the Free Software Foundation, either version 3 of the
18+# License, or (at your option) any later version.
19+#
20+# This program is distributed in the hope that it will be useful,
21+# but WITHOUT ANY WARRANTY; without even the implied warranty of
22+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23+# GNU Affero General Public License for more details.
24+#
25+# You should have received a copy of the GNU Affero General Public License
26+# along with this program. If not, see <http://www.gnu.org/licenses/>.
27+#
28+##############################################################################
29+
30+import invoice
31+import report
32+import params_pac
33+import ir_sequence_approval
34+import ir_attachment_facturae
35
36=== added file 'l10n_mx_facturae_pac_facturalo/__init__.pyc'
37Binary files l10n_mx_facturae_pac_facturalo/__init__.pyc 1970-01-01 00:00:00 +0000 and l10n_mx_facturae_pac_facturalo/__init__.pyc 2014-06-25 00:35:08 +0000 differ
38=== added file 'l10n_mx_facturae_pac_facturalo/__openerp__.py'
39--- l10n_mx_facturae_pac_facturalo/__openerp__.py 1970-01-01 00:00:00 +0000
40+++ l10n_mx_facturae_pac_facturalo/__openerp__.py 2014-06-25 00:35:08 +0000
41@@ -0,0 +1,77 @@
42+# -*- encoding: utf-8 -*-
43+###########################################################################
44+# Module Writen to OpenERP, Open Source Management Solution
45+#
46+# Copyright (c) 2013 OpenPyme - http://www.openpyme.mx
47+# All Rights Reserved.
48+# info OpenPyme (info@openpyme.mx)
49+# Coded by: Agustin Cruz Lozano (agustin.cruz@openpyme.mx)
50+# Verónica Paleta (veronica.paleta@openpyme.mx)
51+#
52+# This program is free software: you can redistribute it and/or modify
53+# it under the terms of the GNU Affero General Public License as
54+# published by the Free Software Foundation, either version 3 of the
55+# License, or (at your option) any later version.
56+#
57+# This program is distributed in the hope that it will be useful,
58+# but WITHOUT ANY WARRANTY; without even the implied warranty of
59+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
60+# GNU Affero General Public License for more details.
61+#
62+# You should have received a copy of the GNU Affero General Public License
63+# along with this program. If not, see <http://www.gnu.org/licenses/>.
64+#
65+##############################################################################
66+
67+{
68+ "name" : "Factura Electronica Mexico - Control de Comprobantes Digitales",
69+ "version" : "1.0",
70+ "author" : "OpenPyme",
71+ "category" : "Localization/Mexico",
72+ "description" : """This module allows use the electronic invoice service
73+provided by control de Comprobantes Digitales S. de R.L. de C.V.
74+Instructions for installation:
75+- Ubuntu:
76+sudo apt-get install xmlsec1 libxml2-dev libxslt-dev libxmlsec-dev
77+sudo apt-set install python-dev python-fpconst
78+sudo easy_install lxml pyxmlsec
79+- Archlinux:
80+yaourt -S python2-lxml python2-fpconst pyxmlsec
81+- Centos:
82+sudo yum install libxml2 libxml2-devel libxslt libxslt-dev
83+sudo yum install python-devel python-fpconst pyxmlsec
84+All operative systems:
85+- Python Soappy
86+ cd /tmp
87+ git clone https://github.com/Fedrojesa/SOAPpy.git
88+ cd SOAPPy
89+ python setup.py build
90+ sudo python setup.py install
91+
92+- Python qrcode (Todos los sistemas operativos)\n
93+ cd /tmp
94+ git clone https://github.com/lincolnloop/python-qrcode.git
95+ cd python-qrcode
96+ python setup.py build
97+ sudo python setup.py install
98+""",
99+ "website" : "http://www.openpyme.mx/",
100+ "license" : "AGPL-3",
101+ "depends" : ["l10n_mx_facturae_groups",
102+ "l10n_mx_params_pac",
103+ "l10n_mx_account_tax_category",
104+ "l10n_mx_facturae_seq",
105+ "l10n_mx_ir_attachment_facturae",
106+ "l10n_mx_facturae_pac",
107+ "l10n_mx_facturae_group_show_wizards",
108+ "account_cancel"
109+ ],
110+ "demo" : [],
111+ "data" : [
112+ "data/l10n_mx_facturae_pac_facturalo_report.xml",
113+ "data/l10n_mx_facturae_pac_facturalo.xml",
114+ "data/l10n_mx_facturae_seq.xml"
115+ ],
116+ "installable" : True,
117+ "active" : False,
118+}
119
120=== added directory 'l10n_mx_facturae_pac_facturalo/data'
121=== added file 'l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_pac_facturalo.xml'
122--- l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_pac_facturalo.xml 1970-01-01 00:00:00 +0000
123+++ l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_pac_facturalo.xml 2014-06-25 00:35:08 +0000
124@@ -0,0 +1,24 @@
125+<?xml version="1.0" ?>
126+<openerp>
127+
128+ <data noupdate="1">
129+ <record id="params_pac_facturalo_cancelar" model="params.pac">
130+ <field name="method_type">pac_facturalo_cancelar</field>
131+ <field name="url_webservice">http://cancelacion.expidetufactura.com.mx:8080/cancelacion/CancelacionPruebas?wsdl</field>
132+ <field name="namespace">http://service.timbrado.cim.mx/</field>
133+ <field name="user">pruebas</field>
134+ <field name="password">123456</field>
135+ <field name="name">Facturalo - Cancelar</field>
136+ </record>
137+
138+ <record id="params_pac_facturalo_firmar" model="params.pac">
139+ <field name="method_type">pac_facturalo_firmar</field>
140+ <field name="url_webservice">http://timbrado.expidetufactura.com.mx:8080/pruebas/TimbradoWS?wsdl</field>
141+ <field name="namespace">http://service.timbrado.cim.mx/</field>
142+ <field name="user">pruebas</field>
143+ <field name="password">123456</field>
144+ <field name="name">Facturalo - Firmar</field>
145+ </record>
146+ </data>
147+
148+</openerp>
149
150=== added file 'l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_pac_facturalo_report.xml'
151--- l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_pac_facturalo_report.xml 1970-01-01 00:00:00 +0000
152+++ l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_pac_facturalo_report.xml 2014-06-25 00:35:08 +0000
153@@ -0,0 +1,18 @@
154+<?xml version="1.0" encoding="utf-8"?>
155+<openerp>
156+ <data>
157+ <report
158+ auto="False"
159+ id="l10n_mx_facturae_report.account_invoice_facturae_webkit"
160+ model="account.invoice"
161+ name="account.invoice.facturae.webkit"
162+ rml="l10n_mx_facturae_pac_facturalo/report/account_print_invoice.rml"
163+ string="Factura Electronica Report"
164+ report_type="pdf"
165+ header="False"
166+ attachment="(object.state in ('open','paid')) and (object.fname_invoice and (object.fname_invoice + ''))"
167+ attachment_use="True"
168+ />
169+ </data>
170+
171+</openerp>
172
173=== added file 'l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_seq.xml'
174--- l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_seq.xml 1970-01-01 00:00:00 +0000
175+++ l10n_mx_facturae_pac_facturalo/data/l10n_mx_facturae_seq.xml 2014-06-25 00:35:08 +0000
176@@ -0,0 +1,37 @@
177+<?xml version="1.0" ?>
178+<openerp>
179+ <data noupdate="1">
180+ <record id="l10n_mx_facturae_ir_seq_01" model="ir.sequence">
181+ <field name="company_id" ref="base.main_company"/>
182+ <field name="name">Sequence CFDI</field>
183+ <field name="active" eval="True"/>
184+ <field name="padding">0</field>
185+ <field name="number_next">1</field>
186+ <field name="number_increment">1</field>
187+ <field name="implementation">standard</field>
188+ </record>
189+
190+ <record id="l10n_mx_facturae_ir_seq_approval_01" model="ir.sequence.approval">
191+ <field name="company_id" ref="base.main_company"/>
192+ <field name="sequence_id" ref="l10n_mx_facturae_ir_seq_01"/>
193+ <field name="approval_number">12345</field>
194+ <field name="serie">A</field>
195+ <field name="approval_year" eval="time.strftime('%Y')"/>
196+ <field name="number_start">1</field>
197+ <field name="number_end">9999</field>
198+ <field name="type">cfdi32</field>
199+ </record>
200+
201+ <record id="l10n_mx_facturae_account_journal_01" model="account.journal">
202+ <field name="company_id" ref="base.main_company"/>
203+ <field name="sequence_id" ref="l10n_mx_facturae_ir_seq_01"/>
204+ <field name="name">Diario de CFDI</field>
205+ <field name="code">CFDI</field>
206+ <field name="type">sale</field>
207+ <field name="update_posted">1</field>
208+ <field name="user_id" ref="base.user_root"/>
209+ <field name="company2_id" ref="base.main_company"/>
210+ <field name="currency" ref="base.MXN"/>
211+ </record>
212+ </data>
213+</openerp>
214
215=== added directory 'l10n_mx_facturae_pac_facturalo/i18n'
216=== added file 'l10n_mx_facturae_pac_facturalo/i18n/es_MX.po'
217--- l10n_mx_facturae_pac_facturalo/i18n/es_MX.po 1970-01-01 00:00:00 +0000
218+++ l10n_mx_facturae_pac_facturalo/i18n/es_MX.po 2014-06-25 00:35:08 +0000
219@@ -0,0 +1,275 @@
220+# Translation of OpenERP Server.
221+# This file contains the translation of the following modules:
222+# * l10n_mx_facturae_pac_facturalo
223+# es_MX.po <agustin.cruz@openpy.mx>, 2014.
224+msgid ""
225+msgstr ""
226+"Project-Id-Version: OpenERP Server 7.0\n"
227+"Report-Msgid-Bugs-To: \n"
228+"POT-Creation-Date: 2014-06-25 00:21+0000\n"
229+"PO-Revision-Date: 2014-06-24 19:25-0500\n"
230+"Last-Translator: es_MX.po <agustin.cruz@openpy.mx>\n"
231+"Language-Team: Laptop\n"
232+"Language: es\n"
233+"MIME-Version: 1.0\n"
234+"Content-Type: text/plain; charset=UTF-8\n"
235+"Content-Transfer-Encoding: \n"
236+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
237+"X-Generator: Virtaal 0.7.1\n"
238+
239+#. module: l10n_mx_facturae_pac_facturalo
240+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:354
241+#, python-format
242+msgid "WARNING, CANCEL IN TEST!!!!\n"
243+"\n"
244+""
245+msgstr ""
246+"PRECACIÓN, CANCELCIÓN HECHA EN PRUEBAS !!!\n"
247+"\n"
248+
249+#. module: l10n_mx_facturae_pac_facturalo
250+#: field:account.invoice,cfdi_folio_fiscal:0
251+msgid "CFD-I Folio Fiscal"
252+msgstr ""
253+
254+#. module: l10n_mx_facturae_pac_facturalo
255+#: code:addons/l10n_mx_facturae_pac_facturalo/params_pac.py:36
256+#, python-format
257+msgid "PAC Factura-lo - Sign"
258+msgstr "Firma - PAC Factura-lo"
259+
260+#. module: l10n_mx_facturae_pac_facturalo
261+#: model:ir.model,name:l10n_mx_facturae_pac_facturalo.model_params_pac
262+msgid "params.pac"
263+msgstr ""
264+
265+#. module: l10n_mx_facturae_pac_facturalo
266+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:137
267+#, python-format
268+msgid "Press in the button 'Upload File'"
269+msgstr ""
270+
271+#. module: l10n_mx_facturae_pac_facturalo
272+#: help:account.invoice,cfdi_cadena_original:0
273+msgid "Original String used in the electronic invoice"
274+msgstr "Cadena Original usada en la factura electrónica"
275+
276+#. module: l10n_mx_facturae_pac_facturalo
277+#: help:account.invoice,cfdi_folio_fiscal:0
278+msgid "Folio used in the electronic invoice"
279+msgstr "Foluo usado en la factura electrónica"
280+
281+#. module: l10n_mx_facturae_pac_facturalo
282+#: help:account.invoice,cfdi_no_certificado:0
283+msgid "Serial Number of the Certificate"
284+msgstr "Número de Serie del Certificado"
285+
286+#. module: l10n_mx_facturae_pac_facturalo
287+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:385
288+#, python-format
289+msgid "codMensaje"
290+msgstr ""
291+
292+#. module: l10n_mx_facturae_pac_facturalo
293+#: field:account.invoice,cfdi_fecha_timbrado:0
294+msgid "CFD-I Fecha Timbrado"
295+msgstr "CFD-I Fecha Timbrado"
296+
297+#. module: l10n_mx_facturae_pac_facturalo
298+#: code:addons/l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py:182
299+#, python-format
300+msgid "Not Vat Number set on partner"
301+msgstr "No se ha asignado un RFC en la ficha del cliente"
302+
303+#. module: l10n_mx_facturae_pac_facturalo
304+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:247
305+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:379
306+#, python-format
307+msgid "Can't get XML file from PAC."
308+msgstr "No se pudo obtener el archivo XML del PAC."
309+
310+#. module: l10n_mx_facturae_pac_facturalo
311+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:311
312+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:388
313+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:400
314+#, python-format
315+msgid "Stamped Code: %s. \n"
316+" Stamped Message: %s."
317+msgstr ""
318+"Código devuelto: %s.\n"
319+"Mensaje devuelto: %s."
320+
321+#. module: l10n_mx_facturae_pac_facturalo
322+#: help:account.invoice,cfdi_fecha_timbrado:0
323+msgid "Date when is stamped the electronic invoice"
324+msgstr "Fecha en que fue firmada la factura electrónica"
325+
326+#. module: l10n_mx_facturae_pac_facturalo
327+#: help:account.invoice,cfdi_sello:0
328+msgid "Sign assigned by the SAT"
329+msgstr "Sello asignado por el SAT"
330+
331+#. module: l10n_mx_facturae_pac_facturalo
332+#: code:addons/l10n_mx_facturae_pac_facturalo/params_pac.py:37
333+#, python-format
334+msgid "PAC Factura-lo - Cancel"
335+msgstr ""
336+
337+#. module: l10n_mx_facturae_pac_facturalo
338+#: code:addons/l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py:185
339+#, python-format
340+msgid "Customer Address Not Invoice Type"
341+msgstr "La dirección del cliente no es de tipo Invoice"
342+
343+#. module: l10n_mx_facturae_pac_facturalo
344+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:537
345+#, python-format
346+msgid "Not captured a CERTIFICATE file in format PEM, in the company!"
347+msgstr ""
348+
349+#. module: l10n_mx_facturae_pac_facturalo
350+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:551
351+#, python-format
352+msgid "Check date of invoice and the validity of certificate"
353+msgstr "Revise la fecha de la factura y la fecha de validez del certificado"
354+
355+#. module: l10n_mx_facturae_pac_facturalo
356+#: code:addons/l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py:242
357+#, python-format
358+msgid "SIN FOLIO O ESTATUS NO VALIDO\n"
359+""
360+msgstr ""
361+
362+#. module: l10n_mx_facturae_pac_facturalo
363+#: code:addons/l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py:249
364+#, python-format
365+msgid "Este comprobante tendrá una vigencia de dos años contados a partir de la fecha aprobación de la asignación de folios, la cual es: %s"
366+msgstr ""
367+
368+#. module: l10n_mx_facturae_pac_facturalo
369+#: field:account.invoice,cfdi_sello:0
370+msgid "CFD-I Sello"
371+msgstr "Sello CFDI"
372+
373+#. module: l10n_mx_facturae_pac_facturalo
374+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:246
375+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:378
376+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:387
377+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:399
378+#, python-format
379+msgid "Error"
380+msgstr ""
381+
382+#. module: l10n_mx_facturae_pac_facturalo
383+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:536
384+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:546
385+#, python-format
386+msgid "Error !"
387+msgstr ""
388+
389+#. module: l10n_mx_facturae_pac_facturalo
390+#: field:account.invoice,cfdi_fecha_cancelacion:0
391+msgid "CFD-I Fecha Cancelacion"
392+msgstr ""
393+
394+#. module: l10n_mx_facturae_pac_facturalo
395+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:550
396+#, python-format
397+msgid "Warning !"
398+msgstr ""
399+
400+#. module: l10n_mx_facturae_pac_facturalo
401+#: field:account.invoice,cfdi_cadena_original:0
402+msgid "CFD-I Cadena Original"
403+msgstr ""
404+
405+#. module: l10n_mx_facturae_pac_facturalo
406+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:396
407+#, python-format
408+msgid "UUID No existe"
409+msgstr ""
410+
411+#. module: l10n_mx_facturae_pac_facturalo
412+#: help:account.invoice,cfdi_fecha_cancelacion:0
413+msgid "If the invoice is cancel, this field saved the date when is cancel"
414+msgstr ""
415+
416+#. module: l10n_mx_facturae_pac_facturalo
417+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:394
418+#, python-format
419+msgid "UUID No corresponde al emisor"
420+msgstr ""
421+
422+#. module: l10n_mx_facturae_pac_facturalo
423+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:347
424+#, python-format
425+msgid "Not found information from web services of PAC, verify that the configuration of PAC is correct"
426+msgstr ""
427+
428+#. module: l10n_mx_facturae_pac_facturalo
429+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:395
430+#, python-format
431+msgid "UUID No aplicable para cancelación"
432+msgstr ""
433+
434+#. module: l10n_mx_facturae_pac_facturalo
435+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:220
436+#, python-format
437+msgid "Not found information from web services of PACVerify that the configuration of PAC is correct"
438+msgstr ""
439+
440+#. module: l10n_mx_facturae_pac_facturalo
441+#: model:ir.model,name:l10n_mx_facturae_pac_facturalo.model_ir_sequence_approval
442+msgid "ir.sequence.approval"
443+msgstr ""
444+
445+#. module: l10n_mx_facturae_pac_facturalo
446+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:546
447+#, python-format
448+msgid "Not captured a KEY file in format PEM, in the company!"
449+msgstr ""
450+
451+#. module: l10n_mx_facturae_pac_facturalo
452+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:219
453+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:310
454+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:346
455+#, python-format
456+msgid "Warning"
457+msgstr ""
458+
459+#. module: l10n_mx_facturae_pac_facturalo
460+#: model:ir.model,name:l10n_mx_facturae_pac_facturalo.model_ir_attachment_facturae_mx
461+msgid "Email Thread"
462+msgstr ""
463+
464+#. module: l10n_mx_facturae_pac_facturalo
465+#: model:ir.model,name:l10n_mx_facturae_pac_facturalo.model_account_invoice
466+msgid "Invoice"
467+msgstr ""
468+
469+#. module: l10n_mx_facturae_pac_facturalo
470+#: code:addons/l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py:239
471+#, python-format
472+msgid "Número de aprobación SICOFI: %s\n"
473+""
474+msgstr ""
475+
476+#. module: l10n_mx_facturae_pac_facturalo
477+#: code:addons/l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py:243
478+#, python-format
479+msgid "La reproducción apócrifa de este comprobante constituye un delito en los términos de las disposiciones fiscales.\n"
480+""
481+msgstr ""
482+
483+#. module: l10n_mx_facturae_pac_facturalo
484+#: field:account.invoice,cfdi_no_certificado:0
485+msgid "CFD-I Certificado"
486+msgstr ""
487+
488+#. module: l10n_mx_facturae_pac_facturalo
489+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:228
490+#, python-format
491+msgid "WARNING, SIGNED IN TEST!!!!\n"
492+"\n"
493+""
494+msgstr ""
495
496=== added file 'l10n_mx_facturae_pac_facturalo/i18n/l10n_mx_facturae_pac_facturalo.pot'
497--- l10n_mx_facturae_pac_facturalo/i18n/l10n_mx_facturae_pac_facturalo.pot 1970-01-01 00:00:00 +0000
498+++ l10n_mx_facturae_pac_facturalo/i18n/l10n_mx_facturae_pac_facturalo.pot 2014-06-25 00:35:08 +0000
499@@ -0,0 +1,270 @@
500+# Translation of OpenERP Server.
501+# This file contains the translation of the following modules:
502+# * l10n_mx_facturae_pac_facturalo
503+#
504+msgid ""
505+msgstr ""
506+"Project-Id-Version: OpenERP Server 7.0\n"
507+"Report-Msgid-Bugs-To: \n"
508+"POT-Creation-Date: 2014-06-25 00:21+0000\n"
509+"PO-Revision-Date: 2014-06-25 00:21+0000\n"
510+"Last-Translator: <>\n"
511+"Language-Team: \n"
512+"MIME-Version: 1.0\n"
513+"Content-Type: text/plain; charset=UTF-8\n"
514+"Content-Transfer-Encoding: \n"
515+"Plural-Forms: \n"
516+
517+#. module: l10n_mx_facturae_pac_facturalo
518+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:354
519+#, python-format
520+msgid "WARNING, CANCEL IN TEST!!!!\n"
521+"\n"
522+""
523+msgstr ""
524+
525+#. module: l10n_mx_facturae_pac_facturalo
526+#: field:account.invoice,cfdi_folio_fiscal:0
527+msgid "CFD-I Folio Fiscal"
528+msgstr ""
529+
530+#. module: l10n_mx_facturae_pac_facturalo
531+#: code:addons/l10n_mx_facturae_pac_facturalo/params_pac.py:36
532+#, python-format
533+msgid "PAC Factura-lo - Sign"
534+msgstr ""
535+
536+#. module: l10n_mx_facturae_pac_facturalo
537+#: model:ir.model,name:l10n_mx_facturae_pac_facturalo.model_params_pac
538+msgid "params.pac"
539+msgstr ""
540+
541+#. module: l10n_mx_facturae_pac_facturalo
542+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:137
543+#, python-format
544+msgid "Press in the button 'Upload File'"
545+msgstr ""
546+
547+#. module: l10n_mx_facturae_pac_facturalo
548+#: help:account.invoice,cfdi_cadena_original:0
549+msgid "Original String used in the electronic invoice"
550+msgstr ""
551+
552+#. module: l10n_mx_facturae_pac_facturalo
553+#: help:account.invoice,cfdi_folio_fiscal:0
554+msgid "Folio used in the electronic invoice"
555+msgstr ""
556+
557+#. module: l10n_mx_facturae_pac_facturalo
558+#: help:account.invoice,cfdi_no_certificado:0
559+msgid "Serial Number of the Certificate"
560+msgstr ""
561+
562+#. module: l10n_mx_facturae_pac_facturalo
563+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:385
564+#, python-format
565+msgid "codMensaje"
566+msgstr ""
567+
568+#. module: l10n_mx_facturae_pac_facturalo
569+#: field:account.invoice,cfdi_fecha_timbrado:0
570+msgid "CFD-I Fecha Timbrado"
571+msgstr ""
572+
573+#. module: l10n_mx_facturae_pac_facturalo
574+#: code:addons/l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py:182
575+#, python-format
576+msgid "Not Vat Number set on partner"
577+msgstr ""
578+
579+#. module: l10n_mx_facturae_pac_facturalo
580+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:247
581+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:379
582+#, python-format
583+msgid "Can't get XML file from PAC."
584+msgstr ""
585+
586+#. module: l10n_mx_facturae_pac_facturalo
587+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:311
588+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:388
589+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:400
590+#, python-format
591+msgid "Stamped Code: %s. \n"
592+" Stamped Message: %s."
593+msgstr ""
594+
595+#. module: l10n_mx_facturae_pac_facturalo
596+#: help:account.invoice,cfdi_fecha_timbrado:0
597+msgid "Date when is stamped the electronic invoice"
598+msgstr ""
599+
600+#. module: l10n_mx_facturae_pac_facturalo
601+#: help:account.invoice,cfdi_sello:0
602+msgid "Sign assigned by the SAT"
603+msgstr ""
604+
605+#. module: l10n_mx_facturae_pac_facturalo
606+#: code:addons/l10n_mx_facturae_pac_facturalo/params_pac.py:37
607+#, python-format
608+msgid "PAC Factura-lo - Cancel"
609+msgstr ""
610+
611+#. module: l10n_mx_facturae_pac_facturalo
612+#: code:addons/l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py:185
613+#, python-format
614+msgid "Customer Address Not Invoice Type"
615+msgstr ""
616+
617+#. module: l10n_mx_facturae_pac_facturalo
618+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:537
619+#, python-format
620+msgid "Not captured a CERTIFICATE file in format PEM, in the company!"
621+msgstr ""
622+
623+#. module: l10n_mx_facturae_pac_facturalo
624+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:551
625+#, python-format
626+msgid "Check date of invoice and the validity of certificate"
627+msgstr ""
628+
629+#. module: l10n_mx_facturae_pac_facturalo
630+#: code:addons/l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py:242
631+#, python-format
632+msgid "SIN FOLIO O ESTATUS NO VALIDO\n"
633+""
634+msgstr ""
635+
636+#. module: l10n_mx_facturae_pac_facturalo
637+#: code:addons/l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py:249
638+#, python-format
639+msgid "Este comprobante tendrá una vigencia de dos años contados a partir de la fecha aprobación de la asignación de folios, la cual es: %s"
640+msgstr ""
641+
642+#. module: l10n_mx_facturae_pac_facturalo
643+#: field:account.invoice,cfdi_sello:0
644+msgid "CFD-I Sello"
645+msgstr ""
646+
647+#. module: l10n_mx_facturae_pac_facturalo
648+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:246
649+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:378
650+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:387
651+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:399
652+#, python-format
653+msgid "Error"
654+msgstr ""
655+
656+#. module: l10n_mx_facturae_pac_facturalo
657+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:536
658+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:546
659+#, python-format
660+msgid "Error !"
661+msgstr ""
662+
663+#. module: l10n_mx_facturae_pac_facturalo
664+#: field:account.invoice,cfdi_fecha_cancelacion:0
665+msgid "CFD-I Fecha Cancelacion"
666+msgstr ""
667+
668+#. module: l10n_mx_facturae_pac_facturalo
669+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:550
670+#, python-format
671+msgid "Warning !"
672+msgstr ""
673+
674+#. module: l10n_mx_facturae_pac_facturalo
675+#: field:account.invoice,cfdi_cadena_original:0
676+msgid "CFD-I Cadena Original"
677+msgstr ""
678+
679+#. module: l10n_mx_facturae_pac_facturalo
680+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:396
681+#, python-format
682+msgid "UUID No existe"
683+msgstr ""
684+
685+#. module: l10n_mx_facturae_pac_facturalo
686+#: help:account.invoice,cfdi_fecha_cancelacion:0
687+msgid "If the invoice is cancel, this field saved the date when is cancel"
688+msgstr ""
689+
690+#. module: l10n_mx_facturae_pac_facturalo
691+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:394
692+#, python-format
693+msgid "UUID No corresponde al emisor"
694+msgstr ""
695+
696+#. module: l10n_mx_facturae_pac_facturalo
697+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:347
698+#, python-format
699+msgid "Not found information from web services of PAC, verify that the configuration of PAC is correct"
700+msgstr ""
701+
702+#. module: l10n_mx_facturae_pac_facturalo
703+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:395
704+#, python-format
705+msgid "UUID No aplicable para cancelación"
706+msgstr ""
707+
708+#. module: l10n_mx_facturae_pac_facturalo
709+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:220
710+#, python-format
711+msgid "Not found information from web services of PACVerify that the configuration of PAC is correct"
712+msgstr ""
713+
714+#. module: l10n_mx_facturae_pac_facturalo
715+#: model:ir.model,name:l10n_mx_facturae_pac_facturalo.model_ir_sequence_approval
716+msgid "ir.sequence.approval"
717+msgstr ""
718+
719+#. module: l10n_mx_facturae_pac_facturalo
720+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:546
721+#, python-format
722+msgid "Not captured a KEY file in format PEM, in the company!"
723+msgstr ""
724+
725+#. module: l10n_mx_facturae_pac_facturalo
726+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:219
727+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:310
728+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:346
729+#, python-format
730+msgid "Warning"
731+msgstr ""
732+
733+#. module: l10n_mx_facturae_pac_facturalo
734+#: model:ir.model,name:l10n_mx_facturae_pac_facturalo.model_ir_attachment_facturae_mx
735+msgid "Email Thread"
736+msgstr ""
737+
738+#. module: l10n_mx_facturae_pac_facturalo
739+#: model:ir.model,name:l10n_mx_facturae_pac_facturalo.model_account_invoice
740+msgid "Invoice"
741+msgstr ""
742+
743+#. module: l10n_mx_facturae_pac_facturalo
744+#: code:addons/l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py:239
745+#, python-format
746+msgid "Número de aprobación SICOFI: %s\n"
747+""
748+msgstr ""
749+
750+#. module: l10n_mx_facturae_pac_facturalo
751+#: code:addons/l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py:243
752+#, python-format
753+msgid "La reproducción apócrifa de este comprobante constituye un delito en los términos de las disposiciones fiscales.\n"
754+""
755+msgstr ""
756+
757+#. module: l10n_mx_facturae_pac_facturalo
758+#: field:account.invoice,cfdi_no_certificado:0
759+msgid "CFD-I Certificado"
760+msgstr ""
761+
762+#. module: l10n_mx_facturae_pac_facturalo
763+#: code:addons/l10n_mx_facturae_pac_facturalo/invoice.py:228
764+#, python-format
765+msgid "WARNING, SIGNED IN TEST!!!!\n"
766+"\n"
767+""
768+msgstr ""
769+
770
771=== added file 'l10n_mx_facturae_pac_facturalo/invoice.py'
772--- l10n_mx_facturae_pac_facturalo/invoice.py 1970-01-01 00:00:00 +0000
773+++ l10n_mx_facturae_pac_facturalo/invoice.py 2014-06-25 00:35:08 +0000
774@@ -0,0 +1,560 @@
775+# -*- encoding: utf-8 -*-
776+###########################################################################
777+# Module Writen to OpenERP, Open Source Management Solution
778+#
779+# Copyright (c) 2013 OpenPyme - http://www.openpyme.mx
780+# All Rights Reserved.
781+# info OpenPyme (info@openpyme.mx)
782+# Coded by: Agustín Cruz (agustin.cruz@openpyme.mx)
783+#
784+# This program is free software: you can redistribute it and/or modify
785+# it under the terms of the GNU Affero General Public License as
786+# published by the Free Software Foundation, either version 3 of the
787+# License, or (at your option) any later version.
788+#
789+# This program is distributed in the hope that it will be useful,
790+# but WITHOUT ANY WARRANTY; without even the implied warranty of
791+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
792+# GNU Affero General Public License for more details.
793+#
794+# You should have received a copy of the GNU Affero General Public License
795+# along with this program. If not, see <http://www.gnu.org/licenses/>.
796+#
797+##############################################################################
798+
799+import logging
800+import base64
801+import string
802+from datetime import datetime
803+from pytz import timezone
804+import pytz
805+import re
806+import time
807+
808+from openerp.tools.translate import _
809+from openerp.osv import fields, osv
810+from openerp import tools
811+
812+
813+logger = logging.getLogger(__name__)
814+
815+try:
816+ from SOAPpy import WSDL
817+except:
818+ logger.error("Package SOAPpy missed")
819+
820+
821+class account_invoice(osv.osv):
822+ _inherit = 'account.invoice'
823+
824+ _columns = {
825+ 'cfdi_sello': fields.text('CFD-I Sello', help='Sign assigned by the SAT'),
826+ 'cfdi_no_certificado': fields.char('CFD-I Certificado', size=32,
827+ help='Serial Number of the Certificate'),
828+ 'cfdi_cadena_original': fields.text('CFD-I Cadena Original',
829+ help='Original String used in the electronic invoice'),
830+ 'cfdi_fecha_timbrado': fields.datetime('CFD-I Fecha Timbrado',
831+ help='Date when is stamped the electronic invoice'),
832+ 'cfdi_fecha_cancelacion': fields.datetime('CFD-I Fecha Cancelacion',
833+ help='If the invoice is cancel, this field saved the date when is cancel'),
834+ 'cfdi_folio_fiscal': fields.char('CFD-I Folio Fiscal', size=64,
835+ help='Folio used in the electronic invoice'),
836+ }
837+
838+ def write_cfd_data(self, cr, uid, ids, cfd_data=None, context=None):
839+ """
840+ @param cfd_datas : Dictionary with data that is used in facturae CFDI
841+ """
842+ if not cfd_data:
843+ cfd_data = {}
844+
845+ comprobante = self._get_type_sequence(cr, uid, ids, context=context)
846+
847+ noCertificado = cfd_data.get(comprobante, {}).get('noCertificado', '')
848+ certificado = cfd_data.get(comprobante, {}).get('certificado', '')
849+ sello = cfd_data.get(comprobante, {}).get('sello', '')
850+ cadena_original = cfd_data.get('cadena_original', '')
851+ data = {
852+ 'no_certificado': noCertificado,
853+ 'certificado': certificado,
854+ 'sello': sello,
855+ 'cadena_original': cadena_original,
856+ }
857+ self.write(cr, uid, ids, data, context=context)
858+ return True
859+
860+ def copy(self, cr, uid, id, default={}, context=None):
861+ if context is None:
862+ context = {}
863+ default.update({
864+ 'cfdi_cbb': False,
865+ 'cfdi_sello': False,
866+ 'cfdi_no_certificado': False,
867+ 'cfdi_cadena_original': False,
868+ 'cfdi_fecha_timbrado': False,
869+ 'cfdi_folio_fiscal': False,
870+ 'cfdi_fecha_cancelacion': False,
871+ })
872+ return super(account_invoice, self).copy(cr, uid, id, default, context)
873+
874+ def action_cancel_draft(self, cr, uid, ids, *args):
875+ self.write(cr, uid, ids, {
876+ 'cfdi_sello':False,
877+ 'cfdi_no_certificado':False,
878+ 'cfdi_cadena_original':False,
879+ 'cfdi_fecha_timbrado': False,
880+ 'cfdi_folio_fiscal': False,
881+ 'cfdi_fecha_cancelacion': False,
882+ })
883+ return super(account_invoice, self).action_cancel_draft(cr, uid, ids, args)
884+
885+ def _get_file(self, cr, uid, inv_ids, context={}):
886+ if not context:
887+ context = {}
888+ id = inv_ids[0]
889+ invoice = self.browse(cr, uid, [id], context=context)[0]
890+ fname_invoice = invoice.fname_invoice and invoice.fname_invoice + \
891+ '.xml' or ''
892+ aids = self.pool.get('ir.attachment').search(cr, uid, [(
893+ 'datas_fname', '=', invoice.fname_invoice + '.xml'), (
894+ 'res_model', '=', 'account.invoice'), ('res_id', '=', id)])
895+ xml_data = ""
896+ if aids:
897+ brow_rec = self.pool.get('ir.attachment').browse(cr, uid, aids[0])
898+ if brow_rec.datas:
899+ xml_data = base64.decodestring(brow_rec.datas)
900+ else:
901+ fname, xml_data = self._get_facturae_invoice_xml_data(
902+ cr, uid, inv_ids, context=context)
903+ self.pool.get('ir.attachment').create(cr, uid, {
904+ 'name': fname_invoice,
905+ 'datas': base64.encodestring(xml_data),
906+ 'datas_fname': fname_invoice,
907+ 'res_model': 'account.invoice',
908+ 'res_id': invoice.id,
909+ }, context=context)
910+ self.fdata = base64.encodestring(xml_data)
911+ msg = _("Press in the button 'Upload File'")
912+ return {'file': self.fdata, 'fname': fname_invoice,
913+ 'name': fname_invoice, 'msg': msg}
914+
915+ def add_node(self, node_name=None, attrs=None, parent_node=None,
916+ minidom_xml_obj=None, attrs_types=None, order=False):
917+ """
918+ @params node_name : Name node to added
919+ @params attrs : Attributes to add in node
920+ @params parent_node : Node parent where was add new node children
921+ @params minidom_xml_obj : File XML where add nodes
922+ @params attrs_types : Type of attributes added in the node
923+ @params order : If need add the params in order in the XML, add a
924+ list with order to params
925+ """
926+ if not order:
927+ order = attrs
928+ new_node = minidom_xml_obj.createElement(node_name)
929+ for key in order:
930+ if attrs_types[key] == 'attribute':
931+ new_node.setAttribute(key, attrs[key])
932+ elif attrs_types[key] == 'textNode':
933+ key_node = minidom_xml_obj.createElement(key)
934+ text_node = minidom_xml_obj.createTextNode(attrs[key])
935+
936+ key_node.appendChild(text_node)
937+ new_node.appendChild(key_node)
938+ parent_node.appendChild(new_node)
939+ return new_node
940+
941+ def _get_type_sequence(self, cr, uid, ids, context=None):
942+ ir_seq_app_obj = self.pool.get('ir.sequence.approval')
943+ invoice = self.browse(cr, uid, ids[0], context=context)
944+ sequence_app_id = ir_seq_app_obj.search(cr, uid, [(
945+ 'sequence_id', '=', invoice.invoice_sequence_id.id)], context=context)
946+ type_inv = 'cfd22'
947+ if sequence_app_id:
948+ type_inv = ir_seq_app_obj.browse(
949+ cr, uid, sequence_app_id[0], context=context).type
950+ if type_inv == 'cfdi32':
951+ comprobante = 'cfdi:Comprobante'
952+ else:
953+ comprobante = 'Comprobante'
954+ return comprobante
955+
956+ def _get_time_zone(self, cr, uid, invoice_id, context=None):
957+ res_users_obj = self.pool.get('res.users')
958+ userstz = res_users_obj.browse(cr, uid, [uid])[0].partner_id.tz
959+ a = 0
960+ if userstz:
961+ hours = timezone(userstz)
962+ fmt = '%Y-%m-%d %H:%M:%S %Z%z'
963+ now = datetime.now()
964+ loc_dt = hours.localize(datetime(now.year, now.month, now.day,
965+ now.hour, now.minute, now.second))
966+ timezone_loc = (loc_dt.strftime(fmt))
967+ diff_timezone_original = timezone_loc[-5:-2]
968+ timezone_original = int(diff_timezone_original)
969+ s = str(datetime.now(pytz.timezone(userstz)))
970+ s = s[-6:-3]
971+ timezone_present = int(s) * -1
972+ a = timezone_original + ((
973+ timezone_present + timezone_original) * -1)
974+ return a
975+
976+ def _upload_ws_file(self, cr, uid, invoice_id, fdata=None, context={}):
977+ """
978+ @params
979+ invoice_id: Id from invoice to validate
980+ fdata : File.xml base64 codificated
981+ """
982+ pac_params_obj = self.pool.get('params.pac')
983+ invoice = self.browse(cr, uid, invoice_id, context=context)[0]
984+ f = False
985+ msg = ''
986+ pac_ids = pac_params_obj.search(cr, uid,
987+ [('method_type', '=', 'pac_facturalo_firmar'),
988+ ('company_id', '=', invoice.company_emitter_id.id),
989+ ('active', '=', True)
990+ ], limit=1, context=context)
991+
992+ if not pac_ids:
993+ raise osv.except_osv(_('Warning'),
994+ _('Not found information from web services of PAC'
995+ 'Verify that the configuration of PAC is correct')
996+ )
997+
998+ # Get pac params for use on this invoice
999+ pac = pac_params_obj.browse(cr, uid, pac_ids, context)[0]
1000+
1001+ if pac.user == 'pruebas':
1002+ msg += _(u'WARNING, SIGNED IN TEST!!!!\n\n')
1003+
1004+ try:
1005+ wsdl = WSDL.SOAPProxy(pac.url_webservice, pac.namespace)
1006+
1007+ # Webservice configuration
1008+ wsdl.soapproxy.config.dumpSOAPOut = 0
1009+ wsdl.soapproxy.config.dumpSOAPIn = 0
1010+ wsdl.soapproxy.config.debug = 0
1011+ wsdl.soapproxy.config.dict_encoding = 'UTF-8'
1012+
1013+ # Timbrar factura
1014+ resultado = wsdl.timbrar(usuario=pac.user,
1015+ contrasena=pac.password,
1016+ cfdi=fdata)
1017+ except Exception, e:
1018+ # Error al establecer comunicación con el PAC
1019+ logger.error(str(e))
1020+ raise osv.except_osv(_('Error'),
1021+ _(u"Can't get XML file from PAC."))
1022+
1023+ # Procesar los resultados obtenidos del PAC
1024+ mensaje = _(tools.ustr(resultado['mensaje']))
1025+ logger.debug(str(resultado))
1026+ valid_codes = ['200', '504']
1027+ if resultado['codigo'] in valid_codes:
1028+ # Proceso satisfactorio
1029+ timbre = resultado['timbre']
1030+ regex = '<cfdi:Complemento>(.*?)</cfdi:Complemento>'
1031+ timbref = re.search(regex, resultado['timbre']).group(1)
1032+
1033+ # Obtiene el sello del SAT
1034+ try:
1035+ selloSAT = re.search('selloSAT="(.+?)" ', timbref).group(1)
1036+ except:
1037+ selloSAT = ''
1038+
1039+ # Obtiene el nuúmero de certificado
1040+ try:
1041+ no_certificado = re.search('noCertificadoSAT="(.+?)" ', timbref).group(1)
1042+ except:
1043+ no_certificado = ''
1044+
1045+ # Obtiene la fecha en que se timbó la factura
1046+ try:
1047+ fecha = re.search('FechaTimbrado="(.+?)" ', timbref).group(1)
1048+ except:
1049+ fecha = ''
1050+
1051+ # Obtiene el folio de la factura
1052+ try:
1053+ uuid = re.search('UUID="(.+?)" ', timbref).group(1)
1054+ except:
1055+ uuid = ''
1056+
1057+ # Obtiene la cadena original
1058+ try:
1059+ version = re.search('version="(.+?)" ', timbref).group(1)
1060+ cadena = ('|').join([version,
1061+ uuid,
1062+ invoice.date_invoice_tz,
1063+ invoice.sello,
1064+ no_certificado])
1065+ cadena = "||{0}||".format(cadena)
1066+ except:
1067+ cadena = ''
1068+
1069+ cfdi_data = {
1070+ 'cfdi_sello': selloSAT,
1071+ 'cfdi_no_certificado': no_certificado,
1072+ 'cfdi_cadena_original': cadena,
1073+ 'cfdi_fecha_timbrado': fecha,
1074+ 'cfdi_folio_fiscal': uuid,
1075+ }
1076+ self.write(cr, uid, invoice_id, cfdi_data)
1077+
1078+ # Codifica el XML para almacenarlo
1079+ f = base64.encodestring(timbre or '')
1080+
1081+ msg += string.join([mensaje, ". Folio Fiscal ", uuid, "."])
1082+ else:
1083+ # El CFDI no ha sido timbrado
1084+ raise osv.except_osv(_(u'Warning'),
1085+ _(u'Stamped Code: %s. \n Stamped Message: %s.') %
1086+ (resultado['codigo'], mensaje))
1087+
1088+ return {'file': f, 'msg': msg, 'cfdi_xml': timbre}
1089+
1090+ def _get_file_cancel(self, cr, uid, inv_ids, context={}):
1091+ inv_ids = inv_ids[0]
1092+ atta_obj = self.pool.get('ir.attachment')
1093+ atta_id = atta_obj.search(cr, uid, [('res_id', '=', inv_ids), (
1094+ 'name', 'ilike', '%.xml')], context=context)
1095+ if atta_id:
1096+ atta_brw = atta_obj.browse(cr, uid, atta_id, context)[0]
1097+ inv_xml = atta_brw.datas or False
1098+ else:
1099+ inv_xml = False
1100+ raise osv.except_osv(('State of Cancellation!'), (
1101+ "This invoice hasn't stamped, so that not possible cancel."))
1102+ return {'file': inv_xml}
1103+
1104+ def sf_cancel(self, cr, uid, inv_ids, context=None):
1105+ msg = ''
1106+ invoice_id = inv_ids[0]
1107+ pac_params_obj = self.pool.get('params.pac')
1108+ invoice = self.browse(cr, uid, invoice_id, context=context)
1109+ xml_data = self.cancel_invoice_xml(cr, uid, invoice_id, context=context)
1110+
1111+ # ****************** upload file encoded at PAC ******************************************
1112+
1113+ pac_ids = pac_params_obj.search(cr, uid,
1114+ [('method_type', '=', 'pac_facturalo_cancelar'),
1115+ ('company_id', '=', invoice.company_emitter_id.id),
1116+ ('active', '=', True)
1117+ ], limit=1, context=context)
1118+
1119+ if not pac_ids:
1120+ raise osv.except_osv(_('Warning'),
1121+ _('Not found information from web services of PAC, verify that the configuration of PAC is correct'))
1122+
1123+ pac = pac_params_obj.browse(cr, uid, pac_ids, context)[0]
1124+
1125+ #####################################################################
1126+
1127+ if pac.user == 'pruebas':
1128+ msg += _(u'WARNING, CANCEL IN TEST!!!!\n\n')
1129+
1130+ try:
1131+ from SOAPpy import Types
1132+
1133+ wsdl = WSDL.SOAPProxy(pac.url_webservice, pac.namespace)
1134+
1135+ # Webservice configuration
1136+ wsdl.soapproxy.config.dumpSOAPOut = 0
1137+ wsdl.soapproxy.config.dumpSOAPIn = 0
1138+ wsdl.soapproxy.config.debug = 0
1139+ wsdl.config.preventescape = 1
1140+ wsdl.soapproxy.config.dict_encoding = 'UTF-8'
1141+
1142+ # SUBIR factura al PAC
1143+ resultado = wsdl.cancelar(usuario=pac.user,
1144+ # For test environment uncomment this and comment contrasena
1145+ token=pac.password,
1146+ # contrasena=pac.password,
1147+ xmlBytes=Types.base64BinaryType(xml_data))
1148+
1149+ except Exception, e:
1150+ # Error al establecer comunicación con el PAC
1151+ logger.debug(str(e))
1152+ raise osv.except_osv(_('Error'),
1153+ _(u"Can't get XML file from PAC."))
1154+
1155+ #######################################################################
1156+ # Procesar los resultados obtenidos del PAC
1157+
1158+ if not(resultado['codEstatus'] == '200'):
1159+ mensaje = _(resultado['codMensaje'])
1160+ # error
1161+ raise osv.except_osv(_(u'Error'),
1162+ _(u'Stamped Code: %s. \n Stamped Message: %s.') %
1163+ (resultado['codEstatus'], mensaje))
1164+
1165+ cancel_status = ['201', '202']
1166+ if not(resultado['estatusUUIDs'] in cancel_status):
1167+ errors = {
1168+ '203':_('UUID No corresponde al emisor'),
1169+ '204':_('UUID No aplicable para cancelación'),
1170+ '205':_('UUID No existe')
1171+ }
1172+ # error
1173+ raise osv.except_osv(_(u'Error'),
1174+ _(u'Stamped Code: %s. \n Stamped Message: %s.') %
1175+ (resultado['estatusUUIDs'],
1176+ errors[resultado['estatusUUIDs']]))
1177+
1178+ # Invoice canceled, save the cancelation date on database
1179+ self.write(cr, uid, [invoice_id], {'cfdi_fecha_cancelacion':time.strftime('%Y-%m-%d %H:%M:%S')}, context=None)
1180+
1181+ return {'message':msg, 'status':resultado['codEstatus']}
1182+
1183+ def cancel_invoice_dict(self, cr, uid, ids, context={}):
1184+ """ This function create a dictionary with the necesary data to create the DigestValue.
1185+ @params
1186+ default params
1187+ """
1188+ from lxml import etree
1189+ from openerp import SUPERUSER_ID
1190+
1191+ # get user's timezone
1192+ user_pool = self.pool.get('res.users')
1193+ user = user_pool.browse(cr, SUPERUSER_ID, uid)
1194+ tz = timezone(user.partner_id.tz) or pytz.utc
1195+
1196+ # get localized dates
1197+ date = datetime.now(pytz.utc).astimezone(tz)
1198+
1199+
1200+ invoice = self.browse(cr, uid, ids, context=context)
1201+ xmlns = "http://cancelacfd.sat.gob.mx"
1202+ xsd = 'http://www.w3.org/2001/XMLSchema'
1203+ xsi = 'http://www.w3.org/2001/XMLSchema-instance'
1204+ signed_xmlns = 'http://www.w3.org/2000/09/xmldsig#'
1205+
1206+ nsmap = {None: xmlns,
1207+ 'xsd':xsd,
1208+ 'xsi':xsi}
1209+
1210+ signed_nsmap = {None: signed_xmlns,
1211+ 'xsd':xsd,
1212+ 'xsi':xsi}
1213+ # section: Cancelation
1214+ # create XML
1215+ cancelacion = etree.Element('Cancelacion', nsmap=nsmap)
1216+ cancelacion.set('Fecha', date.strftime('%Y-%m-%dT%H:%M:%S'))
1217+ cancelacion.set('RfcEmisor', invoice.company_id.partner_id.vat_split)
1218+
1219+ folios = etree.Element('Folios')
1220+ uuid = etree.Element('UUID')
1221+ uuid.text = invoice.cfdi_folio_fiscal
1222+ folios.append(uuid)
1223+ cancelacion.append(folios)
1224+
1225+ signature = etree.Element('Signature', nsmap={None:signed_xmlns})
1226+ signedinfo = etree.Element('SignedInfo', nsmap=signed_nsmap)
1227+
1228+ canonicalizacion = etree.Element('CanonicalizationMethod')
1229+ canonicalizacion.set('Algorithm', "http://www.w3.org/TR/2001/REC-xml-c14n-20010315")
1230+ signaturemethod = etree.Element('SignatureMethod')
1231+ signaturemethod.set('Algorithm', "http://www.w3.org/2000/09/xmldsig#rsa-sha1")
1232+ reference = etree.Element('Reference')
1233+ reference.set('URI', "")
1234+ transforms = etree.Element('Transforms')
1235+ transform = etree.Element('Transform')
1236+ transform.set('Algorithm', "http://www.w3.org/2000/09/xmldsig#enveloped-signature")
1237+ digestme = etree.Element('DigestMethod')
1238+ digestme.set('Algorithm', "http://www.w3.org/2000/09/xmldsig#sha1")
1239+ digestva = etree.Element('DigestValue')
1240+ digestva.text = 'TEMPLATE'
1241+
1242+ signedinfo.append(canonicalizacion)
1243+ signedinfo.append(signaturemethod)
1244+ signedinfo.append(reference)
1245+
1246+ transforms.append(transform)
1247+ reference.append(transforms)
1248+ reference.append(digestme)
1249+ reference.append(digestva)
1250+ signature.append(signedinfo)
1251+
1252+ signaturevalue = etree.Element('SignatureValue')
1253+ signaturevalue.text = 'TEMPLATE'
1254+ signature.append(signaturevalue)
1255+
1256+ keyinfo = etree.Element('KeyInfo')
1257+ # Issuername & Serialnumber not added because library
1258+ # auto fill the fields on X509IssuerSerial
1259+ # see www.aleksey.com/pipermail/xmlsec/2007/008125.html
1260+ x509 = etree.Element('X509Data')
1261+ xISerial = etree.Element('X509IssuerSerial')
1262+ xISerial.text = ''
1263+ xCertificate = etree.Element('X509Certificate')
1264+ xCertificate.text = ''
1265+
1266+ x509.append(xISerial)
1267+ x509.append(xCertificate)
1268+ keyinfo.append(x509)
1269+ signature.append(keyinfo)
1270+
1271+ cancelacion.append(signature)
1272+ # pretty string
1273+ logger.debug('XML file %s' % etree.tostring(cancelacion))
1274+ return etree.tostring(cancelacion)
1275+
1276+ def cancel_invoice_xml(self, cr, uid, ids, context=None):
1277+ """ This function call at cancel_invoice_dict to cancel a invoice.
1278+ @params
1279+ default params
1280+ """
1281+ import pyxmldsig
1282+ import tempfile
1283+
1284+ invoice = self.browse(cr, uid, ids, context=context)
1285+ pool_company = self.pool.get('res.company')
1286+
1287+ data_xml = self.cancel_invoice_dict(cr, uid, ids, context=context)
1288+
1289+ new_file = tempfile.NamedTemporaryFile(delete=False)
1290+ new_file.write(data_xml)
1291+ new_file.close()
1292+
1293+ cert_id = pool_company._get_current_certificate(cr, uid, [invoice.company_emitter_id.id],
1294+ context=context)[invoice.company_emitter_id.id]
1295+
1296+ cert_id = cert_id and self.pool.get('res.company.facturae.certificate') \
1297+ .browse(cr, uid, [cert_id], context=context)[0] or False
1298+
1299+ # Get certificate files for sign the xml
1300+ fname_cer_pem = fname_key_pem = False
1301+ name = 'openerp_' + (cert_id.serial_number or '') + '__certificate__'
1302+ if cert_id:
1303+ try:
1304+
1305+ fname_cer_pem = self.binary2file(cr, uid, ids,
1306+ cert_id.certificate_file_pem,
1307+ name,
1308+ '.cer.pem')
1309+ except:
1310+ raise osv.except_osv(_('Error !'),
1311+ _('Not captured a CERTIFICATE file in format PEM, in the company!'))
1312+
1313+
1314+ try:
1315+ fname_key_pem = self.binary2file(cr, uid, ids,
1316+ cert_id.certificate_key_file_pem,
1317+ name,
1318+ '.key.pem')
1319+ except:
1320+ raise osv.except_osv(_('Error !'), _(
1321+ 'Not captured a KEY file in format PEM, in the company!'))
1322+
1323+ else:
1324+ raise osv.except_osv(_('Warning !'),
1325+ _('Check date of invoice and the validity of certificate'))
1326+
1327+ signed_xml = pyxmldsig.sign_file(template_file=new_file.name,
1328+ cert_file=fname_cer_pem.encode('ascii', 'ignore'),
1329+ key_file=fname_key_pem.encode('ascii', 'ignore'),
1330+ password=cert_id.certificate_password.encode('ascii', 'ignore'))
1331+
1332+ logger.debug('Signed file %s' % signed_xml)
1333+
1334+ return signed_xml # data_dict
1335
1336=== added file 'l10n_mx_facturae_pac_facturalo/invoice.pyc'
1337Binary files l10n_mx_facturae_pac_facturalo/invoice.pyc 1970-01-01 00:00:00 +0000 and l10n_mx_facturae_pac_facturalo/invoice.pyc 2014-06-25 00:35:08 +0000 differ
1338=== added file 'l10n_mx_facturae_pac_facturalo/ir_attachment_facturae.py'
1339--- l10n_mx_facturae_pac_facturalo/ir_attachment_facturae.py 1970-01-01 00:00:00 +0000
1340+++ l10n_mx_facturae_pac_facturalo/ir_attachment_facturae.py 2014-06-25 00:35:08 +0000
1341@@ -0,0 +1,40 @@
1342+# -*- encoding: utf-8 -*-
1343+###########################################################################
1344+# Module Writen to OpenERP, Open Source Management Solution
1345+#
1346+# Copyright (c) 2013 OpenPyme - http://www.openpyme.mx
1347+# All Rights Reserved.
1348+# info OpenPyme (info@openpyme.mx)
1349+# Coded by: Agustín Cruz (agustin.cruz@openpyme.mx)
1350+#
1351+# This program is free software: you can redistribute it and/or modify
1352+# it under the terms of the GNU Affero General Public License as
1353+# published by the Free Software Foundation, either version 3 of the
1354+# License, or (at your option) any later version.
1355+#
1356+# This program is distributed in the hope that it will be useful,
1357+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1358+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1359+# GNU Affero General Public License for more details.
1360+#
1361+# You should have received a copy of the GNU Affero General Public License
1362+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1363+#
1364+##############################################################################
1365+
1366+from openerp.osv import fields, osv
1367+
1368+
1369+class ir_attachment_facturae_mx(osv.osv):
1370+ _inherit = 'ir.attachment.facturae.mx'
1371+
1372+ def _get_type(self, cr, uid, ids=None, context=None):
1373+ types = super(ir_attachment_facturae_mx, self)._get_type(
1374+ cr, uid, ids, context=context)
1375+ types.extend([('cfdi32', 'CFDI 3.2')])
1376+ return types
1377+
1378+ _columns = {
1379+ 'type': fields.selection(_get_type, 'Type', type='char', size=64,
1380+ required=True, readonly=True, help="Type of Electronic Invoice"),
1381+ }
1382
1383=== added file 'l10n_mx_facturae_pac_facturalo/ir_attachment_facturae.pyc'
1384Binary files l10n_mx_facturae_pac_facturalo/ir_attachment_facturae.pyc 1970-01-01 00:00:00 +0000 and l10n_mx_facturae_pac_facturalo/ir_attachment_facturae.pyc 2014-06-25 00:35:08 +0000 differ
1385=== added file 'l10n_mx_facturae_pac_facturalo/ir_sequence_approval.py'
1386--- l10n_mx_facturae_pac_facturalo/ir_sequence_approval.py 1970-01-01 00:00:00 +0000
1387+++ l10n_mx_facturae_pac_facturalo/ir_sequence_approval.py 2014-06-25 00:35:08 +0000
1388@@ -0,0 +1,43 @@
1389+# -*- encoding: utf-8 -*-
1390+###########################################################################
1391+# Module Writen to OpenERP, Open Source Management Solution
1392+#
1393+# Copyright (c) 2013 OpenPyme - http://www.openpyme.mx
1394+# All Rights Reserved.
1395+# info OpenPyme (info@openpyme.mx)
1396+# Coded by: Agustín Cruz (agustin.cruz@openpyme.mx)
1397+#
1398+# This program is free software: you can redistribute it and/or modify
1399+# it under the terms of the GNU Affero General Public License as
1400+# published by the Free Software Foundation, either version 3 of the
1401+# License, or (at your option) any later version.
1402+#
1403+# This program is distributed in the hope that it will be useful,
1404+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1405+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1406+# GNU Affero General Public License for more details.
1407+#
1408+# You should have received a copy of the GNU Affero General Public License
1409+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1410+#
1411+##############################################################################
1412+
1413+from openerp.osv import fields, osv
1414+
1415+
1416+class ir_sequence_approval(osv.osv):
1417+ _inherit = 'ir.sequence.approval'
1418+
1419+ def _get_type(self, cr, uid, ids=None, context=None):
1420+ types = super(ir_sequence_approval, self)._get_type(
1421+ cr, uid, ids, context=context)
1422+ """TODO: Encontrar una forma en la que se pueda tener
1423+ instalado al mismo tiempo este módulo y el de SF
1424+ """
1425+ types.extend([('cfdi32', 'CFDI 3.2')])
1426+ return types
1427+
1428+ _columns = {
1429+ 'type': fields.selection(_get_type, 'Type', type='char', size=64,
1430+ required=True, help="Type of Electronic Invoice"),
1431+ }
1432
1433=== added file 'l10n_mx_facturae_pac_facturalo/ir_sequence_approval.pyc'
1434Binary files l10n_mx_facturae_pac_facturalo/ir_sequence_approval.pyc 1970-01-01 00:00:00 +0000 and l10n_mx_facturae_pac_facturalo/ir_sequence_approval.pyc 2014-06-25 00:35:08 +0000 differ
1435=== added file 'l10n_mx_facturae_pac_facturalo/params_pac.py'
1436--- l10n_mx_facturae_pac_facturalo/params_pac.py 1970-01-01 00:00:00 +0000
1437+++ l10n_mx_facturae_pac_facturalo/params_pac.py 2014-06-25 00:35:08 +0000
1438@@ -0,0 +1,45 @@
1439+# -*- encoding: utf-8 -*-
1440+###########################################################################
1441+# Module Writen to OpenERP, Open Source Management Solution
1442+#
1443+# Copyright (c) 2013 OpenPyme - http://www.openpyme.mx
1444+# All Rights Reserved.
1445+# info OpenPyme (info@openpyme.mx)
1446+# Coded by: Agustín Cruz (agustin.cruz@openpyme.mx)
1447+#
1448+# This program is free software: you can redistribute it and/or modify
1449+# it under the terms of the GNU Affero General Public License as
1450+# published by the Free Software Foundation, either version 3 of the
1451+# License, or (at your option) any later version.
1452+#
1453+# This program is distributed in the hope that it will be useful,
1454+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1455+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1456+# GNU Affero General Public License for more details.
1457+#
1458+# You should have received a copy of the GNU Affero General Public License
1459+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1460+#
1461+##############################################################################
1462+
1463+from openerp.osv import fields, osv
1464+from openerp.tools.translate import _
1465+
1466+
1467+class params_pac(osv.osv):
1468+ _inherit = 'params.pac'
1469+
1470+ def _get_method_type_selection(self, cr, uid, context=None):
1471+ types = super(params_pac, self)._get_method_type_selection(
1472+ cr, uid, context=context)
1473+ types.extend([
1474+ ('pac_facturalo_firmar', _('PAC Factura-lo - Sign')),
1475+ ('pac_facturalo_cancelar', _('PAC Factura-lo - Cancel')),
1476+ ])
1477+ return types
1478+
1479+ _columns = {
1480+ 'method_type': fields.selection(_get_method_type_selection,
1481+ "Process to perform", type='char', size=64, required=True,
1482+ help='Type of process to configure in this pac'),
1483+ }
1484
1485=== added file 'l10n_mx_facturae_pac_facturalo/params_pac.pyc'
1486Binary files l10n_mx_facturae_pac_facturalo/params_pac.pyc 1970-01-01 00:00:00 +0000 and l10n_mx_facturae_pac_facturalo/params_pac.pyc 2014-06-25 00:35:08 +0000 differ
1487=== added file 'l10n_mx_facturae_pac_facturalo/pyxmldsig.py'
1488--- l10n_mx_facturae_pac_facturalo/pyxmldsig.py 1970-01-01 00:00:00 +0000
1489+++ l10n_mx_facturae_pac_facturalo/pyxmldsig.py 2014-06-25 00:35:08 +0000
1490@@ -0,0 +1,578 @@
1491+"""
1492+pyxmldsig.py:
1493+
1494+A Python module to create and verify XML Digital Signatures (XML-DSig)
1495+
1496+This is a simple interface to the PyXMLSec library, aiming to provide a more
1497+pythonic API suitable for Python applications.
1498+See http://www.decalage.info/python/pyxmldsig to download the latest version.
1499+
1500+May be used as a command-line tool or as a Python module.
1501+This code is inspired from PyXMLSec samples, with a more pythonic interface:
1502+http://pyxmlsec.labs.libre-entreprise.org/index.php?section=examples
1503+
1504+AUTHOR: Philippe Lagadec (decalage at laposte dot net)
1505+
1506+PROJECT WEBSITE: http://www.decalage.info/python/pyxmldsig
1507+
1508+LICENSE:
1509+
1510+Copyright (c) 2009-2010, Philippe Lagadec (decalage at laposte dot net)
1511+
1512+Permission to use, copy, modify, and/or distribute this software for any
1513+purpose with or without fee is hereby granted, provided that the above copyright
1514+notice and this permission notice appear in all copies.
1515+
1516+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
1517+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
1518+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
1519+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
1520+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1521+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1522+PERFORMANCE OF THIS SOFTWARE.
1523+
1524+
1525+USAGE AS A TOOL:
1526+pyxmldsig.py <data.xml> -k <key-file.pem> [-c cert-file.pem] [-p password]
1527+
1528+USAGE IN A PYTHON APPLICATION:
1529+
1530+import pyxmldsig
1531+
1532+# simple function interface:
1533+signed_xml = pyxmldsig.sign_file(template_file='myfile.xml',
1534+ key_file='mykey.pem', cert_file='myx509cert.pem', password='mypassword')
1535+print signed_xml
1536+
1537+# sign with class interface:
1538+xdsig = pyxmldsig.Xmldsig(key_file='mykey.pem', cert_file='myx509cert.pem',
1539+ password='mypassword')
1540+signed_xml1 = xdsig.sign_file('myfile.xml')
1541+signed_xml2 = xdsig.sign_file(pyxmldsig.TEMPLATE_WITH_CERT)
1542+
1543+# verify with class interface:
1544+xdsig2 = pyxmldsig.Xmldsig()
1545+xdsig2.load_certs(['cacert.pem', 'myx509cert.pem'])
1546+assert xdsig2.verify_xmlstring(signed_xml1) == True
1547+assert xdsig2.verify_xmlstring(signed_xml2) == True
1548+
1549+REQUIREMENTS:
1550+- pyxmlsec: http://pyxmlsec.labs.libre-entreprise.org/
1551+- xmlsec: http://www.aleksey.com/xmlsec/
1552+- libxml2: http://xmlsoft.org
1553+- On Windows see also this site for convenient compiled binaries:
1554+ http://returnbooleantrue.blogspot.com/2009/04/pyxmlsec-windows-binary.html
1555+
1556+REFERENCES:
1557+- http://www.w3.org/TR/xmldsig-core/
1558+"""
1559+
1560+__version__ = '0.05'
1561+
1562+#=== CHANGELOG ================================================================
1563+
1564+# 2009-09-10 v0.01 PL: - first version, inspired from pyxmlsec samples
1565+# 2009-09-14 v0.02 PL: - small improvements, license
1566+# 2010-05-11 v0.03 PL: - renamed to pyxmldsig
1567+# - X509 cert is now optional
1568+# - a key password may be provided
1569+# 2010-06-29 v0.04 PL: - added Xmldsig class to load a key once for several
1570+# signatures
1571+# - added signature verification
1572+# - added simple XML-DSIG templates
1573+# 2010-07-06 v0.05 PL: - added load_cert to load several certificates at once
1574+
1575+#=== TODO =====================================================================
1576+
1577+# - add option to generate XML-DSig template automatically, appended to a chosen
1578+# node.
1579+# - add option to improve detached signature with http URI: fix URI after
1580+# signature.
1581+# - check if all temporary xmlsec objects are destroyed after each operation
1582+# - find a solution to load a certificate with a key name to allow signature
1583+# verification without embedded X509 cert
1584+# - add option to use keys manager or single key?
1585+
1586+#=== IMPORTS ==================================================================
1587+
1588+import sys
1589+
1590+try:
1591+ import libxml2
1592+except ImportError:
1593+ raise ImportError, "libxml2 is required: see http://xmlsoft.org"
1594+
1595+try:
1596+ import xmlsec
1597+except ImportError:
1598+ raise ImportError, "pyxmlsec is required: see http://pyxmlsec.labs.libre-entreprise.org"
1599+
1600+
1601+#=== CONSTANTS ================================================================
1602+
1603+# XML Signature template with X509 certificate:
1604+# - the X.509 cert tag must be empty, else another one will be appended
1605+# - KeyName is optional
1606+TEMPLATE_WITH_CERT = \
1607+"""<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
1608+<SignedInfo>
1609+ <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
1610+ <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
1611+ <Reference URI="">
1612+ <Transforms>
1613+ <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
1614+ </Transforms>
1615+ <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
1616+ <DigestValue>TEMPLATE</DigestValue>
1617+ </Reference>
1618+</SignedInfo>
1619+<SignatureValue>TEMPLATE</SignatureValue>
1620+<KeyInfo>
1621+ <KeyName/>
1622+ <X509Data>
1623+ <X509Certificate></X509Certificate>
1624+ </X509Data>
1625+</KeyInfo>
1626+</Signature>
1627+"""
1628+
1629+# XML Signature template without X509 certificate:
1630+# - KeyInfo / KeyName is optional
1631+TEMPLATE_WITHOUT_CERT = \
1632+"""<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
1633+<SignedInfo>
1634+ <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
1635+ <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
1636+ <Reference URI="">
1637+ <Transforms>
1638+ <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
1639+ </Transforms>
1640+ <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
1641+ <DigestValue>TEMPLATE</DigestValue>
1642+ </Reference>
1643+</SignedInfo>
1644+<SignatureValue>TEMPLATE</SignatureValue>
1645+<KeyInfo>
1646+ <KeyName/>
1647+</KeyInfo>
1648+</Signature>
1649+"""
1650+
1651+#=== CLASSES ==================================================================
1652+
1653+class Xmldsig (object):
1654+ """
1655+ class to sign and verify XML signatures (XML DSig)
1656+ """
1657+
1658+ def __init__(self, key_file=None, cert_file=None, password='', key_name=None):
1659+ """
1660+ - key_file: str, filename of PEM file containing the private key.
1661+ (the file should NOT be password-protected)
1662+ - cert_file: str, filename of PEM file containing the X509 certificate.
1663+ (optional: can be None)
1664+ - password: str, password to open key file, or None if no password.
1665+ - key_name: str, name for the key in the signature, or None if omitted.
1666+ """
1667+ self.dsig_ctx = None
1668+ # TEST: single key
1669+ self.key = None
1670+ # Create and initialize keys manager
1671+ self.keysmngr = xmlsec.KeysMngr()
1672+ if self.keysmngr is None:
1673+ raise RuntimeError, "Error: failed to create keys manager."
1674+ if xmlsec.cryptoAppDefaultKeysMngrInit(self.keysmngr) < 0:
1675+ self.keysmngr.destroy()
1676+ raise RuntimeError, "Error: failed to initialize keys manager."
1677+ # load key
1678+ self.load(key_file, cert_file, password, key_name)
1679+
1680+
1681+ def load(self, key_file=None, cert_file=None, password='', key_name=None):
1682+ """
1683+ load a private key and/or a public certificate for signature and verification
1684+
1685+ - key_file: str, filename of PEM file containing the private key.
1686+ (the file should NOT be password-protected)
1687+ - cert_file: str, filename of PEM file containing the X509 certificate.
1688+ (optional: can be None)
1689+ - password: str, password to open key file, or None if no password.
1690+ """
1691+ # TODO: try except block to destroy key if error
1692+ if key_file is not None:
1693+ # Load private key, with optional password
1694+ # print 'PASSWORD: %s' % password
1695+ key = xmlsec.cryptoAppKeyLoad(filename=key_file,
1696+ format=xmlsec.KeyDataFormatPem, pwd=password,
1697+ pwdCallback=None, pwdCallbackCtx=None)
1698+ # API references:
1699+ # http://pyxmlsec.labs.libre-entreprise.org/docs/html/xmlsec-module.html#cryptoAppKeyLoad
1700+ # http://www.aleksey.com/xmlsec/api/xmlsec-app.html#XMLSECCRYPTOAPPKEYLOAD
1701+ # http://www.aleksey.com/xmlsec/api/xmlsec-keysdata.html#XMLSECKEYDATAFORMAT
1702+ if key is None:
1703+ raise RuntimeError, "Error: failed to load private PEM key from \"%s\"" % key_file
1704+ if key_name is not None:
1705+ # Set key name
1706+ if key.setName(key_name) < 0:
1707+ raise RuntimeError, "Error: failed to set key name to \"%s\"" % key_name
1708+ if cert_file is not None:
1709+ # Load certificate and add to the key
1710+ if xmlsec.cryptoAppKeyCertLoad(key, cert_file, xmlsec.KeyDataFormatPem) < 0:
1711+ raise RuntimeError, "Error: failed to load PEM certificate \"%s\"" % cert_file
1712+ # load key into manager:
1713+ if xmlsec.cryptoAppDefaultKeysMngrAdoptKey(self.keysmngr, key) < 0:
1714+ raise RuntimeError, "Error: failed to load key into keys manager"
1715+
1716+ elif cert_file is not None:
1717+ # case when we only want to load a cert without private key
1718+ if self.keysmngr.certLoad(cert_file, xmlsec.KeyDataFormatPem,
1719+ xmlsec.KeyDataTypeTrusted) < 0:
1720+ # is it better to keep the keys manager if an error occurs?
1721+ # self.keysmngr.destroy()
1722+ raise RuntimeError, "Error: failed to load PEM certificate from \"%s\"" % cert_file
1723+ # THIS DOES NOT WORK: it seems a certificate cannot be loaded like a key with a name...
1724+# # key = xmlsec.cryptoAppKeyLoad(filename = cert_file,
1725+# # format = xmlsec.KeyDataFormatPem, pwd = password,
1726+# # pwdCallback = None, pwdCallbackCtx = None)
1727+# # if key is None:
1728+# # raise RuntimeError, "Error: failed to load PEM certificate from \"%s\"", cert_file
1729+# # if key_name is not None:
1730+# # # Set key name
1731+# # if key.setName(key_name) < 0:
1732+# # raise RuntimeError, "Error: failed to set key name to \"%s\"" % key_name
1733+# # # load key into manager:
1734+# # if xmlsec.cryptoAppDefaultKeysMngrAdoptKey(self.keysmngr, key) < 0:
1735+# # raise RuntimeError, "Error: failed to load certificate into keys manager"
1736+
1737+ def load_certs(self, certificates):
1738+ """
1739+ load one or several certificates into the keys manager for signature
1740+ verification. For example, load the CA cert, any number of intermediate
1741+ certs, and the cert corresponding to the key used for the signature.
1742+
1743+ certificates: list or tuple containing certificate file names
1744+ """
1745+ for cert in certificates:
1746+ self.load(cert_file=cert)
1747+
1748+
1749+ def sign_file (self, template_file):
1750+ """
1751+ Sign a XML file using the signature template in the XML file.
1752+ The certificate from cert_file is placed in the <dsig:X509Data/> node.
1753+
1754+ - template_file: str, filename of XML file containing an XML-DSig template.
1755+
1756+ Returns a string containing the signed XML data.
1757+ Raises an exception if an error occurs.
1758+ """
1759+ xmlstring = open(template_file).read()
1760+ return self.sign_xmlstring(xmlstring)
1761+
1762+
1763+ def sign_xmlstring (self, xmlstring):
1764+ """
1765+ Sign xmlstring using the signature template in xmlstring.
1766+ The certificate from cert_file is placed in the <dsig:X509Data/> node.
1767+
1768+ - xmlstring: str, XML data containing an XML-DSig template.
1769+
1770+ Returns a string containing the signed XML data.
1771+ Raises an exception if an error occurs.
1772+ """
1773+ # try block to ensure cleanup is called even if an exception is raised:
1774+ try:
1775+ # Create signature context
1776+ self._create_context()
1777+ # Load template
1778+ doc = self._parse_xmlstring(xmlstring)
1779+ # find the XML-DSig start node
1780+ node = xmlsec.findNode(doc.getRootElement(), xmlsec.NodeSignature,
1781+ xmlsec.DSigNs)
1782+ if node is None:
1783+ raise RuntimeError, "Error: XML-DSIG node not found"
1784+ # Sign the template
1785+ if self.dsig_ctx.sign(node) < 0:
1786+ raise RuntimeError, "Error: signature failed"
1787+ output = str(doc)
1788+ output = output.replace('\n', '')
1789+ finally:
1790+ # cleanup, even if an exception has been raised:
1791+ self._cleanup_context()
1792+ if doc is not None:
1793+ doc.freeDoc()
1794+ # return output if no exception was raised:
1795+ return output
1796+
1797+
1798+ def verify_file (self, xmlfile):
1799+ """
1800+ Verify signature in XML file using the loaded certificate.
1801+
1802+ - xmlfile: str, filename of XML file containing an XML-DSig signature.
1803+
1804+ Returns True if the signature is valid, False otherwise.
1805+ Raises an exception if an error occurs.
1806+ """
1807+ xmlstring = open(xmlfile).read()
1808+ return self.verify_xmlstring(xmlstring)
1809+
1810+
1811+ def verify_xmlstring (self, xmlstring):
1812+ """
1813+ Verify signature in xmlstring using the loaded certificate.
1814+
1815+ - xmlstring: str, XML data containing an XML-DSig signature.
1816+
1817+ Returns True if the signature is valid, False otherwise.
1818+ Raises an exception if an error occurs.
1819+ """
1820+ doc = None
1821+ # try block to ensure cleanup is called even if an exception is raised:
1822+ try:
1823+ # Create signature context
1824+ self._create_context()
1825+ # Load XML data
1826+ doc = self._parse_xmlstring(xmlstring)
1827+ # find the XML-DSig start node
1828+ node = xmlsec.findNode(doc.getRootElement(), xmlsec.NodeSignature,
1829+ xmlsec.DSigNs)
1830+ if node is None:
1831+ raise RuntimeError, "Error: XML-DSIG node not found"
1832+ # Verify signature
1833+ if self.dsig_ctx.verify(node) < 0:
1834+ # An error occured, the signature could not be verified
1835+ raise RuntimeError, "Error: An error occured, the signature could not be verified"
1836+ if self.dsig_ctx.status == xmlsec.DSigStatusSucceeded:
1837+ # Signature is OK
1838+ return True
1839+ else:
1840+ # Signature is INVALID
1841+ return False
1842+ finally:
1843+ # cleanup, even if an exception has been raised:
1844+ self._cleanup_context()
1845+ if doc is not None:
1846+ doc.freeDoc()
1847+ # return output if no exception was raised:
1848+ return output
1849+
1850+
1851+ def _parse_xmlstring(self, xmlstring):
1852+ """
1853+ parse XML string containing XML-DSIG nodes for signature (template) or
1854+ verification (signed data)
1855+ """
1856+ # doc = libxml2.parseFile(tmpl_file)
1857+ doc = libxml2.parseDoc(xmlstring)
1858+ if doc is None or doc.getRootElement() is None:
1859+ raise RuntimeError, "Error: unable to parse XML data"
1860+ return doc
1861+
1862+
1863+ def _create_context(self):
1864+ """
1865+ create the xmlsec context for signature or verification
1866+ """
1867+ self.dsig_ctx = xmlsec.DSigCtx(self.keysmngr)
1868+ if self.dsig_ctx is None:
1869+ raise RuntimeError, "Error: failed to create signature context"
1870+
1871+
1872+ def _cleanup_context (self):
1873+ """
1874+ cleanup the xmlsec context in case of error
1875+ """
1876+ if self.dsig_ctx is not None:
1877+ self.dsig_ctx.destroy()
1878+ self.dsig_ctx = None
1879+
1880+
1881+
1882+
1883+#=== FUNCTIONS ================================================================
1884+
1885+def sign_file(template_file, key_file, cert_file=None, password='', key_name=None):
1886+ """
1887+ Sign a XML file using private key from key_file and the signature template
1888+ in the XML file.
1889+ The certificate from cert_file is placed in the <dsig:X509Data/> node.
1890+
1891+ - template_file: str, filename of XML file containing an XML-DSig template.
1892+ - key_file: str, filename of PEM file containing the private key.
1893+ (the file should NOT be password-protected)
1894+ - cert_file: str, filename of PEM file containing the X509 certificate.
1895+ (optional: can be None)
1896+ - password: str, password to open key file, or None if no password.
1897+
1898+ Returns a string containing the signed XML data.
1899+ Raises an exception if an error occurs.
1900+ """
1901+ xmldsig = Xmldsig(key_file, cert_file, password, key_name)
1902+ return xmldsig.sign_file(template_file)
1903+# # xmlstring = open(template_file).read()
1904+# # return sign_xmlstring(xmlstring, key_file, cert_file, password)
1905+
1906+
1907+def sign_xmlstring(xmlstring, key_file, cert_file=None, password='', key_name=None):
1908+ """
1909+ Sign xmlstring using private key from key_file and the signature template
1910+ in xmlstring.
1911+ The certificate from cert_file is placed in the <dsig:X509Data/> node.
1912+
1913+ - xmlstring: str, XML data containing an XML-DSig template.
1914+ - key_file: str, filename of PEM file containing the private key.
1915+ (the file should NOT be password-protected)
1916+ - cert_file: str, filename of PEM file containing the X509 certificate.
1917+ (optional: can be None)
1918+ - password: str, password to open key file, or "" if no password.
1919+ (never use None because libxmlsec will ask on the console)
1920+
1921+ Returns a string containing the signed XML data.
1922+ Raises an exception if an error occurs.
1923+ """
1924+ xmldsig = Xmldsig(key_file, cert_file, password, key_name)
1925+ return xmldsig.sign_xmlstring(xmlstring)
1926+# # # Load template
1927+# # #doc = libxml2.parseFile(tmpl_file)
1928+# # doc = libxml2.parseDoc(xmlstring)
1929+# # if doc is None or doc.getRootElement() is None:
1930+# # raise RuntimeError, "Error: unable to parse XML data"
1931+# #
1932+# # # try block to ensure cleanup is called even if an exception is raised:
1933+# # try:
1934+# # dsig_ctx = None
1935+# #
1936+# # # Find XML-DSig start node
1937+# # node = xmlsec.findNode(doc.getRootElement(), xmlsec.NodeSignature,
1938+# # xmlsec.DSigNs)
1939+# # if node is None:
1940+# # raise RuntimeError, "Error: start node not found"
1941+# #
1942+# # # Create signature context, we don't need keys manager in this example
1943+# # dsig_ctx = xmlsec.DSigCtx()
1944+# # if dsig_ctx is None:
1945+# # raise RuntimeError, "Error: failed to create signature context"
1946+# #
1947+# # # Load private key, assuming that there is not password
1948+# # #print 'PASSWORD: %s' % password
1949+# # key = xmlsec.cryptoAppKeyLoad(filename = key_file,
1950+# # format = xmlsec.KeyDataFormatPem, pwd = password,
1951+# # pwdCallback = None, pwdCallbackCtx = None)
1952+# # # API references:
1953+# # # http://pyxmlsec.labs.libre-entreprise.org/docs/html/xmlsec-module.html#cryptoAppKeyLoad
1954+# # # http://www.aleksey.com/xmlsec/api/xmlsec-app.html#XMLSECCRYPTOAPPKEYLOAD
1955+# # # http://www.aleksey.com/xmlsec/api/xmlsec-keysdata.html#XMLSECKEYDATAFORMAT
1956+# # if key is None:
1957+# # raise RuntimeError, "Error: failed to load private PEM key from \"%s\"" % key_file
1958+# # dsig_ctx.signKey = key
1959+# #
1960+# # if cert_file is not None:
1961+# # # Load certificate and add to the key
1962+# # ## if not check_filename(cert_file):
1963+# # ## return cleanup(doc, dsig_ctx)
1964+# # if xmlsec.cryptoAppKeyCertLoad(key, cert_file, xmlsec.KeyDataFormatPem) < 0:
1965+# # raise RuntimeError, "Error: failed to load PEM certificate \"%s\"" % cert_file
1966+# #
1967+# # # Set key name to the file name, this is just an example!
1968+# # if key.setName(key_file) < 0:
1969+# # raise RuntimeError, "Error: failed to set key name for key from \"%s\"" % key_file
1970+# #
1971+# # # Sign the template
1972+# # if dsig_ctx.sign(node) < 0:
1973+# # raise RuntimeError, "Error: signature failed"
1974+# #
1975+# # ## # Print signed document to stdout
1976+# # ## doc.dump("-")
1977+# # ## doc.saveFile("test.xml")
1978+# # output = str(doc)
1979+# # finally:
1980+# # # cleanup, even if an exception has been raised:
1981+# # cleanup(doc, dsig_ctx)
1982+# # # return output if no exception was raised:
1983+# # return output
1984+
1985+
1986+# #def cleanup(doc=None, dsig_ctx=None, res=-1):
1987+# # """
1988+# # Cleans libxml2 context after usage.
1989+# # """
1990+# # if dsig_ctx is not None:
1991+# # dsig_ctx.destroy()
1992+# # if doc is not None:
1993+# # doc.freeDoc()
1994+# # return res
1995+
1996+
1997+def _init():
1998+ """
1999+ Initialize necessary libraries (libxml2 and xmlsec).
2000+ Should be called once only: this is automatic when this module is imported.
2001+ Raises an exception if an error occurs.
2002+ """
2003+ # Init libxml library
2004+ libxml2.initParser()
2005+ libxml2.substituteEntitiesDefault(1)
2006+ # Init xmlsec library
2007+ assert xmlsec.init() >= 0, "Error: xmlsec initialization failed."
2008+ # Check loaded library version
2009+ assert xmlsec.checkVersion() == 1, "Error: loaded xmlsec library version is not compatible."
2010+ # Init crypto library
2011+ assert xmlsec.cryptoAppInit(None) >= 0, "Error: crypto initialization failed."
2012+ # Init xmlsec-crypto library
2013+ assert xmlsec.cryptoInit() >= 0, "Error: xmlsec-crypto initialization failed."
2014+
2015+
2016+def shutdown():
2017+ """
2018+ Shutdown all libraries cleanly.
2019+ Should only be called at the end of all xmlsec actions.
2020+ """
2021+ # Shutdown xmlsec-crypto library
2022+ xmlsec.cryptoShutdown()
2023+ # Shutdown crypto library
2024+ xmlsec.cryptoAppShutdown()
2025+ # Shutdown xmlsec library
2026+ xmlsec.shutdown()
2027+ # Shutdown LibXML2
2028+ libxml2.cleanupParser()
2029+
2030+
2031+#=== MAIN =====================================================================
2032+
2033+# always initialize the xmlsec and libxml2 libraries:
2034+_init()
2035+
2036+def main():
2037+ """
2038+ To use this module as a command-line tool.
2039+ """
2040+ from optparse import OptionParser
2041+ usage = "usage: %prog [options] file.xml"
2042+ parser = OptionParser(usage=usage, version='%prog ' + __version__)
2043+ parser.add_option("-k", "--keyfile",
2044+ metavar="KEYFILE", help="PEM file containing private key",
2045+ action="store", type="string", dest="keyfile")
2046+ parser.add_option("-c", "--certfile",
2047+ metavar="CERTFILE", help="PEM file containing the X.509 certificate",
2048+ action="store", type="string", dest="certfile")
2049+ parser.add_option("-p", "--password", default='', # not None!
2050+ metavar="PASSWORD", help="Password of the private key file",
2051+ action="store", type="string", dest="password")
2052+ (options, args) = parser.parse_args()
2053+
2054+ if len(args) != 1 and not options.keyfile:
2055+ print __doc__
2056+ parser.print_help()
2057+ sys.exit()
2058+
2059+ signed_xml = sign_file(template_file=args[0], key_file=options.keyfile,
2060+ cert_file=options.certfile, password=options.password)
2061+ print signed_xml
2062+ shutdown()
2063+
2064+
2065+if __name__ == "__main__":
2066+ main()
2067+
2068+# This code was developed while listening to Fleet Foxes
2069
2070=== added directory 'l10n_mx_facturae_pac_facturalo/report'
2071=== added file 'l10n_mx_facturae_pac_facturalo/report/__init__.py'
2072--- l10n_mx_facturae_pac_facturalo/report/__init__.py 1970-01-01 00:00:00 +0000
2073+++ l10n_mx_facturae_pac_facturalo/report/__init__.py 2014-06-25 00:35:08 +0000
2074@@ -0,0 +1,25 @@
2075+# -*- encoding: utf-8 -*-
2076+###########################################################################
2077+# Module Writen to OpenERP, Open Source Management Solution
2078+#
2079+# Copyright (c) 2013 OpenPyme - http://www.openpyme.mx
2080+# All Rights Reserved.
2081+# info OpenPyme (info@openpyme.mx)
2082+# Coded by: Agustín Cruz (agustin.cruz@openpyme.mx)
2083+#
2084+# This program is free software: you can redistribute it and/or modify
2085+# it under the terms of the GNU Affero General Public License as
2086+# published by the Free Software Foundation, either version 3 of the
2087+# License, or (at your option) any later version.
2088+#
2089+# This program is distributed in the hope that it will be useful,
2090+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2091+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2092+# GNU Affero General Public License for more details.
2093+#
2094+# You should have received a copy of the GNU Affero General Public License
2095+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2096+#
2097+##############################################################################
2098+
2099+import account_print_invoice
2100
2101=== added file 'l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py'
2102--- l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py 1970-01-01 00:00:00 +0000
2103+++ l10n_mx_facturae_pac_facturalo/report/account_print_invoice.py 2014-06-25 00:35:08 +0000
2104@@ -0,0 +1,266 @@
2105+# -*- encoding: utf-8 -*-
2106+###########################################################################
2107+# Module Writen to OpenERP, Open Source Management Solution
2108+#
2109+# Copyright (c) 2013 OpenPyme - http://www.openpyme.mx
2110+# All Rights Reserved.
2111+# info OpenPyme (info@openpyme.mx)
2112+# Coded by: Agustín Cruz (agustin.cruz@openpyme.mx)
2113+#
2114+# This program is free software: you can redistribute it and/or modify
2115+# it under the terms of the GNU Affero General Public License as
2116+# published by the Free Software Foundation, either version 3 of the
2117+# License, or (at your option) any later version.
2118+#
2119+# This program is distributed in the hope that it will be useful,
2120+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2121+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2122+# GNU Affero General Public License for more details.
2123+#
2124+# You should have received a copy of the GNU Affero General Public License
2125+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2126+#
2127+##############################################################################
2128+
2129+import string
2130+import logging
2131+import base64
2132+import StringIO
2133+
2134+from openerp.tools.translate import _
2135+from account.report import account_print_invoice
2136+from openerp.report import report_sxw
2137+import openerp
2138+
2139+
2140+logger = logging.getLogger(__name__)
2141+
2142+try:
2143+ import qrcode
2144+except:
2145+ logger.error("Package Python Qrcode missed")
2146+
2147+
2148+class account_invoice(account_print_invoice.account_invoice):
2149+ def __init__(self, cr, uid, name, context):
2150+ super(account_invoice, self).__init__(cr, uid, name, context=context)
2151+
2152+ self.localcontext.update({
2153+ 'split_string': self._split_string,
2154+ 'get_taxes': self._get_taxes,
2155+ 'get_taxes_ret': self._get_taxes_ret,
2156+ 'float': float,
2157+ 'exists_key': self._exists_key,
2158+ 'get_text_promissory': self._get_text_promissory,
2159+ 'get_emitter_data': self._get_emitter_data,
2160+ 'get_partner_data': self._get_partner_data,
2161+ 'qrcode': self._get_qrcode,
2162+ 'legend': self._get_legend,
2163+ })
2164+
2165+
2166+ def _exists_key(self, invoice, key):
2167+ return key in invoice._columns
2168+
2169+
2170+ def _get_text_promissory(self, company, partner):
2171+ text = ''
2172+ context = {}
2173+ lang = self.pool.get('res.partner').browse(self.cr, self.uid,
2174+ partner.id).lang
2175+ if lang:
2176+ context.update({'lang' : lang})
2177+ company = self.pool.get('res.company').browse(self.cr, self.uid,
2178+ company.id, context=context)
2179+ if company.dinamic_text:
2180+ try:
2181+ text = company.dinamic_text % eval("{" + company.dict_var + "}")
2182+ except:
2183+ pass
2184+ return text
2185+
2186+
2187+ def _get_taxes(self, invoice):
2188+ # TODO: Optimizar esta funcion y combinarla con _get_taxes_ret
2189+ lista = []
2190+ lista2 = []
2191+
2192+ taxes = [tax for tax in invoice.tax_line if tax.tax_percent >= 0.0]
2193+
2194+ # comparacion de los taxes, para que todos sean distintos entre sí
2195+ for tax in taxes:
2196+ lista.append([tax.name2, tax.amount])
2197+
2198+ for i in range(0, len(lista)):
2199+ for j in range(i + 1, len(lista)):
2200+ if (lista[i][0] == lista[j][0])and (lista[j][0] <> 0) :
2201+ lista[j][0] = 0
2202+ lista[i][1] = lista[i][1] + lista[j][1]
2203+
2204+
2205+ for k in range(0, len(lista)):
2206+ if lista[k][0] <> 0:
2207+ lista2.append(lista[k])
2208+
2209+ return lista2
2210+
2211+
2212+ def _get_taxes_ret(self, invoice):
2213+ lista = []
2214+ lista2 = []
2215+
2216+ taxes = [tax for tax in invoice.tax_line if tax.tax_percent < 0.0]
2217+
2218+ # comparacion de los taxes, para que todos sean distintos entre sí
2219+ for tax in taxes:
2220+ lista.append([tax.name2, tax.amount])
2221+
2222+ for i in range(0, len(lista)):
2223+ for j in range(i + 1, len(lista)):
2224+ if (lista[i][0] == lista[j][0])and (lista[j][0] <> 0) :
2225+ lista[j][0] = 0
2226+ lista[i][1] = lista[i][1] + lista[j][1]
2227+
2228+
2229+ for k in range(0, len(lista)):
2230+ if lista[k][0] <> 0:
2231+ lista2.append(lista[k])
2232+
2233+ return lista2
2234+
2235+
2236+ def _split_string(self, string, length=75):
2237+ if string:
2238+ for i in range(0, len(string), length):
2239+ string = string[:i] + ' ' + string[i:]
2240+ return string
2241+
2242+
2243+ def _get_emitter_data(self, partner, data='name'):
2244+ # Simple cache for speed up
2245+ if not hasattr(self, 'emitter_data'):
2246+ self.emitter_data = self._get_invoice_address(partner)
2247+ return self.emitter_data[data]
2248+
2249+
2250+ def _get_partner_data(self, partner, data='name'):
2251+ # Simple cache for speed up
2252+ if not hasattr(self, 'partner_data'):
2253+ self.partner_data = self._get_invoice_address(partner)
2254+ return self.partner_data[data]
2255+
2256+
2257+ def _get_invoice_address(self, partner):
2258+ # Si la dirección del partner no es default o invoice
2259+ if partner.type not in ['invoice', 'default']:
2260+ # Obtiene la dirección del padre
2261+ add_invoice = partner.parent_id
2262+ else:
2263+ add_invoice = partner
2264+
2265+ # Aseguramos que la dirección sea de facturación
2266+ if add_invoice.type in ['invoice', 'default']:
2267+ res = {
2268+ 'name': add_invoice.name or '',
2269+ 'vat': add_invoice.vat_split or add_invoice.vat or '',
2270+ 'street': add_invoice.street or False,
2271+ 'no_ext': add_invoice.l10n_mx_street3 or '',
2272+ 'no_int': add_invoice.l10n_mx_street4 or '',
2273+ 'suburb': add_invoice.street2 or '',
2274+ 'city': add_invoice.city or '',
2275+ 'state': add_invoice.state_id.name or '',
2276+ 'country': add_invoice.country_id.name or '',
2277+ 'county': add_invoice.l10n_mx_city2 or '',
2278+ 'zip': add_invoice.zip or '',
2279+ 'phone': add_invoice.phone or '',
2280+ 'fax': add_invoice.fax or '',
2281+ 'mobile': add_invoice.mobile or '',
2282+ }
2283+ if not res['vat']:
2284+ # Comprobamos que tengamos un RFC definido
2285+ raise openerp.exceptions.Warning(
2286+ _('Not Vat Number set on partner'))
2287+ else:
2288+ raise openerp.exceptions.Warning(
2289+ _('Customer Address Not Invoice Type'))
2290+ return res
2291+
2292+
2293+ def _get_qrcode(self, invoice):
2294+ """ If invoice type is cbb then return the uplodaded code
2295+ Else return the generated code
2296+ """
2297+ if invoice.invoice_sequence_id.approval_id.type == 'cbb':
2298+ return invoice.invoice_sequence_id.approval_id.cbb_image
2299+ else:
2300+ return self._gen_qrcode(invoice)
2301+
2302+
2303+ def _gen_qrcode(self, invoice):
2304+ """Genera el código de barras bidimensional para una factura
2305+ @param invoice: Objeto invoice con los datos de la factura
2306+ @param timbre: Cadena del XML obtenido del PAC
2307+
2308+ @return: Imagen del código de barras o None
2309+ """
2310+ # Procesar invoice para obtener el total con 17 posiciones
2311+ tt = string.zfill('%.6f' % invoice.amount_total, 17)
2312+
2313+ # Init qr code
2314+ qr = qrcode.QRCode(version=4, box_size=4, border=1)
2315+ # Add the data to qr code
2316+ qr.add_data('?re=' + invoice.company_id.partner_id.vat_split or invoice.company_id.partner_id.vat)
2317+ qr.add_data('&rr=' + invoice.partner_id.vat_split or invoice.company_id.vat)
2318+ qr.add_data('&tt=' + tt)
2319+ qr.add_data('&id=' + invoice.cfdi_folio_fiscal)
2320+ qr.make(fit=True)
2321+
2322+ # Genera la imagen y la pone en memoria para poder
2323+ # codificarla en 64bits y mandarla al reporte
2324+ img = qr.make_image()
2325+ output = StringIO.StringIO()
2326+ img.save(output, 'PNG')
2327+ output_s = output.getvalue()
2328+
2329+ return base64.b64encode(output_s)
2330+
2331+
2332+ def _get_legend(self, invoice):
2333+ """ Helper funcion to print legend according
2334+ to invoice type.
2335+ """
2336+ if invoice.invoice_sequence_id.approval_id.type == 'cbb':
2337+ approval = invoice.invoice_sequence_id.approval_id.approval_number
2338+ date = invoice.invoice_sequence_id.approval_id.date_start
2339+ types = ['out_invoice', 'out_refund']
2340+ states = ['open', 'paid', 'cancel']
2341+ if (invoice.type in types) and (invoice.state in states):
2342+ legend = _('Número de aprobación SICOFI: %s\n' %
2343+ approval.encode('utf-8', 'ignore')
2344+ )
2345+ else:
2346+ legend = _('SIN FOLIO O ESTATUS NO VALIDO\n')
2347+ legend += _('La reproducción apócrifa de este comprobante '
2348+ 'constituye un delito en los términos de las '
2349+ 'disposiciones fiscales.\n')
2350+ legend += _('Este comprobante tendrá una vigencia de dos años '
2351+ 'contados a partir de la fecha aprobación de la '
2352+ 'asignación de folios, la cual es: %s' %
2353+ date.encode('utf-8', 'ignore')
2354+ )
2355+ else:
2356+ legend = '“Este documento es una representacion impresa de un CFDI”'
2357+ return legend
2358+
2359+
2360+from openerp.netsvc import Service
2361+# Borra el servicio del reporte para poder declararlo nuevamente
2362+del Service._services['report.account.invoice.facturae.webkit']
2363+
2364+report_sxw.report_sxw(
2365+ 'report.account.invoice.facturae.webkit',
2366+ 'account.invoice',
2367+ 'addons/l10n_mx_facturae_pac_facturalo/report/account_print_invoice.rml',
2368+ header=False,
2369+ parser=account_invoice,
2370+)
2371
2372=== added file 'l10n_mx_facturae_pac_facturalo/report/account_print_invoice.rml'
2373--- l10n_mx_facturae_pac_facturalo/report/account_print_invoice.rml 1970-01-01 00:00:00 +0000
2374+++ l10n_mx_facturae_pac_facturalo/report/account_print_invoice.rml 2014-06-25 00:35:08 +0000
2375@@ -0,0 +1,476 @@
2376+<?xml version="1.0"?>
2377+<document filename="test.pdf">
2378+ <template pageSize="(612.0,780.0)" title="Test" allowSplitting="20">
2379+ <pageTemplate id="first">
2380+ <frame id="first" x1="57.0" y1="46.0" width="498" height="729"/>
2381+ <pageGraphics>
2382+ <!--page bottom(footer)-->
2383+ <lines>1.33cm 1.33cm 20cm 1.33cm</lines>
2384+ <place x="1.30cm" y="0cm" height="1.30cm" width="34.0cm">
2385+ <para style="main_footer">Generado por openpyme.mx</para>
2386+ </place>
2387+ </pageGraphics>
2388+ </pageTemplate>
2389+ </template>
2390+
2391+ <stylesheet>
2392+ <blockTableStyle id="Standard_Outline">
2393+ <blockAlignment value="LEFT"/>
2394+ <blockValign value="TOP"/>
2395+ </blockTableStyle>
2396+ <blockTableStyle id="Tabla1">
2397+ <blockAlignment value="LEFT"/>
2398+ <blockValign value="TOP"/>
2399+ </blockTableStyle>
2400+ <blockTableStyle id="Tabla2">
2401+ <blockAlignment value="LEFT"/>
2402+ <blockValign value="TOP"/>
2403+ <lineStyle kind="LINEABOVE" colorName="#000080" start="0,0" stop="0,0"/>
2404+ <lineStyle kind="LINEABOVE" colorName="#000080" start="1,0" stop="1,0"/>
2405+ </blockTableStyle>
2406+ <blockTableStyle id="Tabla3">
2407+ <blockAlignment value="LEFT"/>
2408+ <blockValign value="TOP"/>
2409+ <lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
2410+ <lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
2411+ <lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
2412+ <lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
2413+ <lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
2414+ <lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
2415+ <lineStyle kind="LINEBEFORE" colorName="#cccccc" start="2,0" stop="2,-1"/>
2416+ <lineStyle kind="LINEAFTER" colorName="#cccccc" start="2,0" stop="2,-1"/>
2417+ <lineStyle kind="LINEABOVE" colorName="#cccccc" start="2,0" stop="2,0"/>
2418+ <lineStyle kind="LINEBELOW" colorName="#cccccc" start="2,-1" stop="2,-1"/>
2419+ <lineStyle kind="LINEBEFORE" colorName="#cccccc" start="3,0" stop="3,-1"/>
2420+ <lineStyle kind="LINEAFTER" colorName="#cccccc" start="3,0" stop="3,-1"/>
2421+ <lineStyle kind="LINEABOVE" colorName="#cccccc" start="3,0" stop="3,0"/>
2422+ <lineStyle kind="LINEBELOW" colorName="#cccccc" start="3,-1" stop="3,-1"/>
2423+ <lineStyle kind="LINEBEFORE" colorName="#cccccc" start="4,0" stop="4,-1"/>
2424+ <lineStyle kind="LINEABOVE" colorName="#cccccc" start="4,0" stop="4,0"/>
2425+ <lineStyle kind="LINEBELOW" colorName="#cccccc" start="4,-1" stop="4,-1"/>
2426+ <lineStyle kind="LINEBEFORE" colorName="#cccccc" start="5,0" stop="5,-1"/>
2427+ <lineStyle kind="LINEABOVE" colorName="#cccccc" start="5,0" stop="5,0"/>
2428+ <lineStyle kind="LINEBELOW" colorName="#cccccc" start="5,-1" stop="5,-1"/>
2429+ <lineStyle kind="LINEBEFORE" colorName="#cccccc" start="6,0" stop="6,-1"/>
2430+ <lineStyle kind="LINEAFTER" colorName="#cccccc" start="6,0" stop="6,-1"/>
2431+ <lineStyle kind="LINEABOVE" colorName="#cccccc" start="6,0" stop="6,0"/>
2432+ <lineStyle kind="LINEBELOW" colorName="#cccccc" start="6,-1" stop="6,-1"/>
2433+ <lineStyle kind="LINEBEFORE" colorName="#cccccc" start="7,0" stop="7,-1"/>
2434+ <lineStyle kind="LINEAFTER" colorName="#cccccc" start="7,0" stop="7,-1"/>
2435+ <lineStyle kind="LINEABOVE" colorName="#cccccc" start="7,0" stop="7,0"/>
2436+ <lineStyle kind="LINEBELOW" colorName="#cccccc" start="7,-1" stop="7,-1"/>
2437+ </blockTableStyle>
2438+ <blockTableStyle id="Tabla4">
2439+ <blockAlignment value="LEFT"/>
2440+ <blockValign value="TOP"/>
2441+ </blockTableStyle>
2442+ <blockTableStyle id="Tabla5">
2443+ <blockAlignment value="LEFT"/>
2444+ <blockValign value="TOP"/>
2445+ <lineStyle kind="LINEBEFORE" colorName="#000000" start="0,0" stop="0,-1"/>
2446+ <lineStyle kind="LINEABOVE" colorName="#000000" start="0,0" stop="0,0"/>
2447+ <lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/>
2448+ <lineStyle kind="LINEBEFORE" colorName="#000000" start="1,0" stop="1,-1"/>
2449+ <lineStyle kind="LINEABOVE" colorName="#000000" start="1,0" stop="1,0"/>
2450+ <lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/>
2451+ <lineStyle kind="LINEBEFORE" colorName="#000000" start="2,0" stop="2,-1"/>
2452+ <lineStyle kind="LINEABOVE" colorName="#000000" start="2,0" stop="2,0"/>
2453+ <lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/>
2454+ <lineStyle kind="LINEBEFORE" colorName="#000000" start="3,0" stop="3,-1"/>
2455+ <lineStyle kind="LINEAFTER" colorName="#000000" start="3,0" stop="3,-1"/>
2456+ <lineStyle kind="LINEABOVE" colorName="#000000" start="3,0" stop="3,0"/>
2457+ <lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/>
2458+ <lineStyle kind="LINEBEFORE" colorName="#000000" start="0,1" stop="0,-1"/>
2459+ <lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/>
2460+ <lineStyle kind="LINEBEFORE" colorName="#000000" start="1,1" stop="1,-1"/>
2461+ <lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/>
2462+ <lineStyle kind="LINEBEFORE" colorName="#000000" start="2,1" stop="2,-1"/>
2463+ <lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/>
2464+ <lineStyle kind="LINEBEFORE" colorName="#000000" start="3,1" stop="3,-1"/>
2465+ <lineStyle kind="LINEAFTER" colorName="#000000" start="3,1" stop="3,-1"/>
2466+ <lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/>
2467+ </blockTableStyle>
2468+ <blockTableStyle id="Tabla6">
2469+ <blockAlignment value="LEFT"/>
2470+ <blockValign value="TOP"/>
2471+ <lineStyle kind="LINEBEFORE" colorName="#000000" start="0,0" stop="0,-1"/>
2472+ <lineStyle kind="LINEABOVE" colorName="#000000" start="0,0" stop="0,0"/>
2473+ <lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/>
2474+ <lineStyle kind="LINEBEFORE" colorName="#000000" start="1,0" stop="1,-1"/>
2475+ <lineStyle kind="LINEABOVE" colorName="#000000" start="1,0" stop="1,0"/>
2476+ <lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/>
2477+ <lineStyle kind="LINEBEFORE" colorName="#000000" start="2,0" stop="2,-1"/>
2478+ <lineStyle kind="LINEAFTER" colorName="#000000" start="2,0" stop="2,-1"/>
2479+ <lineStyle kind="LINEABOVE" colorName="#000000" start="2,0" stop="2,0"/>
2480+ <lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/>
2481+ </blockTableStyle>
2482+ <blockTableStyle id="Tabla7">
2483+ <blockAlignment value="LEFT"/>
2484+ <blockValign value="TOP"/>
2485+ </blockTableStyle>
2486+ <initialize>
2487+ <paraStyle name="all" alignment="justify"/>
2488+ </initialize>
2489+
2490+ <paraStyle name="P1" fontName="Helvetica-Bold" textColor="#ff3333" fontSize="30.0" alignment="CENTER"/>
2491+ <paraStyle name="P7" fontName="Helvetica" fontSize="6.5" alignment="RIGHT"/>
2492+ <paraStyle name="P8" fontName="Helvetica-Bold" fontSize="6.5" alignment="RIGHT" textColor="#280099"/>
2493+ <paraStyle name="P9" fontName="Helvetica-Bold" fontSize="6.5" alignment="CENTER" textColor="#280099"/>
2494+ <paraStyle name="P10" fontName="Helvetica-Bold" alignment="CENTER" fontSize="6.5" textColor="#280099"/>
2495+ <paraStyle name="P11" fontName="Helvetica-Bold" textColor="#ff3333" size="6.0" alignment="CENTER"/>
2496+ <paraStyle name="P12" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="4.5" alignment="LEFT" leading="10" spaceBefore="0.0" spaceAfter="0.0"/>
2497+ <paraStyle name="P13" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="4.0" leading="10" spaceBefore="0.0" spaceAfter="0.0"/>
2498+ <paraStyle name="P14" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="5.0" leading="10" spaceBefore="0.0" spaceAfter="0.0" alignment="CENTER" />
2499+ <paraStyle name="P15" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="6.5" alignment="RIGHT" leading="10" spaceBefore="0.0" spaceAfter="0.0"/>
2500+ <paraStyle name="P21" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="6.0" leading="8" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
2501+ <paraStyle name="P22" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="7.0" leading="8" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
2502+ <paraStyle name="P23" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="9.0" leading="8" alignment="LEFT" spaceBefore="0.0" spaceAfter="2.0"/>
2503+ <paraStyle name="P24" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="7.0" leading="8" alignment="LEFT" spaceBefore="1.0" spaceAfter="0.0"/>
2504+ <paraStyle name="P25" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="5.0" leading="8" alignment="LEFT" spaceBefore="1.0" spaceAfter="0.0"/>
2505+ <paraStyle name="P30" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
2506+ <paraStyle name="P31" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="6.0" leading="8" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
2507+ <paraStyle name="Standard" fontName="Helvetica"/>
2508+ <paraStyle name="Heading" fontName="Helvetica" fontSize="14.0" leading="17" spaceBefore="12.0" spaceAfter="6.0"/>
2509+ <paraStyle name="Text body" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
2510+ <paraStyle name="List" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
2511+ <paraStyle name="Caption" fontName="Helvetica" fontSize="12.0" leading="15" spaceBefore="6.0" spaceAfter="6.0"/>
2512+ <paraStyle name="Index" fontName="Helvetica"/>
2513+ <paraStyle name="Table Contents" fontName="Helvetica" alignment="LEFT" fontSize="6.5"/>
2514+ <paraStyle name="terp_header" fontName="Helvetica-Bold" fontSize="12.0" leading="15" alignment="LEFT" spaceBefore="12.0" spaceAfter="6.0"/>
2515+ <paraStyle name="terp_default_8" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="6.0" leading="8" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
2516+ <paraStyle name="terp_default_9" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="7.0" leading="8" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
2517+ <paraStyle name="Table Heading" fontName="Helvetica" alignment="CENTER"/>
2518+ <paraStyle name="Quotations" rightIndent="28.0" leftIndent="28.0" fontName="Helvetica" spaceBefore="0.0" spaceAfter="14.0"/>
2519+ <paraStyle name="main_footer" fontSize="6.5" fontName="Helvetica" alignment="CENTER"/>
2520+ <paraStyle name="main_footer2" fontSize="7.5" fontName="Helvetica-Bold" alignment="RIGHT"/>
2521+ <images/>
2522+ </stylesheet>
2523+ <story>
2524+ <section>
2525+ <para style="P31">[[ repeatIn(objects,'o') ]] </para>
2526+ <para style="P31">[[ setLang(o.partner_id.lang) ]] </para>
2527+
2528+ <blockTable colWidths="150.0,220.0,200.0" style="Tabla1">
2529+ <tr>
2530+ <td>
2531+ <blockTable>
2532+ <tr>
2533+ </tr>
2534+ <tr>
2535+ <td><para style="P30">[[ o.company_emitter_id.logo and setTag('para','image',{'width':'4.5cm','height':'2.25cm'}) ]] [[ o.company_emitter_id.logo ]]</para></td>
2536+ </tr>
2537+ </blockTable>
2538+ </td>
2539+ <td>
2540+ <blockTable>
2541+ <tr>
2542+ <td>
2543+ <para style="P23">[[ get_emitter_data(o.company_emitter_id.address_invoice_parent_company_id, 'name') ]]</para>
2544+ <para style="P24">[[ get_emitter_data(o.company_emitter_id.address_invoice_parent_company_id, 'vat') ]]</para>
2545+ <para style="P25">Domicilio Fiscal</para>
2546+ <para style="P24">[[ get_emitter_data(o.company_emitter_id.address_invoice_parent_company_id, 'street') ]] Nro. Ext: [[ get_emitter_data(o.company_emitter_id.partner_id, 'no_ext') ]] <font>Int: [[ get_emitter_data(o.company_emitter_id.address_invoice_parent_company_id, 'no_int') or removeParentNode('font') ]]</font></para>
2547+ <para style="P24">Colonia: [[ get_emitter_data(o.company_emitter_id.address_invoice_parent_company_id, 'street2') or removeParentNode('para') ]]</para>
2548+ <para style="P24">Ciudad: [[ get_emitter_data(o.company_emitter_id.address_invoice_parent_company_id, 'city') ]] Estado: [[ get_emitter_data(o.company_emitter_id.partner_id, 'state') ]]</para>
2549+ <para style="P24">Localidad: [[ get_emitter_data(o.company_emitter_id.address_invoice_parent_company_id, 'county') or removeParentNode('para') ]]</para>
2550+ <para style="P24">CP: [[ get_emitter_data(o.company_emitter_id.address_invoice_parent_company_id, 'zip') or removeParentNode('para') ]]</para>
2551+ <para style="P24">Phone(s): [[ get_emitter_data(o.company_emitter_id.address_invoice_parent_company_id, 'phone') or removeParentNode('para') ]]</para>
2552+ </td>
2553+ </tr>
2554+ </blockTable>
2555+ </td>
2556+ <td>
2557+ <blockTable rowWidths="10.0" style="Tabla5">
2558+ <tr><td>
2559+ <para style="P11">Factura[[ ((o.type == 'out_invoice' and (o.state == 'open' or o.state == 'paid')) or removeParentNode('para')) and '' ]] [[ o.number ]]</para>
2560+ <para style="P11">FACTURA CANCELADA[[ ((o.type == 'out_invoice' and o.state == 'cancel') or removeParentNode('para')) and '' ]] [[ o.number ]]</para>
2561+ <para style="P11">Credit Node [[ (o.type=='out_refund' or removeParentNode('para')) and '' ]] [[ o.number ]]</para>
2562+ </td></tr>
2563+ <tr><td>
2564+ <para style="terp_default_8">FOLIO FISCAL(UUID):</para>
2565+ <para style="terp_default_9">[[ o.cfdi_folio_fiscal or removeParentNode('tr') ]]</para>
2566+ </td></tr>
2567+ <tr><td>
2568+ <para style="terp_default_8">NO. DE SERIE DEL CERTIFICADO DEL SAT:</para>
2569+ <para style="terp_default_9">[[ o.cfdi_no_certificado or removeParentNode('tr') ]]</para>
2570+ </td></tr>
2571+ <tr><td>
2572+ <para style="terp_default_8">NO. DE SERIE DEL CERTIFICADO DEL EMISOR:</para>
2573+ <para style="terp_default_9">[[ o.no_certificado or removeParentNode('tr') ]]</para>
2574+ </td></tr>
2575+ <tr><td>
2576+ <para style="terp_default_8">CERTIFICATION DATE AND TIME:</para>
2577+ <para style="terp_default_9">[[o.date_invoice_tz or removeParentNode('tr') ]]</para>
2578+ </td></tr>
2579+ <tr><td>
2580+ <para style="terp_default_8">ISSUE DATE AND TIME:</para>
2581+ <para style="terp_default_9">[[ (o.invoice_sequence_id.approval_id.type != 'cbb') and o.cfdi_fecha_timbrado or removeParentNode('tr') ]]</para>
2582+ </td></tr>
2583+ </blockTable>
2584+ </td>
2585+ </tr>
2586+ </blockTable>
2587+
2588+ <blockTable colWidths="250.0,50.0,250.0" style="Tabla4">
2589+ <tr>
2590+ <td>
2591+ <blockTable colWidths="60.0,190.0">
2592+ <tr>
2593+ <td><para style="P21"><font face="Helvetica">CLIENTE:</font></para></td>
2594+ <td><para style="P22">[[ get_partner_data(o.partner_id, 'name') ]]</para></td>
2595+ </tr>
2596+ <tr>
2597+ <td><para style="P21"><font face="Helvetica">RFC:</font></para></td>
2598+ <td><para style="P22">[[ get_partner_data(o.partner_id, 'vat') ]]</para></td>
2599+ </tr>
2600+ <tr>
2601+ <td><para style="P21"><font face="Helvetica">ADDRESS:</font></para></td>
2602+ <td><para style="P22">[[ get_partner_data(o.partner_id, 'street') ]] [[ get_partner_data(o.partner_id, 'no_ext') ]] <font>INT. [[ get_partner_data(o.partner_id, 'no_int') or removeParentNode('font')]]</font></para>
2603+ </td>
2604+ </tr>
2605+ <tr>
2606+ <td><para style="P21"><font face="Helvetica">COLONIA:</font></para></td>
2607+ <td><para style="P22">[[ get_partner_data(o.partner_id, 'suburb') ]] [[ get_partner_data(o.partner_id, 'county') ]]</para></td>
2608+ </tr>
2609+ <tr>
2610+ <td><para style="P21"><font face="Helvetica">LOCALIDAD:</font></para></td>
2611+ <td><para style="P22">[[ get_partner_data(o.partner_id, 'city') ]] [[ get_partner_data(o.partner_id, 'state') ]] [[ get_partner_data(o.partner_id, 'country') ]]</para></td>
2612+ </tr>
2613+ <tr>
2614+ <td><para style="P21"><font face="Helvetica">CP:</font></para></td>
2615+ <td><para style="P22">[[ get_partner_data(o.partner_id, 'zip') or 'N/D' ]] </para></td>
2616+ </tr>
2617+ <tr>
2618+ <td><para style="P21"><font face="Helvetica">PHONE:</font></para></td>
2619+ <td><para style="P22">[[ get_partner_data(o.partner_id, 'phone') or 'N/D']]</para></td>
2620+ </tr>
2621+ </blockTable>
2622+ </td>
2623+ <td></td>
2624+ <td>
2625+ <blockTable colWidths="90.0,160.0" >
2626+ <tr>
2627+ <td><para style="P21"><font face="Helvetica">Taxation: </font></para></td>
2628+ <td><para style="P22">[[o.company_emitter_id.partner_id.regimen_fiscal_id and o.company_emitter_id.partner_id.regimen_fiscal_id.name or removeParentNode('tr')]]</para></td>
2629+ </tr>
2630+ <tr>
2631+ <td><para style="P21"><font face="Helvetica">Place of issue: </font></para></td>
2632+ <td><para style="P22">[[ o.address_issued_id and o.address_issued_id.city and o.address_issued_id.state_id and o.address_issued_id.state_id.name or removeParentNode('tr') ]]</para></td>
2633+ </tr>
2634+ <tr>
2635+ <td><para style="P21"><font face="Helvetica">Condiciones de Pago: </font></para></td>
2636+ <td><para style="P22">[[ format(o.payment_term and (o.payment_term.note or o.payment_term.name) or removeParentNode('tr') ) ]]</para></td>
2637+ </tr>
2638+ <tr>
2639+ <td><para style="P21"><font face="Helvetica">NumCtaPago:</font></para></td>
2640+ <td><para style="P22">[[o.acc_payment.last_acc_number or removeParentNode('tr')]]</para></td>
2641+ </tr>
2642+ <tr>
2643+ <td><para style="P21"><font face="Helvetica">Payment Method:</font></para></td>
2644+ <td><para style="P22"> [[o.pay_method_id.name or removeParentNode('tr')]]</para></td>
2645+ </tr>
2646+ <tr>
2647+ <td><para style="P21"><font face="Helvetica">BANCO:</font></para></td>
2648+ <td><para style="P22"> [[ o.acc_payment.bank_name or removeParentNode('tr')]] </para></td>
2649+ </tr>
2650+ <tr>
2651+ <td><para style="P21"><font face="Helvetica">Clave de Moneda:</font></para></td>
2652+ <td><para style="P22">[[o.currency_id.name or removeParentNode('tr')]]</para></td>
2653+ </tr>
2654+ <tr>
2655+ <td><para style="P21"><font face="Helvetica">Origin:</font></para></td>
2656+ <td><para style="P22">[[ o.origin or removeParentNode('tr')]]</para></td>
2657+ </tr>
2658+ <tr>
2659+ <td><para style="P21"><font face="Helvetica">Customer Reference:</font></para></td>
2660+ <td><para style="P22">[[ o.name or removeParentNode('tr')]]</para></td>
2661+ </tr>
2662+ </blockTable>
2663+ </td>
2664+ </tr>
2665+ </blockTable>
2666+
2667+
2668+ <blockTable colWidths="70.0,70.0,70.0,210.0,70.0,70.0" style="Tabla3">
2669+ <tr style="Tabla3">
2670+ <td>
2671+ <para style="P9">CANTIDAD</para>
2672+ </td>
2673+ <td>
2674+ <para style="P9">UNIDAD DE MEDIDA</para>
2675+ </td>
2676+ <td>
2677+ <para style="P9">CODE</para>
2678+ </td>
2679+ <td>
2680+ <para style="P9">DESCRIPTION</para>
2681+ </td>
2682+ <td>
2683+ <para style="P9">PRECIO UNITARIO</para>
2684+ </td>
2685+ <td>
2686+ <para style="P9">IMPORTES</para>
2687+ </td>
2688+ </tr>
2689+ <tr>
2690+ <td>
2691+ <para style="Table Contents"><font face="Helvetica" size="8.0">[[ repeatIn(o.invoice_line,'l') ]] </font>[[ formatLang(l.quantity) ]]</para>
2692+ </td>
2693+ <td >
2694+ <para style="Table Contents">[[ (l.uos_id and l.uos_id.name) or '' ]]</para>
2695+ </td>
2696+ <td >
2697+ <para style="Table Contents">[[ l.product_id and l.product_id.default_code ]]</para>
2698+ </td>
2699+ <td >
2700+ <para style="Table Contents">[[ l.name ]]</para>
2701+ <para style="Table Contents">Notas: [[l.note or removeParentNode('para')]]</para>
2702+ </td>
2703+ <td>
2704+ <para style="P15">[[ formatLang(l.price_unit) ]]</para>
2705+ </td>
2706+ <td>
2707+ <para style="P15">[[ exists_key(o, 'global_discount_percent') and (formatLang(l.quantity * l.price_unit, digits=get_digits(dp='Account'))) or formatLang(l.price_subtotal) ]]</para>
2708+ </td>
2709+ </tr>
2710+ </blockTable>
2711+
2712+ <blockTable colWidths="425,70.0,75.0" style="Tabla4">
2713+ <tr>
2714+ <td>
2715+ <para style="P10">[[ o.amount_to_text ]]</para>
2716+ </td>
2717+ <td>
2718+ <blockTable rowHeights="10.0" style="Tabla4">
2719+ <tr>
2720+ <td><para style="P8">SUMA: [[ exists_key(o, 'global_discount_percent') and o.global_discount_percent or removeParentNode('tr')]]$</para></td>
2721+ </tr>
2722+ <tr>
2723+ <td><para style="P8">DESCUENTO: [[ exists_key(o, 'global_discount_percent') and o.global_discount_percent or removeParentNode('tr')]] %</para>
2724+ </td>
2725+ </tr>
2726+ <tr>
2727+ <td>
2728+ <para style="P8">SUBTOTAL: $</para>
2729+ </td>
2730+ </tr>
2731+ <tr>
2732+ <td>
2733+ <para style="P8">
2734+ <font face="Helvetica" >[[ repeatIn( get_taxes(o), 'tax' ) ]]</font>
2735+ </para>
2736+ <para style="P8">
2737+ <font face="Helvetica" >[[ tax[0] ]]: $</font>
2738+ </para>
2739+ </td>
2740+ </tr>
2741+ <tr>
2742+ <td>
2743+ <para style="P8">
2744+ <font face="Helvetica" >[[ repeatIn( get_taxes_ret(o), 'tax_ret' ) ]]</font>
2745+ </para>
2746+ <para style="P8">
2747+ <font face="Helvetica" >[[ tax_ret[0] ]] Ret: $</font>
2748+ </para>
2749+ </td>
2750+ </tr>
2751+ <tr>
2752+ <td>
2753+ <para style="P8">TOTAL: $</para>
2754+ </td>
2755+ </tr>
2756+ </blockTable>
2757+
2758+ </td>
2759+ <td>
2760+ <blockTable rowHeights="10.0" style="Tabla4">
2761+ <tr>
2762+ <td><para style="P7">[[formatLang( ( exists_key(o, 'global_discount_percent') and o.global_discount_amount or removeParentNode('tr') ) + (o.amount_untaxed or removeParentNode('tr')), digits=get_digits(dp='Account') ) or removeParentNode('tr')]]</para></td>
2763+ </tr>
2764+ <tr>
2765+ <td><para style="P7">[[o.global_discount_amount and formatLang( o.global_discount_amount) or removeParentNode('tr')]]</para></td>
2766+ </tr>
2767+ <tr>
2768+ <td><para style="P7">[[ formatLang(o.amount_untaxed) ]]</para></td>
2769+ </tr>
2770+ <tr>
2771+ <td><para style="P7">
2772+ <font face="Helvetica">[[ repeatIn( get_taxes(o), 'tax' ) ]]</font>
2773+ </para>
2774+ <para style="P7">
2775+ <font face="Helvetica">[[ formatLang(float(tax[1])) ]]</font>
2776+ </para>
2777+ </td>
2778+ </tr>
2779+ <tr>
2780+ <td><para style="P7">
2781+ <font face="Helvetica">[[ repeatIn( get_taxes_ret(o), 'tax_ret' ) ]]</font>
2782+ </para>
2783+ <para style="P7">
2784+ <font face="Helvetica">[[ formatLang(float(tax_ret[1])*-1) ]]</font>
2785+ </para>
2786+ </td>
2787+ </tr>
2788+ <tr>
2789+ <td><para style="P7">[[ formatLang(o.amount_total) ]]</para></td>
2790+ </tr>
2791+ </blockTable>
2792+ </td>
2793+ </tr>
2794+ </blockTable>
2795+
2796+ <blockTable colWidths="500.00">
2797+ <tr>
2798+ <td><para style="P22">[[ (o.comment and format(o.comment )) or removeParentNode('blockTable') ]]</para></td>
2799+ </tr>
2800+ </blockTable>
2801+
2802+ <blockTable style="Tabla4">
2803+ <tr><td>
2804+ <para style="P1">[[ (o.state == 'cancel' and 'CANCELADA') or removeParentNode('blockTable') ]]</para>
2805+ </td></tr>
2806+ </blockTable>
2807+
2808+ <blockTable colWidths="470.0,110.0" style="Tabla4">
2809+ <tr>
2810+ <td>
2811+ <blockTable colWidths="465.0" rowHeights="10.0,10.0,10.0,10.0,10.0,10.0,20.0" style="Tabla4">
2812+ <tr></tr>
2813+ <tr>
2814+ <td><para style="P12">SELLO DIGITAL DEL EMISOR</para></td>
2815+ </tr>
2816+ <tr style ="Tabla3">
2817+ <td><para style="P13">[[ split_string( o.sello or '') ]]</para></td>
2818+ </tr>
2819+ <tr>
2820+ <td><para style="P12">SELLO DIGITAL DEL SAT:</para></td>
2821+ </tr>
2822+ <tr style ="Tabla3">
2823+ <td><para style="P13">[[ split_string( o.cfdi_sello or '') ]]</para></td>
2824+ </tr>
2825+ <tr>
2826+ <td><para style="P12">CADENA ORIGINAL DEL COMPLEMENTO DE CERTIFICACION DIGITAL DEL SAT:</para></td>
2827+ </tr>
2828+ <tr style ="Tabla3" >
2829+ <td><para style="P13">[[ split_string( o.cfdi_cadena_original or '' ) ]]</para></td>
2830+ </tr>
2831+ </blockTable>
2832+ </td>
2833+ <td>
2834+ <blockTable style="Tabla4">
2835+ <tr>
2836+ <td><para style="P13">[[ setTag('para','image',{'width':'3cm','height':'3cm'}) ]] [[ qrcode(o) ]]</para></td>
2837+ </tr>
2838+ </blockTable>
2839+ </td>
2840+ </tr>
2841+ </blockTable>
2842+
2843+ <blockTable style="Tabla4">
2844+ <tr>
2845+ <td><para style="P14"> [[ legend(o) ]] </para></td>
2846+ </tr>
2847+ </blockTable>
2848+ </section>
2849+
2850+ </story>
2851+</document>
2852\ No newline at end of file