Created
August 9, 2016 20:42
-
-
Save mgscreativa/b4f32c43865148071c96a1ded3de048a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
############################################################################## | |
# For copyright and license notices, see __openerp__.py file in module root | |
# directory | |
############################################################################## | |
from pyi25 import PyI25 | |
from openerp import fields, models, api, _ | |
from openerp.exceptions import Warning | |
from cStringIO import StringIO as StringIO | |
import logging | |
import sys | |
import traceback | |
_logger = logging.getLogger(__name__) | |
try: | |
from pyafipws.soap import SoapFault | |
except ImportError: | |
_logger.debug('Can not `from pyafipws.soap import SoapFault`.') | |
class invoice(models.Model): | |
_inherit = "account.invoice" | |
afip_batch_number = fields.Integer( | |
copy=False, | |
string='Batch Number', | |
readonly=True | |
) | |
afip_cae = fields.Char( | |
copy=False, | |
string='CAE number', | |
readonly=True, | |
size=24, | |
states={'draft': [('readonly', False)]}, | |
) | |
afip_cae_due = fields.Date( | |
copy=False, | |
readonly=True, | |
string='CAE due Date', | |
states={'draft': [('readonly', False)]}, | |
) | |
afip_barcode = fields.Char( | |
compute='_get_barcode', | |
string=_('AFIP Barcode') | |
) | |
afip_barcode_img = fields.Binary( | |
compute='_get_barcode', | |
string=_('AFIP Barcode Image') | |
) | |
afip_message = fields.Text( | |
string='AFIP Message', | |
copy=False, | |
) | |
afip_xml_request = fields.Text( | |
string='AFIP XML Request', | |
copy=False, | |
) | |
afip_xml_response = fields.Text( | |
string='AFIP XML Response', | |
copy=False, | |
) | |
afip_result = fields.Selection([ | |
('', 'n/a'), | |
('A', 'Aceptado'), | |
('R', 'Rechazado'), | |
('O', 'Observado')], | |
'Resultado', | |
readonly=True, | |
states={'draft': [('readonly', False)]}, | |
copy=False, | |
help="AFIP request result" | |
) | |
validation_type = fields.Char( | |
'Validation Type', | |
compute='get_validation_type', | |
) | |
@api.one | |
def get_validation_type(self): | |
# for compatibility with account_invoice_operation, if module installed | |
# and there are operations we return no_validation so no validate | |
# button is displayed | |
if self._fields.get('operation_ids') and self.operation_ids: | |
self.validation_type = 'no_validation' | |
# if invoice has cae then me dont validate it against afip | |
elif self.journal_id.point_of_sale_id.afip_ws and not self.afip_cae: | |
self.validation_type = self.env[ | |
'res.company']._get_environment_type() | |
@api.one | |
@api.depends('afip_cae') | |
def _get_barcode(self): | |
barcode = False | |
if self.afip_cae: | |
cae_due = ''.join( | |
[c for c in str(self.afip_cae_due or '') if c.isdigit()]) | |
barcode = ''.join( | |
[str(self.company_id.partner_id.vat[2:]), | |
"%02d" % int(self.afip_document_class_id.afip_code), | |
"%04d" % int(self.journal_id.point_of_sale_id.number), | |
str(self.afip_cae), cae_due]) | |
barcode = barcode + self.verification_digit_modulo10(barcode) | |
self.afip_barcode = barcode | |
"Generate the required barcode Interleaved of 7 image using PIL" | |
image = False | |
if barcode: | |
# create the helper: | |
pyi25 = PyI25() | |
output = StringIO() | |
# call the helper: | |
bars = ''.join([c for c in 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: | |
image = output.getvalue() | |
image = output.getvalue().encode("base64") | |
output.close() | |
self.afip_barcode_img = image | |
@api.model | |
def verification_digit_modulo10(self, code): | |
"Calculate the verification digit 'modulo 10'" | |
# Step 1: sum all digits in odd positions, left to right | |
code = code.strip() | |
if not code or not code.isdigit(): | |
return '' | |
etapa1 = sum([int(c) for i, c in enumerate(code) 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(code) 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) | |
@api.multi | |
def get_related_invoices_data(self): | |
""" | |
List related invoice information to fill CbtesAsoc. | |
""" | |
self.ensure_one() | |
rel_invoices = self.search([ | |
('number', '=', self.origin), | |
('state', 'not in', | |
['draft', 'proforma', 'proforma2', 'cancel'])]) | |
return rel_invoices | |
@api.multi | |
# def invoice_validate(self): | |
def action_number(self): | |
""" | |
We would prefere use invoice_validate or call request cae after | |
action_number so that CAE is requested at the last part but raise error | |
can not fall back the sequence next number. | |
We want to add a inv._cr.commit() after sucesfuul cae request because | |
we dont want to loose cae data because of a raise error on next steps | |
but it doesn work as expected | |
""" | |
self.do_pyafipws_request_cae() | |
# self._cr.commit() | |
res = super(invoice, self).action_number() | |
return res | |
@api.multi | |
def do_pyafipws_request_cae(self): | |
"Request to AFIP the invoices' Authorization Electronic Code (CAE)" | |
for inv in self: | |
# Ignore invoices with cae | |
if inv.afip_cae and inv.afip_cae_due: | |
continue | |
afip_ws = self.journal_id.point_of_sale_id.afip_ws | |
# Ignore invoice if not ws on point of sale | |
if not afip_ws: | |
continue | |
# get the electronic invoice type, point of sale and afip_ws: | |
commercial_partner = inv.commercial_partner_id | |
country = commercial_partner.country_id | |
journal = inv.journal_id | |
point_of_sale = journal.point_of_sale_id | |
pos_number = point_of_sale.number | |
doc_afip_code = self.afip_document_class_id.afip_code | |
# authenticate against AFIP: | |
ws = self.company_id.get_connection(afip_ws).connect() | |
next_invoice_number = inv.next_invoice_number | |
# get the last invoice number registered in AFIP | |
if afip_ws == "wsfe" or afip_ws == "wsmtxca": | |
ws_invoice_number = ws.CompUltimoAutorizado( | |
doc_afip_code, pos_number) | |
elif afip_ws == 'wsfex': | |
ws_invoice_number = ws.GetLastCMP( | |
doc_afip_code, pos_number) | |
if not country: | |
raise Warning(_( | |
'For WS "%s" country is required on partner' % ( | |
afip_ws))) | |
elif not country.code: | |
raise Warning(_( | |
'For WS "%s" country code is mandatory' | |
'Country: %s' % ( | |
afip_ws, country.name))) | |
elif not country.afip_code: | |
raise Warning(_( | |
'For WS "%s" country afip code is mandatory' | |
'Country: %s' % ( | |
afip_ws, country.name))) | |
ws_next_invoice_number = int(ws_invoice_number) + 1 | |
# verify that the invoice is the next one to be registered in AFIP | |
if next_invoice_number != ws_next_invoice_number: | |
raise Warning(_( | |
'Error!' | |
'Invoice id: %i' | |
'Next invoice number should be %i and not %i' % ( | |
inv.id, | |
ws_next_invoice_number, | |
next_invoice_number))) | |
partner_doc_code = commercial_partner.document_type_id.afip_code | |
tipo_doc = partner_doc_code or '99' | |
nro_doc = ( | |
partner_doc_code and commercial_partner.document_number or "0") | |
cbt_desde = cbt_hasta = cbte_nro = next_invoice_number | |
concepto = tipo_expo = int(inv.afip_concept) | |
fecha_cbte = inv.date_invoice | |
if afip_ws != 'wsmtxca': | |
fecha_cbte = fecha_cbte.replace("-", "") | |
# due and billing dates only for concept "services" | |
if int(concepto) != 1: | |
fecha_venc_pago = inv.date_due | |
fecha_serv_desde = inv.afip_service_start | |
fecha_serv_hasta = inv.afip_service_end | |
if afip_ws != 'wsmtxca': | |
fecha_venc_pago = fecha_venc_pago.replace("-", "") | |
fecha_serv_desde = fecha_serv_desde.replace("-", "") | |
fecha_serv_hasta = fecha_serv_hasta.replace("-", "") | |
else: | |
fecha_venc_pago = fecha_serv_desde = fecha_serv_hasta = None | |
# # invoice amount totals: | |
imp_total = str("%.2f" % abs(inv.amount_total)) | |
# ImpTotConc es el iva no gravado, si es Comprobante C debe ser 0.00 | |
if doc_afip_code != 11: | |
imp_tot_conc = str("%.2f" % abs(inv.vat_untaxed)) | |
else: | |
imp_tot_conc = "0.00" | |
if doc_afip_code != 11: | |
# en la v9 lo hicimos diferente, aca restamos al vat amount | |
# lo que seria exento y no gravado | |
imp_neto = str("%.2f" % abs( | |
inv.vat_base_amount - inv.vat_untaxed - inv.vat_exempt_amount)) | |
else: | |
# tomado de la v9 | |
imp_neto = str("%.2f" % abs(inv.amount_untaxed)) | |
imp_iva = str("%.2f" % abs(inv.vat_amount)) | |
imp_subtotal = str("%.2f" % abs(inv.amount_untaxed)) | |
imp_trib = str("%.2f" % abs(inv.other_taxes_amount)) | |
imp_op_ex = str("%.2f" % abs(inv.vat_exempt_amount)) | |
moneda_id = inv.currency_id.afip_code | |
moneda_ctz = inv.currency_rate | |
# moneda_ctz = str(inv.company_id.currency_id.compute( | |
# 1., inv.currency_id)) | |
# # foreign trade data: export permit, country code, etc.: | |
if inv.afip_incoterm_id: | |
incoterms = inv.afip_incoterm_id.afip_code | |
incoterms_ds = inv.afip_incoterm_id.name | |
else: | |
incoterms = incoterms_ds = None | |
if int(doc_afip_code) in [19, 20, 21] and tipo_expo == 1: | |
permiso_existente = "N" or "S" # not used now | |
else: | |
permiso_existente = "" | |
obs_generales = inv.comment | |
if inv.payment_term: | |
forma_pago = inv.payment_term.name | |
obs_comerciales = inv.payment_term.name | |
else: | |
forma_pago = obs_comerciales = None | |
idioma_cbte = 1 # invoice language: spanish / español | |
# customer data (foreign trade): | |
nombre_cliente = commercial_partner.name | |
# If argentinian and cuit, then use cuit | |
if country.code == 'AR' and tipo_doc == 80 and nro_doc: | |
id_impositivo = nro_doc | |
cuit_pais_cliente = None | |
# If not argentinian and vat, use vat | |
elif country.code != 'AR' and commercial_partner.vat: | |
id_impositivo = commercial_partner.vat[2:] | |
cuit_pais_cliente = None | |
# else use cuit pais cliente | |
else: | |
id_impositivo = None | |
if commercial_partner.is_company: | |
cuit_pais_cliente = country.cuit_juridica | |
else: | |
cuit_pais_cliente = country.cuit_fisica | |
domicilio_cliente = " - ".join([ | |
commercial_partner.name or '', | |
commercial_partner.street or '', | |
commercial_partner.street2 or '', | |
commercial_partner.zip or '', | |
commercial_partner.city or '', | |
]) | |
pais_dst_cmp = commercial_partner.country_id.afip_code | |
# create the invoice internally in the helper | |
if afip_ws == 'wsfe': | |
ws.CrearFactura( | |
concepto, tipo_doc, nro_doc, doc_afip_code, pos_number, | |
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 afip_ws == 'wsmtxca': | |
ws.CrearFactura( | |
concepto, tipo_doc, nro_doc, doc_afip_code, pos_number, | |
cbt_desde, cbt_hasta, imp_total, imp_tot_conc, imp_neto, | |
imp_subtotal, # difference with wsfe | |
imp_trib, imp_op_ex, fecha_cbte, fecha_venc_pago, | |
fecha_serv_desde, fecha_serv_hasta, | |
moneda_id, moneda_ctz, | |
obs_generales # difference with wsfe | |
) | |
elif afip_ws == 'wsfex': | |
ws.CrearFactura( | |
doc_afip_code, pos_number, 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 | |
) | |
# TODO ver si en realidad tenemos que usar un vat pero no lo | |
# subimos | |
if afip_ws != 'wsfex': | |
for vat in self.vat_tax_ids: | |
# we dont send no gravado y exento | |
if vat.tax_code_id.afip_code in [1, 2]: | |
continue | |
_logger.info('Adding VAT %s' % vat.tax_code_id.name) | |
ws.AgregarIva( | |
vat.tax_code_id.afip_code, | |
"%.2f" % abs(vat.base_amount), | |
"%.2f" % abs(vat.tax_amount), | |
) | |
for tax in self.not_vat_tax_ids: | |
_logger.info('Adding TAX %s' % tax.tax_code_id.name) | |
ws.AgregarTributo( | |
tax.tax_code_id.application_code, | |
tax.tax_code_id.name, | |
"%.2f" % abs(tax.base_amount), | |
# como no tenemos la alicuota pasamos cero, en v9 | |
# podremos pasar la alicuota | |
0, | |
"%.2f" % abs(tax.tax_amount), | |
) | |
CbteAsoc = inv.get_related_invoices_data() | |
if CbteAsoc: | |
ws.AgregarCmpAsoc( | |
CbteAsoc.afip_document_class_id.afip_code, | |
CbteAsoc.point_of_sale, | |
CbteAsoc.invoice_number, | |
) | |
# analize line items - invoice detail | |
# wsfe do not require detail | |
if afip_ws != 'wsfe': | |
for line in inv.invoice_line: | |
codigo = line.product_id.code | |
# unidad de referencia del producto si se comercializa | |
# en una unidad distinta a la de consumo | |
if not line.uos_id.afip_code: | |
raise Warning(_('Not afip code con producto UOM %s' % ( | |
line.uos_id.name))) | |
cod_mtx = line.uos_id.afip_code | |
ds = line.name | |
qty = line.quantity | |
umed = line.uos_id.afip_code | |
precio = line.price_unit | |
importe = line.price_subtotal | |
bonif = line.discount or None | |
if afip_ws == 'wsmtxca': | |
if not line.product_id.uom_id.afip_code: | |
raise Warning(_('Not afip code con producto UOM %s' % ( | |
line.product_id.uom_id.name))) | |
u_mtx = line.product_id.uom_id.afip_code or line.uos_id.afip_code | |
if self.invoice_id.type in ('out_invoice', 'in_invoice'): | |
iva_id = line.vat_tax_ids.tax_code_id.afip_code | |
else: | |
iva_id = line.vat_tax_ids.ref_tax_code_id.afip_code | |
vat_taxes_amounts = line.vat_tax_ids.compute_all( | |
line.price_unit, line.quantity, | |
product=self.product_id, | |
partner=self.invoice_id.partner_id) | |
imp_iva = vat_taxes_amounts[ | |
'total_included'] - vat_taxes_amounts['total'] | |
ws.AgregarItem( | |
u_mtx, cod_mtx, codigo, ds, qty, umed, | |
precio, bonif, iva_id, imp_iva, importe + imp_iva) | |
elif afip_ws == 'wsfex': | |
ws.AgregarItem( | |
codigo, ds, qty, umed, precio, importe, | |
bonif) | |
# Request the authorization! (call the AFIP webservice method) | |
vto = None | |
msg = False | |
try: | |
if afip_ws == 'wsfe': | |
ws.CAESolicitar() | |
vto = ws.Vencimiento | |
elif afip_ws == 'wsmtxca': | |
ws.AutorizarComprobante() | |
vto = ws.Vencimiento | |
elif afip_ws == 'wsfex': | |
ws.Authorize(inv.id) | |
vto = ws.FchVencCAE | |
except SoapFault as fault: | |
msg = 'Falla SOAP %s: %s' % ( | |
fault.faultcode, fault.faultstring) | |
except Exception, e: | |
msg = e | |
except Exception: | |
if ws.Excepcion: | |
# get the exception already parsed by the helper | |
msg = ws.Excepcion | |
else: | |
# avoid encoding problem when raising error | |
msg = traceback.format_exception_only( | |
sys.exc_type, | |
sys.exc_value)[0] | |
if msg: | |
_logger.info(_('AFIP Validation Error. %s' % msg)+' XML Request: %s XML Response: %s' % ( | |
ws.XmlRequest, ws.XmlResponse)) | |
raise Warning(_('AFIP Validation Error. %s' % msg)) | |
msg = u"\n".join([ws.Obs or "", ws.ErrMsg or ""]) | |
if not ws.CAE or ws.Resultado != 'A': | |
raise Warning(_('AFIP Validation Error. %s' % msg)) | |
# TODO ver que algunso campos no tienen sentido porque solo se | |
# escribe aca si no hay errores | |
_logger.info('CAE solicitado con exito. CAE: %s. Resultado %s' % ( | |
ws.CAE, ws.Resultado)) | |
inv.write({ | |
'afip_cae': ws.CAE, | |
'afip_cae_due': vto, | |
'afip_result': ws.Resultado, | |
'afip_message': msg, | |
'afip_xml_request': ws.XmlRequest, | |
'afip_xml_response': ws.XmlResponse, | |
}) | |
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment