Merge branch 'develop'

Importar XML
This commit is contained in:
Mauricio Baeza 2018-02-07 00:48:26 -06:00
commit 5bfcc4ca07
2 changed files with 225 additions and 28 deletions

View File

@ -20,8 +20,8 @@ import zipfile
from io import BytesIO
from pathlib import Path
# ~ from smtplib import SMTPException, SMTPAuthenticationError
from xml.etree import ElementTree as ET
from xml.dom.minidom import parseString
try:
import uno
@ -1360,7 +1360,15 @@ def import_nomina(rfc):
def parse_xml(xml):
return ET.fromstring(xml)
try:
return ET.fromstring(xml)
except ET.ParseError:
return None
def to_pretty_xml(xml):
tree = parseString(xml)
return tree.toprettyxml(encoding='utf-8').decode('utf-8')
def get_dict(data):
@ -1445,8 +1453,9 @@ def _comprobante(doc, options):
pass
data['fechaformato'] = fecha.strftime('%A, %d de %B de %Y')
data['tipocambio'] = 'Tipo de Cambio: $ {:0.2f}'.format(
float(data['tipocambio']))
if 'tipocambio' in data:
data['tipocambio'] = 'Tipo de Cambio: $ {:0.2f}'.format(
float(data['tipocambio']))
data['notas'] = options['notas']
return data
@ -1503,10 +1512,17 @@ def _conceptos(doc, version, options):
continue
if version == '3.3':
values['noidentificacion'] = '{}\n(SAT {})'.format(
values['noidentificacion'], values['ClaveProdServ'])
values['unidad'] = '({})\n{}'.format(
values['ClaveUnidad'], values['unidad'])
if 'noidentificacion' in values:
values['noidentificacion'] = '{}\n(SAT {})'.format(
values['noidentificacion'], values['ClaveProdServ'])
else:
values['noidentificacion'] = 'SAT {}'.format(
values['ClaveProdServ'])
if 'unidad' in values:
values['unidad'] = '({})\n{}'.format(
values['ClaveUnidad'], values['unidad'])
else:
values['unidad'] = '{}'.format(values['ClaveUnidad'])
n = c.find('{}CuentaPredial'.format(PRE[version]))
if n is not None:
@ -1559,8 +1575,8 @@ def _totales(doc, cfdi, version):
for n in node.getchildren():
tmp = CaseInsensitiveDict(n.attrib.copy())
if version == '3.3':
title = 'Traslado {} {}'.format(
tn.get(tmp['impuesto']), tmp['tasaocuota'])
tasa = round(float(tmp['tasaocuota']), DECIMALES)
title = 'Traslado {} {}'.format(tn.get(tmp['impuesto']), tasa)
else:
title = 'Traslado {} {}'.format(tmp['impuesto'], tmp['tasa'])
traslados.append((title, float(tmp['importe'])))
@ -1691,7 +1707,7 @@ def _nomina(doc, data, values, version_cfdi):
def get_data_from_xml(invoice, values):
data = {'cancelada': invoice.cancelada, 'donativo': False}
if hasattr('invoice', 'donativo'):
if hasattr(invoice, 'donativo'):
data['donativo'] = invoice.donativo
doc = parse_xml(invoice.xml)
data['comprobante'] = _comprobante(doc, values)
@ -1883,14 +1899,6 @@ def upload_file(rfc, opt, file_obj):
name = '{}_nomina.ods'.format(rfc.lower())
path = _join(PATH_MEDIA, 'tmp', name)
elif opt == 'cfdixml':
tmp = file_obj.filename.split('.')
ext = tmp[-1].lower()
if ext != 'xml':
msg = 'Extensión de archivo incorrecta, selecciona un archivo XML'
return {'status': 'server', 'name': msg, 'ok': False}
return import_xml(file_obj.file.read())
if save_file(path, file_obj.file.read()):
return {'status': 'server', 'name': file_obj.filename, 'ok': True}
@ -3113,6 +3121,79 @@ class ImportFacturaLibre(object):
return data
class ImportCFDI(object):
def __init__(self, xml):
self._doc = xml
self._pre = ''
def _emisor(self):
emisor = self._doc.find('{}Emisor'.format(self._pre))
data = CaseInsensitiveDict(emisor.attrib.copy())
node = emisor.find('{}RegimenFiscal'.format(self._pre))
if not node is None:
data['regimen_fiscal'] = node.attrib['Regimen']
return data
def _receptor(self):
node = self._doc.find('{}Receptor'.format(self._pre))
data = CaseInsensitiveDict(node.attrib.copy())
node = node.find('{}Domicilio'.format(self._pre))
if not node is None:
data.update(node.attrib.copy())
return data
def _conceptos(self):
data = []
conceptos = self._doc.find('{}Conceptos'.format(self._pre))
for c in conceptos.getchildren():
values = CaseInsensitiveDict(c.attrib.copy())
data.append(values)
return data
def _impuestos(self):
data = {}
node = self._doc.find('{}Impuestos'.format(self._pre))
if not node is None:
data = CaseInsensitiveDict(node.attrib.copy())
return data
def _timbre(self):
node = self._doc.find('{}Complemento/{}TimbreFiscalDigital'.format(
self._pre, PRE['TIMBRE']))
data = CaseInsensitiveDict(node.attrib.copy())
data.pop('SelloCFD', None)
data.pop('SelloSAT', None)
data.pop('Version', None)
return data
def get_data(self):
invoice = CaseInsensitiveDict(self._doc.attrib.copy())
invoice.pop('certificado', '')
invoice.pop('sello', '')
self._pre = PRE[invoice['version']]
emisor = self._emisor()
receptor = self._receptor()
conceptos = self._conceptos()
impuestos = self._impuestos()
timbre = self._timbre()
invoice.update(emisor)
invoice.update(receptor)
invoice.update(impuestos)
invoice.update(timbre)
data = {
'invoice': invoice,
'emisor': emisor,
'receptor': receptor,
'conceptos': conceptos,
}
return data
def print_ticket(data, info):
p = PrintTicket(info)
return p.printer(data)
@ -3176,12 +3257,3 @@ def get_log(name):
return data, name
def import_xml(stream):
try:
xml = ET.fromstring(stream.decode())
except ET.ParseError:
return {'ok': False, 'status': 'error'}
print (xml)
return {'ok': True, 'status': 'server'}

