1
# -*- encoding: utf-8 -*-
2
###########################################################################
3
# Module Writen to OpenERP, Open Source Management Solution
5
# Copyright (c) 2012 Vauxoo - http://www.vauxoo.com/
7
# info Vauxoo (info@vauxoo.com)
8
############################################################################
9
# Coded by: Isaac Lopez (isaac@vauxoo.com)
10
# moylop260 (moylop260@vauxoo.com)
11
############################################################################
13
# This program is free software: you can redistribute it and/or modify
14
# it under the terms of the GNU Affero General Public License as
15
# published by the Free Software Foundation, either version 3 of the
16
# License, or (at your option) any later version.
18
# This program is distributed in the hope that it will be useful,
19
# but WITHOUT ANY WARRANTY; without even the implied warranty of
20
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
# GNU Affero General Public License for more details.
23
# You should have received a copy of the GNU Affero General Public License
24
# along with this program. If not, see <http://www.gnu.org/licenses/>.
26
##############################################################################
29
from osv import fields
35
from tools.translate import _
40
class account_invoice(osv.osv):
41
_inherit = 'account.invoice'
43
def create_report(self, cr, uid, res_ids, report_name=False, file_name=False):
44
if not report_name or not res_ids:
45
return (False,Exception('Report name and Resources ids are required !!!'))
47
ret_file_name = file_name+'.pdf'
48
service = netsvc.LocalService("report."+report_name);
49
(result,format) = service.create(cr, uid, res_ids, {}, {})
50
fp = open(ret_file_name,'wb+');
54
#print 'Exception in create report:',e
55
#return (False,str(e))
56
return (True,ret_file_name)
58
def create_report_pdf(self, cr, uid, ids, context={}):
63
(fileno, fname) = tempfile.mkstemp('.pdf', 'openerp_' + (False or '') + '__facturae__' )
66
file = self.create_report(cr, uid, [id], "account.invoice.facturae.pdf", fname)
69
if is_file and os.path.isfile(fname):
75
'name': context.get('fname'),
76
'datas': data and base64.encodestring( data ) or None,
77
'datas_fname': context.get('fname'),
78
'description': 'Factura-E PDF',
79
'res_model': self._name,
82
self.pool.get('ir.attachment').create(cr, uid, data_attach, context=context)
85
def action_make_cfd(self, cr, uid, ids, *args):
86
self._attach_invoice(cr, uid, ids)
89
def ________action_number(self, cr, uid, ids, *args):
90
cr.execute('SELECT id, type, number, move_id, reference ' \
91
'FROM account_invoice ' \
92
'WHERE id IN ('+','.join(map(str,ids))+')')
93
obj_inv = self.browse(cr, uid, ids)[0]
95
invoice_id__sequence_id = self._get_sequence(cr, uid, ids)##agregado
97
for (id, invtype, number, move_id, reference) in cr.fetchall():
100
'fiscalyear_id' : obj_inv.period_id.fiscalyear_id.id,
102
if invoice_id__sequence_id[id]:
103
sid = invoice_id__sequence_id[id]
104
number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', context=tmp_context)
105
elif obj_inv.journal_id.invoice_sequence_id:
106
sid = obj_inv.journal_id.invoice_sequence_id.id
107
number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', context=tmp_context)
109
number = self.pool.get('ir.sequence').get_id(cr, uid,
110
'account.invoice.' + invtype,
114
raise osv.except_osv('Warning !', 'No hay una secuencia de folios bien definida. !')
115
if invtype in ('in_invoice', 'in_refund'):
118
ref = self._convert_ref(cr, uid, number)
119
cr.execute('UPDATE account_invoice SET number=%s ' \
120
'WHERE id=%d', (number, id))
121
cr.execute('UPDATE account_move_line SET ref=%s ' \
122
'WHERE move_id=%d AND (ref is null OR ref = \'\')',
124
cr.execute('UPDATE account_analytic_line SET ref=%s ' \
125
'FROM account_move_line ' \
126
'WHERE account_move_line.move_id = %d ' \
127
'AND account_analytic_line.move_id = account_move_line.id',
131
def _attach_invoice(self, cr, uid, ids, context=None):
134
inv_type_facturae = {'out_invoice': True, 'out_refund': True, 'in_invoice': False, 'in_refund': False}
135
for inv in self.browse(cr, uid, ids):
136
if inv_type_facturae.get(inv.type, False):
137
fname, xml_data = self.pool.get('account.invoice')._get_facturae_invoice_xml_data(cr, uid, [inv.id], context=context)
140
#'datas':binascii.b2a_base64(str(attachents.get(attactment))),
141
'datas': xml_data and base64.encodestring( xml_data ) or None,
142
'datas_fname': fname,
143
'description': 'Factura-E XML',
144
'res_model': self._name,
147
self.pool.get('ir.attachment').create(cr, uid, data_attach, context=context)
148
fname = fname.replace('.xml', '.pdf')
149
self.create_report_pdf(cr, uid, ids, context={'fname': fname})
152
def _get_fname_invoice(self, cr, uid, ids, field_names=None, arg=False, context={}):
156
sequence_obj = self.pool.get('ir.sequence')
158
invoice_id__sequence_id = self._get_invoice_sequence(cr, uid, ids, context=context)
159
for invoice in self.browse(cr, uid, ids, context=context):
160
sequence_id = invoice_id__sequence_id[invoice.id]
163
sequence = sequence_obj.browse(cr, uid, [sequence_id], context)[0]
165
fname += (invoice.company_id.partner_id and (invoice.company_id.partner_id._columns.has_key('vat_split') and invoice.company_id.partner_id.vat_split or invoice.company_id.partner_id.vat) or '')
167
number_work = invoice.number or invoice.internal_number
169
context.update({ 'number_work': int( number_work ) or False })
170
fname += sequence and sequence.approval_id and sequence.approval_id.serie or ''
174
fname += number_work or ''
175
res[invoice.id] = fname
178
def action_cancel_draft(self, cr, uid, ids, *args):
179
attachment_obj = self.pool.get('ir.attachment')
180
for invoice in self.browse(cr, uid, ids):
182
attachment_xml_id = attachment_obj.search(cr, uid, [
183
('name','=',invoice.fname_invoice+'.xml'),
184
('datas_fname','=',invoice.fname_invoice+'.xml'),
185
('res_model','=','account.invoice'),
186
('res_id','=',invoice.id)
188
attachment_obj.unlink(cr, uid, attachment_xml_id)
190
attachment_pdf_id = attachment_obj.search(cr, uid, [
191
('name','=',invoice.fname_invoice),###no se agrega.pdf, porque el generador de reportes, no lo hace asi, actualmente o agrega doble .pdf o nada
192
#('name','=',invoice.fname_invoice+'.pdf'),
193
('datas_fname','=',invoice.fname_invoice+'.pdf'),
194
('res_model','=','account.invoice'),
195
('res_id','=',invoice.id)
197
attachment_obj.unlink(cr, uid, attachment_pdf_id)
200
self.write(cr, uid, ids, {
201
'no_certificado': False,
202
'certificado': False,
204
'cadena_original': False,
205
'date_invoice_cancel': False,
207
return super(account_invoice, self).action_cancel_draft(cr, uid, ids, args)
209
def action_cancel(self, cr, uid, ids, *args):
210
self.write(cr, uid, ids, {'date_invoice_cancel': time.strftime('%Y-%m-%d %H:%M:%S')})
211
return super(account_invoice, self).action_cancel(cr, uid, ids, args)
213
def _get_cfd_xml_invoice(self, cr, uid, ids, field_name=None, arg=False, context=None):
215
attachment_obj = self.pool.get('ir.attachment')
216
for invoice in self.browse(cr, uid, ids, context=context):
217
attachment_xml_id = attachment_obj.search(cr, uid, [
218
('name','=',invoice.fname_invoice+'.xml'),
219
('datas_fname','=',invoice.fname_invoice+'.xml'),
220
('res_model','=','account.invoice'),
221
('res_id','=',invoice.id),
223
res[invoice.id] = attachment_xml_id and attachment_xml_id[0] or False
227
##Extract date_invoice from original, but add datetime
228
#'date_invoice': fields.datetime('Date Invoiced', states={'open':[('readonly',True)],'close':[('readonly',True)]}, help="Keep empty to use the current date"),
229
#'invoice_sequence_id': fields.function(_get_invoice_sequence, method=True, type='many2one', relation='ir.sequence', string='Invoice Sequence', store=True),
230
#'certificate_id': fields.function(_get_invoice_certificate, method=True, type='many2one', relation='res.company.facturae.certificate', string='Invoice Certificate', store=True),
231
'fname_invoice': fields.function(_get_fname_invoice, method=True, type='char', size=26, string='File Name Invoice'),
232
#'amount_to_text': fields.function(_get_amount_to_text, method=True, type='char', size=256, string='Amount to Text', store=True),
233
'no_certificado': fields.char('No. Certificado', size=64),
234
'certificado': fields.text('Certificado', size=64),
235
'sello': fields.text('Sello', size=512),
236
'cadena_original': fields.text('Cadena Original', size=512),
237
'date_invoice_cancel': fields.datetime('Date Invoice Cancelled', readonly=True),
238
'cfd_xml_id': fields.function(_get_cfd_xml_invoice, method=True, type='many2one', relation='ir.attachment', string='XML'),
242
#'date_invoice': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
245
def copy(self, cr, uid, id, default={}, context=None):
249
'invoice_sequence_id': False,
250
'no_certificado': False,
251
'certificado': False,
253
'cadena_original': False,
255
return super(account_invoice, self).copy(cr, uid, id, default, context=context)
257
def binary2file(self, cr, uid, ids, binary_data, file_prefix="", file_suffix=""):
258
(fileno, fname) = tempfile.mkstemp(file_suffix, file_prefix)
259
f = open( fname, 'wb' )
260
f.write( base64.decodestring( binary_data ) )
265
def _get_file_globals(self, cr, uid, ids, context={}):
268
id = ids and ids[0] or False
271
invoice = self.browse(cr, uid, id, context=context)
272
#certificate_id = invoice.company_id.certificate_id
273
context.update( {'date_work': invoice.date_invoice_tz} )
274
certificate_id = self.pool.get('res.company')._get_current_certificate(cr, uid, [invoice.company_id.id], context=context)[invoice.company_id.id]
275
certificate_id = certificate_id and self.pool.get('res.company.facturae.certificate').browse(cr, uid, [certificate_id], context=context)[0] or False
278
if not certificate_id.certificate_file_pem:
279
#generate certificate_id.certificate_file_pem, a partir del certificate_id.certificate_file
281
fname_cer_pem = False
283
fname_cer_pem = self.binary2file(cr, uid, ids, certificate_id.certificate_file_pem, 'openerp_' + (certificate_id.serial_number or '') + '__certificate__', '.cer.pem')
285
raise osv.except_osv('Error !', 'No se ha capturado un archivo CERTIFICADO en formato PEM, en la company!')
286
file_globals['fname_cer'] = fname_cer_pem
288
fname_key_pem = False
290
fname_key_pem = self.binary2file(cr, uid, ids, certificate_id.certificate_key_file_pem, 'openerp_' + (certificate_id.serial_number or '') + '__certificate__', '.key.pem')
292
raise osv.except_osv('Error !', 'No se ha capturado un archivo KEY en formato PEM, en la company!')
293
file_globals['fname_key'] = fname_key_pem
295
fname_cer_no_pem = False
297
fname_cer_no_pem = self.binary2file(cr, uid, ids, certificate_id.certificate_file, 'openerp_' + (certificate_id.serial_number or '') + '__certificate__', '.cer')
300
file_globals['fname_cer_no_pem'] = fname_cer_no_pem
302
fname_key_no_pem = False
304
fname_key_no_pem = self.binary2file(cr, uid, ids, certificate_id.certificate_key_file, 'openerp_' + (certificate_id.serial_number or '') + '__certificate__', '.key')
307
file_globals['fname_key_no_pem'] = fname_key_no_pem
309
file_globals['password'] = certificate_id.certificate_password
311
if certificate_id.fname_xslt:
312
if ( certificate_id.fname_xslt[0] == os.sep or certificate_id.fname_xslt[1] == ':' ):
313
file_globals['fname_xslt'] = certificate_id.fname_xslt
315
file_globals['fname_xslt'] = os.path.join( tools.config["root_path"], certificate_id.fname_xslt )
317
file_globals['fname_xslt'] = os.path.join( tools.config["addons_path"], 'l10n_mx_facturae', 'SAT', 'cadenaoriginal_2_0_l.xslt' )
319
file_globals['fname_repmensual_xslt'] = os.path.join( tools.config["addons_path"], 'l10n_mx_facturae', 'SAT', 'reporte_mensual_2_0.xslt' )
321
if not file_globals.get('fname_xslt', False):
322
raise osv.except_osv('Warning !', 'No se ha definido fname_xslt. !')
324
if not os.path.isfile(file_globals.get('fname_xslt', ' ')):
325
raise osv.except_osv('Warning !', 'No existe el archivo [%s]. !'%(file_globals.get('fname_xslt', ' ')))
327
file_globals['serial_number'] = certificate_id.serial_number
329
raise osv.except_osv('Warning !', 'Verique la fecha de la factura y la vigencia del certificado, y que el registro del certificado este activo.\n%s!'%(msg2))
332
def _____________get_facturae_invoice_txt_data(self, cr, uid, ids, context={}):
333
#TODO: Transform date to fmt %d/%m/%Y %H:%M:%S
334
certificate_lib = self.pool.get('facturae.certificate.library')
335
fname_repmensual_xslt = self._get_file_globals(cr, uid, ids, context=context)['fname_repmensual_xslt']
336
fname_tmp = certificate_lib.b64str_to_tempfile( base64.encodestring(''), file_suffix='.txt', file_prefix='openerp__' + (False or '') + '__repmensual__' )
338
for invoice in self.browse(cr, uid, ids, context=context):
339
xml_b64 = invoice.cfd_xml_id and invoice.cfd_xml_id.datas or False
341
fname_xml = certificate_lib.b64str_to_tempfile( xml_b64 or '', file_suffix='.xml', file_prefix='openerp__' + (False or '') + '__xml__' )
342
rep_mensual += certificate_lib._transform_xml(fname_xml=fname_xml, fname_xslt=fname_repmensual_xslt, fname_out=fname_tmp)
343
rep_mensual += '\r\n'
344
return rep_mensual, fname_tmp
346
def _get_facturae_invoice_txt_data(self, cr, uid, ids, context={}):
347
facturae_datas = self._get_facturae_invoice_dict_data(cr, uid, ids, context=context)
348
facturae_data_txt_lists = []
349
folio_data = self._get_folio(cr, uid, ids, context=context)
350
facturae_type_dict = {'out_invoice': 'I', 'out_refund': 'E', 'in_invoice': False, 'in_refund': False}
352
for facturae_data in facturae_datas:
353
invoice_comprobante_data = facturae_data['Comprobante']
354
fechas.append( invoice_comprobante_data['fecha'] )
355
if facturae_data['state'] in ['open', 'paid']:
357
elif facturae_data['state'] in ['cancel']:
361
facturae_type = facturae_type_dict[ facturae_data['type'] ]
362
rate = facturae_data['rate']
364
if not facturae_type:
366
#if not invoice_comprobante_data['Receptor']['rfc']:
367
#raise osv.except_osv('Warning !', 'No se tiene definido el RFC de la factura [%s].\n%s !'%(facturae_data['Comprobante']['folio'], msg2))
369
invoice = self.browse(cr, uid, [facturae_data['invoice_id']], context=context)[0]
370
pedimento_numeros = []
371
pedimento_fechas = []
372
pedimento_aduanas = []
373
for line in invoice.invoice_line:
375
pedimento_numeros.append(line.tracking_id.import_id.name or '')
376
pedimento_fechas.append(line.tracking_id.import_id.date or '')
377
pedimento_aduanas.append(line.tracking_id.import_id.customs or '')
380
pedimento_numeros = ','.join(map(lambda x: str(x) or '', pedimento_numeros))
381
pedimento_fechas = ','.join(map(lambda x: str(x) or '', pedimento_fechas))
382
pedimento_aduanas = ','.join(map(lambda x: str(x) or '', pedimento_aduanas))
384
facturae_data_txt_list = [
385
invoice_comprobante_data['Receptor']['rfc'] or '',
386
invoice_comprobante_data.get('serie', False) or '',
387
invoice_comprobante_data['folio'] or '',
388
str( invoice_comprobante_data['anoAprobacion'] ) + str( invoice_comprobante_data['noAprobacion'] ),
389
time.strftime('%d/%m/%Y %H:%M:%S', time.strptime( facturae_data['date_invoice_tz'], '%Y-%m-%d %H:%M:%S')),#invoice_comprobante_data['fecha'].replace('T', ' '),
390
"%.2f"%( round( float(invoice_comprobante_data['total'] or 0.0) * rate, 2) ),
391
"%.2f"%( round( float(invoice_comprobante_data['Impuestos']['totalImpuestosTrasladados'] or 0.0) * rate, 2) ),
398
facturae_data_txt_lists.append( facturae_data_txt_list )
400
fecha_promedio = time.strftime('%Y-%m-%d')
402
fecha_promedio = fechas[ int( len(fechas)/2 )-1 ]
405
for facturae_data_txt in facturae_data_txt_lists:
407
cad += '|'.join(map(lambda x: str(x) or '', facturae_data_txt))
411
fname = "1" + invoice_comprobante_data['Emisor']['rfc'] + '-' + time.strftime('%m%Y', time.strptime(fecha_promedio, '%Y-%m-%dT%H:%M:%S')) + '.txt'
414
def _get_folio(self, cr, uid, ids, context={}):
416
id = ids and ids[0] or False
418
invoice = self.browse(cr, uid, id, context=context)
420
def get_id(self, cr, uid, sequence_id, test='id=%s', context=None):
421
if test not in ('id=%s', 'code=%s'):
422
raise ValueError('invalid test')
423
cr.execute('SELECT id, number_next, prefix, suffix, padding FROM ir_sequence WHERE '+test+' AND active=%s FOR UPDATE', (sequence_id, True))
424
res = cr.dictfetchone()
429
'fiscalyear_id' : invoice.period_id.fiscalyear_id.id,
431
if invoice.journal_id.invoice_sequence_id:
432
sid = invoice.journal_id.invoice_sequence_id.id
433
number = self.pool.get('ir.sequence').get_id(cr, uid, sid, 'id=%s', context=tmp_context)
435
number = self.pool.get('ir.sequence').get_id(cr, uid,
436
'account.invoice.' + invtype,
441
raise osv.except_osv('Warning !', 'There is no active invoice sequence defined for the journal !')
443
sequence_id = self._get_invoice_sequence(cr, uid, [id])[id]
445
if invoice.journal_id.invoice_sequence_id or invoice_id__sequence_id[id]:
446
sequence_id = invoice_id__sequence_id[id] or invoice.journal_id.invoice_sequence_id.id
449
test_value = 'account.invoice.' + invoice.type
450
test2 = '\n--company_id=%s\n'
451
test2_value = invoice.company_id.id
452
cr.execute('SELECT id, number_next, prefix, suffix, padding FROM ir_sequence WHERE '+test + test2+ ' AND active=%s FOR UPDATE', (test_value, test2_value, True))
453
res = cr.dictfetchone()
454
sequence_id = res and res['id'] or False
457
#NO ES COMPATIBLE CON TINYERP approval_id = sequence.approval_id.id
458
number_work = invoice.number or invoice.internal_number
459
if invoice.type in ['out_invoice', 'out_refund']:
464
raise osv.except_osv(_('Warning !'), _('El folio [%s] tiene que ser un numero entero, sin letras.')%( number_work ) )
465
context.update({ 'number_work': number_work or False })
466
approval_id = self.pool.get('ir.sequence')._get_current_approval(cr, uid, [sequence_id], field_names=None, arg=False, context=context)[sequence_id]
467
approval = approval_id and self.pool.get('ir.sequence.approval').browse(cr, uid, [approval_id], context=context)[0] or False
470
'serie': approval.serie or '',
472
'noAprobacion': approval.approval_number or '',
473
'anoAprobacion': approval.approval_year or '',
474
'desde': approval.number_start or '',
475
'hasta': approval.number_end or '',
476
#'noCertificado': "30001000000100000800",
479
raise osv.except_osv(u'Warning !', u'La secuencia no tiene datos de facturacion electronica.\nEn la sequence_id [%d].\n %s !'%(sequence_id, msg2))
481
raise osv.except_osv(u'Warning !', u'No se encontro un sequence de configuracion. %s !'%(msg2))
484
def _dict_iteritems_sort(self, data_dict):#cr=False, uid=False, ids=[], context={}):
491
keys = data_dict.keys()
495
key_item_sort.append( [ko, data_dict[ko]] )
496
keys.pop( keys.index( ko ) )
498
key_item_sort.append( [key_too, data_dict[key_too]] )
501
def dict2xml(self, data_dict, node=False, doc=False):
506
for element, attribute in self._dict_iteritems_sort( data_dict ):
508
doc = minidom.Document()
509
if isinstance( attribute, dict ):
511
node = doc.createElement( element )
512
self.dict2xml( attribute, node, doc )
514
child = doc.createElement( element )
515
self.dict2xml( attribute, child, doc )
516
node.appendChild(child)
517
elif isinstance( attribute, list):
518
child = doc.createElement( element )
519
for attr in attribute:
520
if isinstance( attr, dict ):
521
self.dict2xml( attr, child, doc )
522
node.appendChild(child)
524
if isinstance(attribute, str) or isinstance(attribute, unicode) :
525
attribute = conv_ascii(attribute)
527
attribute = str(attribute)
528
node.setAttribute(element, attribute)
529
#print "attribute",unicode( attribute, 'UTF-8')
531
doc.appendChild(node)
534
def _get_facturae_invoice_xml_data(self, cr, uid, ids, context={}):
537
data_dict = self._get_facturae_invoice_dict_data(cr, uid, ids, context=context)[0]
538
doc_xml = self.dict2xml( {'Comprobante': data_dict.get('Comprobante') } )
539
invoice_number = "sn"
540
(fileno_xml, fname_xml) = tempfile.mkstemp('.xml', 'openerp_' + (invoice_number or '') + '__facturae__' )
541
fname_txt = fname_xml + '.txt'
542
f = open(fname_xml, 'w')
543
doc_xml.writexml(f, indent=' ', addindent=' ', newl='\r\n', encoding='UTF-8')
547
(fileno_sign, fname_sign) = tempfile.mkstemp('.txt', 'openerp_' + (invoice_number or '') + '__facturae_txt_md5__' )
548
os.close(fileno_sign)
551
'fname_xml': fname_xml,
552
'fname_txt': fname_txt,
553
'fname_sign': fname_sign,
555
context.update( self._get_file_globals(cr, uid, ids, context=context) )
556
fname_txt, txt_str = self._xml2cad_orig(cr=False, uid=False, ids=False, context=context)
557
data_dict['cadena_original'] = txt_str
560
raise osv.except_osv(_('Error en Cadena original!'), _('No se pudo obtener la cadena original del comprobante.\nVerifique su configuracion.\n%s'%(msg2)) )
562
if not data_dict['Comprobante'].get('folio', ''):
563
raise osv.except_osv(_('Error en Folio!'), _('No se pudo obtener el Folio del comprobante.\nAntes de generar el XML, de clic en el boton, generar factura.\nVerifique su configuracion.\n%s'%(msg2)) )
565
#time.strftime('%Y-%m-%dT%H:%M:%S', time.strptime(invoice.date_invoice, '%Y-%m-%d %H:%M:%S'))
566
context.update( { 'fecha': data_dict['Comprobante']['fecha'] } )
567
sign_str = self._get_sello(cr=False, uid=False, ids=False, context=context)
569
raise osv.except_osv('Error en Sello !', 'No se pudo generar el sello del comprobante.\nVerifique su configuracion.\ns%s'%(msg2))
571
nodeComprobante = doc_xml.getElementsByTagName("Comprobante")[0]
572
nodeComprobante.setAttribute("sello", sign_str)
573
data_dict['Comprobante']['sello'] = sign_str
575
noCertificado = self._get_noCertificado( context['fname_cer'] )
576
if not noCertificado:
577
raise osv.except_osv('Error en No Certificado !', 'No se pudo obtener el No de Certificado del comprobante.\nVerifique su configuracion.\n%s'%(msg2))
578
nodeComprobante.setAttribute("noCertificado", noCertificado)
579
data_dict['Comprobante']['noCertificado'] = noCertificado
581
cert_str = self._get_certificate_str( context['fname_cer'] )
583
raise osv.except_osv('Error en Certificado!', 'No se pudo generar el Certificado del comprobante.\nVerifique su configuracion.\n%s'%(msg2))
584
nodeComprobante.setAttribute("certificado", cert_str)
585
data_dict['Comprobante']['certificado'] = cert_str
587
self.write_cfd_data(cr, uid, ids, data_dict, context=context)
589
if context.get('type_data') == 'dict':
591
if context.get('type_data') == 'xml_obj':
593
data_xml = doc_xml.toxml('UTF-8')
594
data_xml = codecs.BOM_UTF8 + data_xml
595
fname_xml = (data_dict['Comprobante']['Emisor']['rfc'] or '') + '.' + ( data_dict['Comprobante'].get('serie', '') or '') + '.' + ( data_dict['Comprobante'].get('folio', '') or '') + '.xml'
596
return fname_xml, data_xml
598
def write_cfd_data(self, cr, uid, ids, cfd_datas, context={}):
601
##obtener cfd_data con varios ids
607
noCertificado = cfd_data.get('Comprobante', {}).get('noCertificado', '')
608
certificado = cfd_data.get('Comprobante', {}).get('certificado', '')
609
sello = cfd_data.get('Comprobante', {}).get('sello', '')
610
cadena_original = cfd_data.get('cadena_original', '')
612
'no_certificado': noCertificado,
613
'certificado': certificado,
615
'cadena_original': cadena_original,
617
self.write(cr, uid, [id], data, context=context)
620
def _get_noCertificado(self, fname_cer, pem=True):
621
certificate_lib = self.pool.get('facturae.certificate.library')
622
fname_serial = certificate_lib.b64str_to_tempfile( base64.encodestring(''), file_suffix='.txt', file_prefix='openerp__' + (False or '') + '__serial__' )
623
result = certificate_lib._get_param_serial(fname_cer, fname_out=fname_serial, type='PEM')
626
def _get_sello(self, cr=False, uid=False, ids=False, context={}):
627
#TODO: Put encrypt date dynamic
628
fecha = context['fecha']
629
year = float( time.strftime('%Y', time.strptime(fecha, '%Y-%m-%dT%H:%M:%S')) )
634
certificate_lib = self.pool.get('facturae.certificate.library')
635
fname_sign = certificate_lib.b64str_to_tempfile( base64.encodestring(''), file_suffix='.txt', file_prefix='openerp__' + (False or '') + '__sign__' )
636
result = certificate_lib._sign(fname=context['fname_xml'], fname_xslt=context['fname_xslt'], fname_key=context['fname_key'], fname_out=fname_sign, encrypt=encrypt, type_key='PEM')
639
def _xml2cad_orig(self, cr=False, uid=False, ids=False, context={}):
640
certificate_lib = self.pool.get('facturae.certificate.library')
641
fname_tmp = certificate_lib.b64str_to_tempfile( base64.encodestring(''), file_suffix='.txt', file_prefix='openerp__' + (False or '') + '__cadorig__' )
642
cad_orig = certificate_lib._transform_xml(fname_xml=context['fname_xml'], fname_xslt=context['fname_xslt'], fname_out=fname_tmp)
643
return fname_tmp, cad_orig
645
#TODO: agregar esta funcionalidad con openssl
646
def _get_certificate_str( self, fname_cer_pem = ""):
647
fcer = open( fname_cer_pem, "r")
648
lines = fcer.readlines()
653
if 'END CERTIFICATE' in line:
657
if 'BEGIN CERTIFICATE' in line:
660
#TODO: agregar esta funcionalidad con openssl
661
def _get_md5_cad_orig(self, cadorig_str, fname_cadorig_digest):
662
cadorig_digest = hashlib.md5(cadorig_str).hexdigest()
663
open(fname_cadorig_digest, "w").write(cadorig_digest)
664
return cadorig_digest, fname_cadorig_digest
666
def _get_facturae_invoice_dict_data(self, cr, uid, ids, context={}):
667
invoices = self.browse(cr, uid, ids, context=context)
668
invoice_tax_obj = self.pool.get("account.invoice.tax")
670
invoice_data_parents = []
671
#'type': fields.selection([
672
#('out_invoice','Customer Invoice'),
673
#('in_invoice','Supplier Invoice'),
674
#('out_refund','Customer Refund'),
675
#('in_refund','Supplier Refund'),
676
#],'Type', readonly=True, select=True),
677
for invoice in invoices:
678
invoice_data_parent = {}
679
if invoice.type == 'out_invoice':
680
tipoComprobante = 'ingreso'
681
elif invoice.type == 'out_refund':
682
tipoComprobante = 'egreso'
684
raise osv.except_osv('Warning !', 'Solo se puede emitir factura electronica a clientes.!')
685
#Inicia seccion: Comprobante
686
invoice_data_parent['Comprobante'] = {}
688
invoice_data_parent['Comprobante'].update({
689
'xmlns': "http://www.sat.gob.mx/cfd/2",
690
'xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance",
691
'xsi:schemaLocation': "http://www.sat.gob.mx/cfd/2 http://www.sat.gob.mx/sitio_internet/cfd/2/cfdv2.xsd",
694
number_work = invoice.number or invoice.internal_number
695
invoice_data_parent['Comprobante'].update({
696
'folio': number_work,
697
'fecha': invoice.date_invoice_tz and \
698
#time.strftime('%d/%m/%y', time.strptime(invoice.date_invoice, '%Y-%m-%d')) \
699
time.strftime('%Y-%m-%dT%H:%M:%S', time.strptime(invoice.date_invoice_tz, '%Y-%m-%d %H:%M:%S'))
701
'tipoDeComprobante': tipoComprobante,
702
'formaDePago': u'Pago en una sola exhibición',
703
'noCertificado': '@',
706
'subTotal': "%.2f"%( invoice.amount_untaxed or 0.0),
707
'descuento': "0",#Add field general
708
'total': "%.2f"%( invoice.amount_total or 0.0),
710
folio_data = self._get_folio(cr, uid, [invoice.id], context=context)
711
invoice_data_parent['Comprobante'].update({
712
'anoAprobacion': folio_data['anoAprobacion'],
713
'noAprobacion': folio_data['noAprobacion'],
715
serie = folio_data.get('serie', False)
717
invoice_data_parent['Comprobante'].update({
720
#Termina seccion: Comprobante
721
#Inicia seccion: Emisor
722
partner_obj = self.pool.get('res.partner')
723
partner = invoice.company_id and invoice.company_id.partner_id and invoice.company_id.partner_id or False
724
partner_parent = (invoice.company_id and invoice.company_id.parent_id and invoice.company_id.parent_id.partner_id) or (invoice.company_id.partner_id and invoice.company_id.partner_id) or False
726
address_invoice_id = partner_obj.address_get(cr, uid, [partner.id], ['invoice'])['invoice']
727
address_invoice_parent_id = partner_obj.address_get(cr, uid, [partner_parent.id], ['invoice'])['invoice']
729
if not address_invoice_id:
730
raise osv.except_osv('Warning !', 'No se tiene definido los datos de facturacion del partner [%s].\n%s !'%(partner.name, msg2))
732
address_invoice = self.pool.get('res.partner.address').browse(cr, uid, address_invoice_id, context)
733
address_invoice_parent = self.pool.get('res.partner.address').browse(cr, uid, address_invoice_parent_id, context)
736
raise osv.except_osv('Warning !', 'No se tiene definido el RFC del partner [%s].\n%s !'%(partner.name, msg2))
738
invoice_data = invoice_data_parent['Comprobante']
739
invoice_data['Emisor'] = {}
740
invoice_data['Emisor'].update({
742
'rfc': ((partner_parent._columns.has_key('vat_split') and partner_parent.vat_split or partner_parent.vat) or '').replace('-', ' ').replace(' ',''),
743
'nombre': address_invoice_parent.name or partner_parent.name or '',
744
#Obtener domicilio dinamicamente
745
#virtual_invoice.append( (invoice.company_id and invoice.company_id.partner_id and invoice.company_id.partner_id.vat or '').replac
748
'calle': invoice.company_id.address_invoice_parent_company_id.street and invoice.company_id.address_invoice_parent_company_id.street.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or '',
749
'noExterior': invoice.company_id.address_invoice_parent_company_id.street3 and invoice.company_id.address_invoice_parent_company_id.street3.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or 'N/A', #"Numero Exterior"
750
'noInterior': invoice.company_id.address_invoice_parent_company_id.street4 and invoice.company_id.address_invoice_parent_company_id.street4.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or 'N/A', #"Numero Interior"
751
'colonia': invoice.company_id.address_invoice_parent_company_id.street2 and invoice.company_id.address_invoice_parent_company_id.street2.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or 'N/A' ,
752
'localidad': invoice.company_id.address_invoice_parent_company_id.city2 and invoice.company_id.address_invoice_parent_company_id.city2.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or 'N/A',
753
'municipio': invoice.company_id.address_invoice_parent_company_id.city and invoice.company_id.address_invoice_parent_company_id.city.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or '',
754
'estado': invoice.company_id.address_invoice_parent_company_id.state_id and invoice.company_id.address_invoice_parent_company_id.state_id.name and invoice.company_id.address_invoice_parent_company_id.state_id.name.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or '' ,
755
'pais': invoice.company_id.address_invoice_parent_company_id.country_id and invoice.company_id.address_invoice_parent_company_id.country_id.name and invoice.company_id.address_invoice_parent_company_id.country_id.name.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ')or '',
756
'codigoPostal': invoice.company_id.address_invoice_parent_company_id.zip and invoice.company_id.address_invoice_parent_company_id.zip.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or '',
759
'calle': invoice.address_invoice_company_id.street and invoice.address_invoice_company_id.street.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or '',
760
'noExterior': invoice.address_invoice_company_id.street3 and invoice.address_invoice_company_id.street3.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or 'N/A', #"Numero Exterior"
761
'noInterior': invoice.address_invoice_company_id.street4 and invoice.address_invoice_company_id.street4.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or 'N/A', #"Numero Interior"
762
'colonia': invoice.address_invoice_company_id.street2 and invoice.address_invoice_company_id.street2.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or 'N/A' ,
763
'localidad': invoice.address_invoice_company_id.city2 and invoice.address_invoice_company_id.city2.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or 'N/A',
764
'municipio': invoice.address_invoice_company_id.city and invoice.address_invoice_company_id.city.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or '',
765
'estado': invoice.address_invoice_company_id.state_id and invoice.address_invoice_company_id.state_id.name and invoice.address_invoice_company_id.state_id.name.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or '' ,
766
'pais': invoice.address_invoice_company_id.country_id and invoice.address_invoice_company_id.country_id.name and invoice.address_invoice_company_id.country_id.name.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ')or '',
767
'codigoPostal': invoice.address_invoice_company_id.zip and invoice.address_invoice_company_id.zip.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or '',
770
#Termina seccion: Emisor
771
#Inicia seccion: Receptor
772
if not invoice.partner_id.vat:
773
raise osv.except_osv('Warning !', 'No se tiene definido el RFC del partner [%s].\n%s !'%(invoice.partner_id.name, msg2))
774
invoice_data['Receptor'] = {}
775
invoice_data['Receptor'].update({
776
'rfc': ((invoice.partner_id._columns.has_key('vat_split') and invoice.partner_id.vat_split or invoice.partner_id.vat) or '').replace('-', ' ').replace(' ',''),
777
'nombre': (invoice.address_invoice_id.name or invoice.partner_id.name or ''),
779
'calle': invoice.address_invoice_id.street and invoice.address_invoice_id.street.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or '',
780
'noExterior': invoice.address_invoice_id.street3 and invoice.address_invoice_id.street3.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or 'N/A', #"Numero Exterior"
781
'noInterior': invoice.address_invoice_id.street4 and invoice.address_invoice_id.street4.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or 'N/A', #"Numero Interior"
782
'colonia': invoice.address_invoice_id.street2 and invoice.address_invoice_id.street2.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or 'N/A' ,
783
'localidad': invoice.address_invoice_id.city2 and invoice.address_invoice_id.city2.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or 'N/A',
784
'municipio': invoice.address_invoice_id.city and invoice.address_invoice_id.city.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or '',
785
'estado': invoice.address_invoice_id.state_id and invoice.address_invoice_id.state_id.name and invoice.address_invoice_id.state_id.name.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or '' ,
786
'pais': invoice.address_invoice_id.country_id and invoice.address_invoice_id.country_id.name and invoice.address_invoice_id.country_id.name.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ')or '',
787
'codigoPostal': invoice.address_invoice_id.zip and invoice.address_invoice_id.zip.replace('\n\r', ' ').replace('\r\n', ' ').replace('\n', ' ').replace('\r', ' ') or '',
790
#Termina seccion: Receptor
791
#Inicia seccion: Conceptos
792
invoice_data['Conceptos'] = []
793
for line in invoice.invoice_line:
794
#price_type = invoice._columns.has_key('price_type') and invoice.price_type or 'tax_excluded'
795
#if price_type == 'tax_included':
796
price_unit = line.price_subtotal/line.quantity#Agrega compatibilidad con modulo TaxIncluded
798
'cantidad': "%.2f"%( line.quantity or 0.0),
799
'descripcion': line.name or '',
800
'valorUnitario': "%.2f"%( price_unit or 0.0),
801
'importe': "%.2f"%( line.price_subtotal or 0.0),#round(line.price_unit *(1-(line.discount/100)),2) or 0.00),#Calc: iva, disc, qty
802
##Falta agregar discount
804
unidad = line.uos_id and line.uos_id.name or ''
806
concepto.update({'unidad': unidad})
807
product_code = line.product_id and line.product_id.default_code or ''
809
concepto.update({'noIdentificacion': product_code})
810
invoice_data['Conceptos'].append( {'Concepto': concepto} )
814
pedimento = line.tracking_id.import_id
818
informacion_aduanera = {
819
'numero': pedimento.name or '',
820
'fecha': pedimento.date or '',
821
'aduana': pedimento.customs,
823
concepto.update( {'InformacionAduanera': informacion_aduanera} )
824
#Termina seccion: Conceptos
825
#Inicia seccion: impuestos
826
invoice_data['Impuestos'] = {}
827
invoice_data['Impuestos'].update({
828
#'totalImpuestosTrasladados': "%.2f"%( invoice.amount_tax or 0.0),
830
invoice_data['Impuestos'].update({
831
#'totalImpuestosRetenidos': "%.2f"%( invoice.amount_tax or 0.0 )
834
invoice_data_impuestos = invoice_data['Impuestos']
835
invoice_data_impuestos['Traslados'] = []
836
#invoice_data_impuestos['Retenciones'] = []
839
totalImpuestosTrasladados = 0
840
totalImpuestosRetenidos = 0
841
for line_tax_id in invoice.tax_line:
842
tax_name = line_tax_id.name2
843
tax_names.append( tax_name )
844
line_tax_id_amount = abs( line_tax_id.amount or 0.0 )
845
if line_tax_id.amount >= 0:
846
impuesto_list = invoice_data_impuestos['Traslados']
847
impuesto_str = 'Traslado'
848
totalImpuestosTrasladados += line_tax_id_amount
850
#impuesto_list = invoice_data_impuestos['Retenciones']
851
impuesto_list = invoice_data_impuestos.setdefault('Retenciones', [])
852
impuesto_str = 'Retencion'
853
totalImpuestosRetenidos += line_tax_id_amount
854
impuesto_dict = {impuesto_str:
856
'impuesto': tax_name,
857
'importe': "%.2f"%( line_tax_id_amount ),
860
if line_tax_id.amount >= 0:
861
impuesto_dict[impuesto_str].update({'tasa': "%.2f"%( abs( line_tax_id.tax_percent ) )})
862
impuesto_list.append( impuesto_dict )
864
invoice_data['Impuestos'].update({
865
'totalImpuestosTrasladados': "%.2f"%( totalImpuestosTrasladados ),
867
if totalImpuestosRetenidos:
868
invoice_data['Impuestos'].update({
869
'totalImpuestosRetenidos': "%.2f"%( totalImpuestosRetenidos )
872
tax_requireds = ['IVA', 'IEPS']
873
for tax_required in tax_requireds:
874
if tax_required in tax_names:
876
invoice_data_impuestos['Traslados'].append( {'Traslado': {
877
'impuesto': tax_required,
878
'tasa': "%.2f"%( 0.0 ),
879
'importe': "%.2f"%( 0.0 ),
881
#Termina seccion: impuestos
882
invoice_data_parents.append( invoice_data_parent )
883
invoice_data_parent['state'] = invoice.state
884
invoice_data_parent['invoice_id'] = invoice.id
885
invoice_data_parent['type'] = invoice.type
886
invoice_data_parent['date_invoice'] = invoice.date_invoice
887
invoice_data_parent['date_invoice_tz'] = invoice.date_invoice_tz
888
invoice_data_parent['currency_id'] = invoice.currency_id.id
890
date_ctx = {'date': invoice.date_invoice_tz and time.strftime('%Y-%m-%d', time.strptime(invoice.date_invoice_tz, '%Y-%m-%d %H:%M:%S')) or False}
891
#rate = self.pool.get('res.currency').compute(cr, uid, invoice.currency_id.id, invoice.company_id.currency_id.id, 1, round=False, context=date_ctx, account=None, account_invert=False)
892
#rate = 1.0/self.pool.get('res.currency')._current_rate(cr, uid, [invoice.currency_id.id], name=False, arg=[], context=date_ctx)[invoice.currency_id.id]
893
currency = self.pool.get('res.currency').browse(cr, uid, [invoice.currency_id.id], context=date_ctx)[0]
894
rate = currency.rate <> 0 and 1.0/currency.rate or 0.0
895
#print "currency.rate",currency.rate
897
invoice_data_parent['rate'] = rate
898
return invoice_data_parents