-
Notifications
You must be signed in to change notification settings - Fork 0
/
electronic_invoice.py
749 lines (690 loc) · 35.7 KB
/
electronic_invoice.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the Affero GNU General Public License as published by
# the Software Foundation; either version 3, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# Copyright 2013 by Mariano Reingart
# Based on code "factura_electronica" by Luis Falcon
# Based on code "openerp-iva-argentina" by Gerardo Allende / Daniel Blanco
# Based on code "l10n_ar_wsafip_fe" by OpenERP - Team de Localización Argentina
"Electronic Invoice for Argentina Federal Tax Administration (AFIP) webservices"
__author__ = "Mariano Reingart ([email protected])"
__copyright__ = "Copyright (C) 2013 Mariano Reingart and others"
__license__ = "AGPL 3.0+"
from osv import fields, osv
from report.interface import report_int
import os, time
import datetime
import decimal
import os
import socket
import sys
import traceback
DEBUG = True
AFIP_COUNTRY_CODE_MAP = {
'ar': 200, 'bo': 202, 'br': 203, 'ca': 204, 'co': 205,
'cu': 207, 'cl': 208, 'ec': 210, 'us': 212, 'mx': 218,
'py': 221, 'pe': 222, 'uy': 225, 've': 226, 'cn': 310,
'tw': 313, 'in': 315, 'il': 319, 'jp': 320, 'at': 405,
'be': 406, 'dk': 409, 'es': 410, 'fr': 412, 'gr': 413,
'it': 417, 'nl': 423, 'pt': 620, 'uk': 426, 'sz': 430,
'de': 438, 'ru': 444, 'eu': 497,
}
class electronic_invoice(osv.osv):
_name = "account.invoice"
_inherit = "account.invoice"
_order = "id"
_columns = {
'pyafipws_concept': fields.selection([
('1','1-Productos'),
('2','2-Servicios'),
('3','3-Productos y Servicios (mercado interno)'),
('4','4-Otros (exportación)'),
], 'Concepto',
select=True, required=True,
readonly=True, states={'draft': [('readonly', False)]}),
'pyafipws_billing_start_date': fields.date('Fecha Desde',
readonly=True, states={'draft': [('readonly', False)]},
help="Seleccionar fecha de fin de servicios - Sólo servicios"),
'pyafipws_billing_end_date': fields.date('Fecha Hasta',
readonly=True, states={'draft': [('readonly', False)]},
help="Seleccionar fecha de inicio de servicios - Sólo servicios"),
'pyafipws_incoterms': fields.many2one('stock.incoterms', 'INCOTERMS',
readonly=True, states={'draft': [('readonly', False)]},
help="Términos de comercio internacional (exportación)"),
'pyafipws_result': fields.selection([
('', 'n/a'),
('A', 'Aceptado'),
('R', 'Rechazado'),
('O', 'Observado'),
], 'Resultado', size=1, readonly=True,
help="Resultado procesamiento de la Solicitud, devuelto por AFIP"),
'pyafipws_cae': fields.char('CAE', size=14, readonly=True,
help="Código de Autorización Electrónico, devuelto por AFIP"),
'pyafipws_cae_due_date': fields.date('Vencimiento CAE', readonly=True,
help="Fecha tope para verificar CAE, devuelto por AFIP"),
'pyafipws_message': fields.text('Mensaje', readonly=True,
help="Mensaje de error u observación, devuelto por AFIP"),
'pyafipws_xml_request': fields.text('Requerimiento XML', readonly=True,
help="Mensaje XML enviado a AFIP (depuración)"),
'pyafipws_xml_response': fields.text('Respuesta XML', readonly=True,
help="Mensaje XML recibido de AFIP (depuración)"),
'pyafipws_barcode': fields.char('Codigo de Barras', size=40,
help="Código de barras para usar en la impresión", readonly=True,),
}
_defaults = {
'pyafipws_concept': lambda *a: '1',
}
def do_pyafipws_request_cae(self, cr, uid, ids, context=None, *args):
"Request to AFIP the invoices' Authorization Electronic Code (CAE)"
for invoice in self.browse(cr, uid, ids):
# if already authorized (electronic invoice with CAE), ignore
if invoice.pyafipws_cae:
continue
# get the electronic invoice type, point of sale and service:
journal = invoice.journal_id
company = journal.company_id
tipo_cbte = journal.pyafipws_invoice_type
punto_vta = journal.pyafipws_point_of_sale
service = journal.pyafipws_electronic_invoice_service
# check if it is an electronic invoice sale point:
if not tipo_cbte or not punto_vta or not service:
continue
# authenticate against AFIP:
auth_data = company.pyafipws_authenticate(service=service)
# create the proxy and get the configuration system parameters:
cfg = self.pool.get('ir.config_parameter')
cache = cfg.get_param(cr, uid, 'pyafipws.cache', context=context)
proxy = cfg.get_param(cr, uid, 'pyafipws.proxy', context=context)
wsdl = cfg.get_param(cr, uid, 'pyafipws.%s.url' % service, context=context)
# import the AFIP webservice helper for electronic invoice
if service == 'wsfe':
from pyafipws.wsfev1 import WSFEv1, SoapFault # local market
ws = WSFEv1()
elif service == 'wsmtxca':
from pyafipws.wsmtx import WSMTXCA, SoapFault # local + detail
wsdl = cfg.get_param(cr, uid, 'pyafipws.wsmtxca.url', context=context)
ws = WSMTXCA()
elif service == 'wsfex':
from pyafipws.wsfexv1 import WSFEXv1, SoapFault # foreign trade
wsdl = cfg.get_param(cr, uid, 'pyafipws.wsfex.url', context=context)
ws = WSFEXv1()
else:
raise osv.except_osv('Error !', "%s no soportado" % service)
# connect to the webservice and call to the test method
ws.Conectar(cache or "", wsdl or "", proxy or "")
# set AFIP webservice credentials:
ws.Cuit = company.pyafipws_cuit
ws.Token = auth_data['token']
ws.Sign = auth_data['sign']
# get the last 8 digit of the invoice number
cbte_nro = int(invoice.number[-8:])
# get the last invoice number registered in AFIP
if service == "wsfe" or service == "wsmtxca":
cbte_nro_afip = ws.CompUltimoAutorizado(tipo_cbte, punto_vta)
elif service == 'wsfex':
cbte_nro_afip = ws.GetLastCMP(tipo_cbte, punto_vta)
cbte_nro_next = int(cbte_nro_afip or 0) + 1
# verify that the invoice is the next one to be registered in AFIP
if cbte_nro != cbte_nro_next:
raise osv.except_osv('Error !',
'Referencia: %s \n'
'El número del comprobante debería ser %s y no %s' % (
str(invoice.number), str(cbte_nro_next), str(cbte_nro)))
# invoice number range (from - to) and date:
cbte_nro = cbt_desde = cbt_hasta = cbte_nro_next
fecha_cbte = invoice.date_invoice
if service != 'wsmtxca':
fecha_cbte = fecha_cbte.replace("-", "")
# due and billing dates only for concept "services"
concepto = tipo_expo = int(invoice.pyafipws_concept or 0)
if int(concepto) != 1:
fecha_venc_pago = invoice.date_invoice
if service != 'wsmtxca':
fecha_venc_pago = fecha_venc_pago.replace("-", "")
if invoice.pyafipws_billing_start_date:
fecha_serv_desde = invoice.pyafipws_billing_start_date
if service != 'wsmtxca':
fecha_serv_desde = fecha_serv_desde.replace("-", "")
else:
fecha_serv_desde = None
if invoice.pyafipws_billing_end_date:
fecha_serv_hasta = invoice.pyafipws_billing_end_date
if service != 'wsmtxca':
fecha_serv_desde = fecha_serv_desde.replace("-", "")
else:
fecha_serv_hasta = None
else:
fecha_venc_pago = fecha_serv_desde = fecha_serv_hasta = None
# customer tax number:
if invoice.partner_id.vat:
nro_doc = invoice.partner_id.vat.replace("-","")
else:
nro_doc = "0" # only "consumidor final"
tipo_doc = None
if nro_doc.startswith("AR"):
nro_doc = nro_doc[2:]
if int(nro_doc) == 0:
tipo_doc = 99 # consumidor final
elif len(nro_doc) < 11:
tipo_doc = 96 # DNI
else:
tipo_doc = 80 # CUIT
# invoice amount totals:
imp_total = str("%.2f" % abs(invoice.amount_total))
imp_tot_conc = "0.00"
imp_neto = str("%.2f" % abs(invoice.amount_untaxed))
imp_iva = str("%.2f" % abs(invoice.amount_tax))
imp_subtotal = imp_neto # TODO: not allways the case!
imp_trib = "0.00"
imp_op_ex = "0.00"
if invoice.currency_id.name == 'ARS':
moneda_id = "PES"
moneda_ctz = 1
else:
moneda_id = {'USD':'DOL'}[invoice.currency_id.name]
moneda_ctz = str(invoice.currency_id.rate)
# foreign trade data: export permit, country code, etc.:
if invoice.pyafipws_incoterms:
incoterms = invoice.pyafipws_incoterms.code
incoterms_ds = invoice.pyafipws_incoterms.name
else:
incoterms = incoterms_ds = None
if int(tipo_cbte) == 19 and tipo_expo == 1:
permiso_existente = "N" or "S" # not used now
else:
permiso_existente = ""
obs_generales = invoice.comment
if invoice.payment_term:
forma_pago = invoice.payment_term.name
obs_comerciales = invoice.payment_term.name
else:
forma_pago = obs_comerciales = None
idioma_cbte = 1 # invoice language: spanish / español
# customer data (foreign trade):
nombre_cliente = invoice.partner_id.name
if invoice.partner_id.vat:
if invoice.partner_id.vat.startswith("AR"):
# use the Argentina AFIP's global CUIT for the country:
cuit_pais_cliente = invoice.partner_id.vat[2:]
id_impositivo = None
else:
# use the VAT number directly
id_impositivo = invoice.partner_id.vat[2:]
# TODO: the prefix could be used to map the customer country
cuit_pais_cliente = None
else:
cuit_pais_cliente = id_impositivo = None
if invoice.address_invoice_id:
domicilio_cliente = " - ".join([
invoice.address_invoice_id.name or '',
invoice.address_invoice_id.street or '',
invoice.address_invoice_id.street2 or '',
invoice.address_invoice_id.zip or '',
invoice.address_invoice_id.city or '',
])
else:
domicilio_cliente = ""
if invoice.address_invoice_id.country_id:
# map ISO country code to AFIP destination country code:
iso_code = invoice.address_invoice_id.country_id.code.lower()
pais_dst_cmp = AFIP_COUNTRY_CODE_MAP[iso_code]
# create the invoice internally in the helper
if service == 'wsfe':
ws.CrearFactura(concepto, tipo_doc, nro_doc, tipo_cbte, punto_vta,
cbt_desde, cbt_hasta, imp_total, imp_tot_conc, imp_neto,
imp_iva, imp_trib, imp_op_ex, fecha_cbte, fecha_venc_pago,
fecha_serv_desde, fecha_serv_hasta,
moneda_id, moneda_ctz)
elif service == 'wsmtxca':
ws.CrearFactura(concepto, tipo_doc, nro_doc, tipo_cbte, punto_vta,
cbt_desde, cbt_hasta, imp_total, imp_tot_conc, imp_neto,
imp_subtotal, imp_trib, imp_op_ex, fecha_cbte,
fecha_venc_pago, fecha_serv_desde, fecha_serv_hasta,
moneda_id, moneda_ctz, obs_generales)
elif service == 'wsfex':
ws.CrearFactura(tipo_cbte, punto_vta, cbte_nro, fecha_cbte,
imp_total, tipo_expo, permiso_existente, pais_dst_cmp,
nombre_cliente, cuit_pais_cliente, domicilio_cliente,
id_impositivo, moneda_id, moneda_ctz, obs_comerciales,
obs_generales, forma_pago, incoterms,
idioma_cbte, incoterms_ds)
# analyze VAT (IVA) and other taxes (tributo):
if service in ('wsfe', 'wsmtxca'):
for tax_line in invoice.tax_line:
if "IVA" in tax_line.name:
if '0%' in tax_line.name:
iva_id = 3
elif '10,5%' in tax_line.name:
iva_id = 4
elif '21%' in tax_line.name:
iva_id = 5
elif '27%' in tax_line.name:
iva_id = 6
else:
iva_id = 0
base_imp = ("%.2f" % abs(tax_line.base))
importe = ("%.2f" % abs(tax_line.amount))
# add the vat detail in the helper
ws.AgregarIva(iva_id, base_imp, importe)
else:
if 'impuesto' in tax_line.name.lower():
tributo_id = 1 # nacional
elif 'iibbb' in tax_line.name.lower():
tributo_id = 3 # provincial
elif 'tasa' in tax_line.name.lower():
tributo_id = 4 # municipal
else:
tributo_id = 99
desc = tax_line.name
base_imp = ("%.2f" % abs(tax_line.base))
importe = ("%.2f" % abs(tax_line.amount))
alic = "%.2f" % tax_line.base
# add the other tax detail in the helper
ws.AgregarTributo(id, desc, base_imp, alic, importe)
# analize line items - invoice detail
if service in ('wsfex', 'wsmtxca'):
for line in invoice.invoice_line:
codigo = line.product_id.code
u_mtx = 1 # TODO: get it from uom?
cod_mtx = line.product_id.ean13
ds = line.name
qty = line.quantity
umed = 7 # TODO: line.uos_id...?
precio = line.price_unit
importe = line.price_subtotal
bonif = line.discount or None
if line.invoice_line_tax_id:
iva_id = 5 # TODO: line.tax_code_id?
imp_iva = importe * line.invoice_line_tax_id[0].amount
else:
iva_id = 1
imp_iva = 0
if service == 'wsmtxca':
ws.AgregarItem(u_mtx, cod_mtx, codigo, ds, qty, umed,
precio, bonif, iva_id, imp_iva, importe+imp_iva)
elif service == 'wsfex':
ws.AgregarItem(codigo, ds, qty, umed, precio, importe,
bonif)
# Request the authorization! (call the AFIP webservice method)
vto = None
try:
if service == 'wsfe':
ws.CAESolicitar()
vto = ws.Vencimiento
elif service == 'wsmtxca':
ws.AutorizarComprobante()
vto = ws.Vencimiento
elif service == 'wsfex':
ws.Authorize(invoice.id)
vto = ws.FchVencCAE
except SoapFault as fault:
msg = 'Falla SOAP %s: %s' % (fault.faultcode, fault.faultstring)
except Exception, e:
if ws.Excepcion:
# get the exception already parsed by the helper
msg = ws.Excepcion
else:
# avoid encoding problem when reporting exceptions to the user:
msg = traceback.format_exception_only(sys.exc_type,
sys.exc_value)[0]
else:
msg = u"\n".join([ws.Obs or "", ws.ErrMsg or ""])
# calculate the barcode:
if ws.CAE:
cae_due = ''.join([c for c in str(vto or '')
if c.isdigit()])
bars = ''.join([str(ws.Cuit), "%02d" % int(tipo_cbte),
"%04d" % int(punto_vta),
str(ws.CAE), cae_due])
bars = bars + self.pyafipws_verification_digit_modulo10(bars)
else:
bars = ""
# store the results
self.write(cr, uid, invoice.id,
{'pyafipws_cae': ws.CAE,
'pyafipws_cae_due_date': vto or None,
'pyafipws_result': ws.Resultado,
'pyafipws_message': msg,
'pyafipws_xml_request': ws.XmlRequest,
'pyafipws_xml_response': ws.XmlResponse,
'pyafipws_barcode': bars,
})
def action_pyafipws_request_cae(self, cr, uid, ids, *args):
"Request to AFIP the invoices' Authorization Electronic Code (CAE)"
for i, invoice in enumerate(self.browse(cr, uid, ids)):
# request authorization (CAE)
self.do_pyafipws_request_cae(cr, uid, ids, *args)
# check if an error message was returned
msg = invoice.pyafipws_message
if not invoice.pyafipws_cae and msg:
# notify the user with an exception message
raise osv.except_osv('Error al solicitar CAE AFIP', msg)
else:
# TODO: use better notification (log not shown in workflow)
msg = "CAE: %s Vto.: %s Resultado: %s"
msg = msg % (invoice.pyafipws_cae,
invoice.pyafipws_cae_due_date,
invoice.pyafipws_result)
self.log(cr, uid, ids[i], msg)
def pyafipws_verification_digit_modulo10(self, codigo):
"Calculate the verification digit 'modulo 10'"
# http://www.consejo.org.ar/Bib_elect/diciembre04_CT/documentos/rafip1702.htm
# Step 1: sum all digits in odd positions, left to right
codigo = codigo.strip()
if not codigo or not codigo.isdigit():
return ''
etapa1 = sum([int(c) for i,c in enumerate(codigo) if not i%2])
# Step 2: multiply the step 1 sum by 3
etapa2 = etapa1 * 3
# Step 3: start from the left, sum all the digits in even positions
etapa3 = sum([int(c) for i,c in enumerate(codigo) if i%2])
# Step 4: sum the results of step 2 and 3
etapa4 = etapa2 + etapa3
# Step 5: the minimun value that summed to step 4 is a multiple of 10
digito = 10 - (etapa4 - (int(etapa4 / 10) * 10))
if digito == 10:
digito = 0
return str(digito)
def _get_pyafipws_barcode_img(self, cr, uid, ids, field_name, arg, context):
"Generate the required barcode Interleaved of 7 image using PIL"
from pyafipws.pyi25 import PyI25
from cStringIO import StringIO as StringIO
# create the helper:
pyi25 = PyI25()
images = {}
for invoice in self.browse(cr, uid, ids):
if not invoice.pyafipws_barcode:
continue
output = StringIO()
# call the helper:
bars = ''.join([c for c in invoice.pyafipws_barcode if c.isdigit()])
if not bars:
bars = "00"
pyi25.GenerarImagen(bars, output, extension="PNG")
# get the result and encode it for openerp binary field:
images[invoice.id] = output.getvalue().encode("base64")
output.close()
return images
# add the computed columns:
_columns.update({
'pyafipws_barcode_img': fields.function(
_get_pyafipws_barcode_img, type='binary', method=True, store=False),
})
electronic_invoice()
class invoice_wizard(osv.osv_memory):
_name = 'pyafipws.invoice.wizard'
_columns = {
'journal': fields.many2one('account.journal', 'Journal'),
'cbte_nro': fields.integer('number'),
'cae': fields.char('CAE', size=14, readonly=True, ),
'cae_due': fields.char('Vencimiento CAE', size=10, readonly=True, ),
'total': fields.float('Total', readonly=True, ),
'vat': fields.float('IVA', readonly=True, ),
}
def get(self,cr,uid,ids,context={}):
#invoice = self.pool.get('account.invoice')
for wiz in self.browse(cr, uid, ids):
company = wiz.journal.company_id
tipo_cbte = wiz.journal.pyafipws_invoice_type
punto_vta = wiz.journal.pyafipws_point_of_sale
service = wiz.journal.pyafipws_electronic_invoice_service
# check if it is an electronic invoice sale point:
if not tipo_cbte or not punto_vta or not service:
raise osv.except_osv('Error !', "Solo factura electrónica")
# authenticate against AFIP:
auth_data = company.pyafipws_authenticate(service=service)
# create the proxy and get the configuration system parameters:
cfg = self.pool.get('ir.config_parameter')
cache = cfg.get_param(cr, uid, 'pyafipws.cache', context=context)
proxy = cfg.get_param(cr, uid, 'pyafipws.proxy', context=context)
wsdl = cfg.get_param(cr, uid, 'pyafipws.%s.url' % service, context=context)
# import the AFIP webservice helper for electronic invoice
if service == 'wsfe':
from pyafipws.wsfev1 import WSFEv1, SoapFault # local market
ws = WSFEv1()
elif service == 'wsmtxca':
from pyafipws.wsmtx import WSMTXCA, SoapFault # local + detail
wsdl = cfg.get_param(cr, uid, 'pyafipws.wsmtxca.url', context=context)
ws = WSMTXCA()
elif service == 'wsfex':
from pyafipws.wsfexv1 import WSFEXv1, SoapFault # foreign trade
wsdl = cfg.get_param(cr, uid, 'pyafipws.wsfex.url', context=context)
ws = WSFEXv1()
else:
raise osv.except_osv('Error !', "%s no soportado" % service)
# connect to the webservice and call to the test method
ws.Conectar(cache or "", wsdl or "", proxy or "")
# set AFIP webservice credentials:
ws.Cuit = company.pyafipws_cuit
ws.Token = auth_data['token']
ws.Sign = auth_data['sign']
if service in ('wsfe', 'wsmtxca'):
if not wiz.cbte_nro:
wiz.cbte_nro = ws.CompUltimoAutorizado(tipo_cbte, punto_vta)
ws.CompConsultar(tipo_cbte, punto_vta, wiz.cbte_nro)
vat = ws.ImptoLiq
else:
if not wiz.cbte_nro:
wiz.cbte_nro = ws.GetLastCMP(tipo_cbte, punto_vta)
ws.GetCMP(tipo_cbte, punto_vta, wiz.cbte_nro)
vat = 0
# update the form fields with the values returned from AFIP:
self.write(cr, uid, ids, {'cae': ws.CAE, 'cae_due': ws.Vencimiento,
'total': ws.ImpTotal or 0, 'vat': vat,
'cbte_nro': ws.CbteNro,
}, context=context)
return {
'type': 'ir.actions.act_window',
'res_model': 'pyafipws.invoice.wizard',
'view_mode': 'form',
'view_type': 'form',
'res_id': wiz.id,
'views': [(False, 'form')],
'target': 'new',
}
invoice_wizard()
class report_pyfepdf(report_int):
"Basic PDF template report for Argentina Electronic Invoices (using FPDF)"
def create(self, cr, uid, ids, datas, context):
import pooler
pool = pooler.get_pool(cr.dbname)
model_obj = pool.get("account.invoice")
try:
# import and instantiate the helper:
from pyafipws.pyfepdf import FEPDF
fepdf = FEPDF()
# load CSV default format (factura.csv)
fepdf.CargarFormato(os.path.join(os.path.dirname(__file__),
"pyafipws", "factura.csv"))
# set the logo image
fepdf.AgregarDato("logo", os.path.join(os.path.dirname(__file__),
"pyafipws", "logo.png"))
# set amount format (decimal places):
fepdf.FmtCantidad = "0.2"
fepdf.FmtPrecio = "0.2"
# TODO: complete all fields and additional data
for invoice in model_obj.browse(cr, uid, ids)[:1]:
# get the electronic invoice type, point of sale and service:
journal = invoice.journal_id
company = journal.company_id
tipo_cbte = journal.pyafipws_invoice_type
punto_vta = journal.pyafipws_point_of_sale
# set basic AFIP data:
fepdf.CUIT = company.pyafipws_cuit
# get the last 8 digit of the invoice number
cbte_nro = int(invoice.number[-8:])
cbt_desde = cbt_hasta = cbte_nro
fecha_cbte = invoice.date_invoice
concepto = tipo_expo = int(invoice.pyafipws_concept or 0)
if int(concepto) != 1:
fecha_venc_pago = invoice.date_invoice
if invoice.pyafipws_billing_start_date:
fecha_serv_desde = invoice.pyafipws_billing_start_date
else:
fecha_serv_desde = None
if invoice.pyafipws_billing_end_date:
fecha_serv_hasta = invoice.pyafipws_billing_end_date
else:
fecha_serv_hasta = None
else:
fecha_venc_pago = fecha_serv_desde = fecha_serv_hasta = None
# customer tax number:
if invoice.partner_id.vat:
nro_doc = invoice.partner_id.vat.replace("-","")
else:
nro_doc = "0" # only "consumidor final"
tipo_doc = 99
if nro_doc.startswith("AR"):
nro_doc = nro_doc[2:]
if int(nro_doc) == 0:
tipo_doc = 99 # consumidor final
elif len(nro_doc) < 11:
tipo_doc = 96 # DNI
else:
tipo_doc = 80 # CUIT
# invoice amount totals:
imp_total = str("%.2f" % abs(invoice.amount_total))
imp_tot_conc = "0.00"
imp_neto = str("%.2f" % abs(invoice.amount_untaxed))
imp_iva = str("%.2f" % abs(invoice.amount_tax))
imp_subtotal = imp_neto # TODO: not allways the case!
imp_trib = "0.00"
imp_op_ex = "0.00"
if invoice.currency_id.name == 'ARS':
moneda_id = "PES"
moneda_ctz = 1
else:
moneda_id = {'USD':'DOL'}[invoice.currency_id.name]
moneda_ctz = str(invoice.currency_id.rate)
# foreign trade data: export permit, country code, etc.:
if invoice.pyafipws_incoterms:
incoterms = invoice.pyafipws_incoterms.code
incoterms_ds = invoice.pyafipws_incoterms.name
else:
incoterms = incoterms_ds = None
if int(tipo_cbte) == 19 and tipo_expo == 1:
permiso_existente = "N" or "S" # not used now
else:
permiso_existente = ""
obs_generales = invoice.comment
if invoice.payment_term:
forma_pago = invoice.payment_term.name
obs_comerciales = invoice.payment_term.name
else:
forma_pago = obs_comerciales = None
idioma_cbte = 1 # invoice language: spanish / español
# customer data (foreign trade):
nombre_cliente = invoice.partner_id.name
if invoice.partner_id.vat:
if invoice.partner_id.vat.startswith("AR"):
# use the Argentina AFIP's global CUIT for the country:
cuit_pais_cliente = invoice.partner_id.vat[2:]
id_impositivo = None
else:
# use the VAT number directly
id_impositivo = invoice.partner_id.vat[2:]
# TODO: the prefix could be used to map the customer country
cuit_pais_cliente = None
else:
cuit_pais_cliente = id_impositivo = None
if invoice.address_invoice_id:
domicilio_cliente = " - ".join([
invoice.address_invoice_id.name or '',
invoice.address_invoice_id.street or '',
invoice.address_invoice_id.street2 or '',
])
localidad_cliente = " - ".join([
invoice.address_invoice_id.city or '',
invoice.address_invoice_id.zip or '',
])
provincia_cliente = invoice.address_invoice_id.state_id
else:
domicilio_cliente = ""
localidad_cliente = ""
provincia_cliente = ""
if invoice.address_invoice_id.country_id:
# map ISO country code to AFIP destination country code:
iso_code = invoice.address_invoice_id.country_id.code.lower()
pais_dst_cmp = AFIP_COUNTRY_CODE_MAP[iso_code]
# set AFIP returned values
cae = invoice.pyafipws_cae or ""
fch_venc_cae = (invoice.pyafipws_cae_due_date or "").replace("-", "")
motivo = invoice.pyafipws_message or ""
# create the invoice internally in the helper
fepdf.CrearFactura(concepto, tipo_doc, nro_doc, tipo_cbte, punto_vta,
cbte_nro, imp_total, imp_tot_conc, imp_neto,
imp_iva, imp_trib, imp_op_ex, fecha_cbte, fecha_venc_pago,
fecha_serv_desde, fecha_serv_hasta,
moneda_id, moneda_ctz, cae, fch_venc_cae, id_impositivo,
nombre_cliente, domicilio_cliente, pais_dst_cmp,
obs_comerciales, obs_generales, forma_pago, incoterms,
idioma_cbte, motivo)
# set custom extra data:
fepdf.AgregarDato("localidad_cliente", localidad_cliente)
fepdf.AgregarDato("provincia_cliente", provincia_cliente)
# analyze VAT (IVA) and other taxes (tributo):
for tax_line in invoice.tax_line:
if "IVA" in tax_line.name:
if '0%' in tax_line.name:
iva_id = 3
elif '10,5%' in tax_line.name:
iva_id = 4
elif '21%' in tax_line.name:
iva_id = 5
elif '27%' in tax_line.name:
iva_id = 6
else:
ivva_id = 0
base_imp = ("%.2f" % abs(tax_line.base))
importe = ("%.2f" % abs(tax_line.amount))
# add the vat detail in the helper
fepdf.AgregarIva(iva_id, base_imp, importe)
else:
if 'impuesto' in tax_line.name.lower():
tributo_id = 1 # nacional
elif 'iibbb' in tax_line.name.lower():
tributo_id = 3 # provincial
elif 'tasa' in tax_line.name.lower():
tributo_id = 4 # municipal
else:
tributo_id = 99
desc = tax_line.name
base_imp = ("%.2f" % abs(tax_line.base))
importe = ("%.2f" % abs(tax_line.amount))
alic = "%.2f" % tax_line.base
# add the other tax detail in the helper
fepdf.AgregarTributo(id, desc, base_imp, alic, importe)
# analize line items - invoice detail
for line in invoice.invoice_line:
codigo = line.product_id.code
u_mtx = 1 # TODO: get it from uom?
cod_mtx = line.product_id.ean13
ds = line.name
qty = line.quantity
umed = 7 # TODO: line.uos_id...?
precio = line.price_unit
importe = line.price_subtotal
bonif = line.discount or None
iva_id = 5 # TODO: line.tax_code_id?
imp_iva = importe * line.invoice_line_tax_id[0].amount
fepdf.AgregarDetalleItem(u_mtx, cod_mtx, codigo, ds, qty, umed,
precio, bonif, iva_id, imp_iva, importe, despacho="")
fepdf.CrearPlantilla(papel="A4", orientacion="portrait")
fepdf.ProcesarPlantilla(num_copias=1, lineas_max=24, qty_pos='izq')
report_type = datas.get('report_type', 'pdf')
pdf = fepdf.GenerarPDF()
return (pdf, report_type)
except:
# catch the exception and send a better message for the user:
from pyafipws import utils
ex = utils.exception_info()
raise osv.except_osv(ex['msg'], ex['tb'])
report_pyfepdf('report.pyafipws.invoice')