View File

@ -66,8 +66,18 @@ def desconectar():
def upload_file(rfc, opt, file_obj):
if opt == 'cfdixml':
sxml = file_obj.file.read().decode()
xml = util.parse_xml(sxml)
# ~ sxml = util.to_pretty_xml(data)
if xml is None:
return {'status': 'error'}
else:
return Facturas.import_cfdi(xml, sxml)
result = util.upload_file(rfc, opt, file_obj)
if result['ok']:
names = ('bdfl', 'employees', 'nomina', 'products', 'invoiceods')
if not opt in names:
Configuracion.add({opt: file_obj.filename})
@ -4029,6 +4039,121 @@ class Facturas(BaseModel):
return result
def _validate_import(self, data, sxml):
try:
emisor = Emisor.select()[0]
except IndexError:
msg = 'Falta Emisor'
log.error(msg)
return False, {}
if not DEBUG:
if emisor.rfc != data['emisor']['rfc']:
msg = 'El CFDI no es del Emisor'
log.error(msg)
return False, {}
invoice = data['invoice']
if invoice['version'] != '3.3':
msg = 'CFDI no es 3.3'
log.error(msg)
return False, {}
w = (Facturas.uuid==invoice['uuid'])
if Facturas.select().where(w).exists():
msg = 'Factura ya existe: {}'.format(invoice['uuid'])
log.error(msg)
return False, {}
receptor = data['receptor']
name = receptor.get('nombre', '')
tipo_persona = 1
if receptor['rfc'] == 'XEXX010101000':
tipo_persona = 4
elif receptor['rfc'] == 'XAXX010101000':
tipo_persona = 3
elif len(receptor['rfc']) == 12:
tipo_persona = 2
new = {
'tipo_persona': tipo_persona,
'rfc': receptor['rfc'],
'slug': util.to_slug(name),
'nombre': name,
'es_cliente': True,
}
cliente, _ = Socios.get_or_create(**new)
tipo_cambio = float(invoice.get('TipoCambio', '1.0'))
total = float(invoice['Total'])
invoice = {
'cliente': cliente,
'version': invoice['version'],
'serie': invoice.get('serie', ''),
'folio': int(invoice.get('folio', '0')),
'fecha': invoice['fecha'],
'fecha_timbrado': invoice['FechaTimbrado'],
'forma_pago': invoice['FormaPago'],
'condiciones_pago': invoice.get('CondicionesDePago', ''),
'subtotal': float(invoice['SubTotal']),
'descuento': float(invoice.get('Descuento', '0.0')),
'moneda': invoice['Moneda'],
'tipo_cambio': tipo_cambio,
'total': total,
'total_mn': round(float(total * tipo_cambio), DECIMALES),
'tipo_comprobante': invoice['TipoDeComprobante'],
'metodo_pago': invoice['MetodoPago'],
'lugar_expedicion': invoice['LugarExpedicion'],
'uso_cfdi': invoice['UsoCFDI'],
'total_retenciones': float(invoice.get('TotalImpuestosRetenidos', '0.0')),
'total_trasladados': float(invoice.get('TotalImpuestosTrasladados', '0.0')),
'xml': sxml,
'uuid': invoice['uuid'],
'estatus': 'Importada',
'regimen_fiscal': invoice['RegimenFiscal'],
'pagada': True,
}
# ~ donativo = BooleanField(default=False)
# ~ tipo_relacion = TextField(default='')
conceptos = []
for concepto in data['conceptos']:
valor_unitario = float(concepto['ValorUnitario'])
descuento = float(concepto.get('Descuento', '0.0'))
c = {
'cantidad': float(concepto['Cantidad']),
'valor_unitario': valor_unitario,
'descuento': descuento,
'precio_final': round(valor_unitario - descuento, DECIMALES),
'importe': float(concepto['Importe']),
'descripcion': concepto['Descripcion'],
'unidad': concepto.get('Unidad', ''),
'clave': concepto.get('NoIdentificacion', ''),
'clave_sat': concepto['ClaveProdServ'],
}
conceptos.append(c)
new = {
'invoice': invoice,
'conceptos': conceptos,
}
return True, new
@classmethod
def import_cfdi(cls, xml, sxml):
result = {'status': 'error'}
data = util.ImportCFDI(xml).get_data()
ok, new = cls._validate_import(cls, data, sxml)
if not ok:
return result
with database_proxy.atomic() as txn:
obj = Facturas.create(**new['invoice'])
for product in new['conceptos']:
product['factura'] = obj
FacturasDetalle.create(**product)
return {'status': 'server'}
class PreFacturas(BaseModel):
cliente = ForeignKeyField(Socios)