forked from elmau/empresa-libre
Importar XML
This commit is contained in:
parent
d622975a89
commit
7185517a29
|
@ -20,8 +20,8 @@ import zipfile
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
# ~ from smtplib import SMTPException, SMTPAuthenticationError
|
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
|
from xml.dom.minidom import parseString
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import uno
|
import uno
|
||||||
|
@ -1360,7 +1360,15 @@ def import_nomina(rfc):
|
||||||
|
|
||||||
|
|
||||||
def parse_xml(xml):
|
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):
|
def get_dict(data):
|
||||||
|
@ -1445,8 +1453,9 @@ def _comprobante(doc, options):
|
||||||
pass
|
pass
|
||||||
data['fechaformato'] = fecha.strftime('%A, %d de %B de %Y')
|
data['fechaformato'] = fecha.strftime('%A, %d de %B de %Y')
|
||||||
|
|
||||||
data['tipocambio'] = 'Tipo de Cambio: $ {:0.2f}'.format(
|
if 'tipocambio' in data:
|
||||||
float(data['tipocambio']))
|
data['tipocambio'] = 'Tipo de Cambio: $ {:0.2f}'.format(
|
||||||
|
float(data['tipocambio']))
|
||||||
data['notas'] = options['notas']
|
data['notas'] = options['notas']
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
@ -1503,10 +1512,17 @@ def _conceptos(doc, version, options):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if version == '3.3':
|
if version == '3.3':
|
||||||
values['noidentificacion'] = '{}\n(SAT {})'.format(
|
if 'noidentificacion' in values:
|
||||||
values['noidentificacion'], values['ClaveProdServ'])
|
values['noidentificacion'] = '{}\n(SAT {})'.format(
|
||||||
values['unidad'] = '({})\n{}'.format(
|
values['noidentificacion'], values['ClaveProdServ'])
|
||||||
values['ClaveUnidad'], values['unidad'])
|
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]))
|
n = c.find('{}CuentaPredial'.format(PRE[version]))
|
||||||
if n is not None:
|
if n is not None:
|
||||||
|
@ -1559,8 +1575,8 @@ def _totales(doc, cfdi, version):
|
||||||
for n in node.getchildren():
|
for n in node.getchildren():
|
||||||
tmp = CaseInsensitiveDict(n.attrib.copy())
|
tmp = CaseInsensitiveDict(n.attrib.copy())
|
||||||
if version == '3.3':
|
if version == '3.3':
|
||||||
title = 'Traslado {} {}'.format(
|
tasa = round(float(tmp['tasaocuota']), DECIMALES)
|
||||||
tn.get(tmp['impuesto']), tmp['tasaocuota'])
|
title = 'Traslado {} {}'.format(tn.get(tmp['impuesto']), tasa)
|
||||||
else:
|
else:
|
||||||
title = 'Traslado {} {}'.format(tmp['impuesto'], tmp['tasa'])
|
title = 'Traslado {} {}'.format(tmp['impuesto'], tmp['tasa'])
|
||||||
traslados.append((title, float(tmp['importe'])))
|
traslados.append((title, float(tmp['importe'])))
|
||||||
|
@ -1691,7 +1707,7 @@ def _nomina(doc, data, values, version_cfdi):
|
||||||
|
|
||||||
def get_data_from_xml(invoice, values):
|
def get_data_from_xml(invoice, values):
|
||||||
data = {'cancelada': invoice.cancelada, 'donativo': False}
|
data = {'cancelada': invoice.cancelada, 'donativo': False}
|
||||||
if hasattr('invoice', 'donativo'):
|
if hasattr(invoice, 'donativo'):
|
||||||
data['donativo'] = invoice.donativo
|
data['donativo'] = invoice.donativo
|
||||||
doc = parse_xml(invoice.xml)
|
doc = parse_xml(invoice.xml)
|
||||||
data['comprobante'] = _comprobante(doc, values)
|
data['comprobante'] = _comprobante(doc, values)
|
||||||
|
@ -1883,14 +1899,6 @@ def upload_file(rfc, opt, file_obj):
|
||||||
|
|
||||||
name = '{}_nomina.ods'.format(rfc.lower())
|
name = '{}_nomina.ods'.format(rfc.lower())
|
||||||
path = _join(PATH_MEDIA, 'tmp', name)
|
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()):
|
if save_file(path, file_obj.file.read()):
|
||||||
return {'status': 'server', 'name': file_obj.filename, 'ok': True}
|
return {'status': 'server', 'name': file_obj.filename, 'ok': True}
|
||||||
|
@ -3113,6 +3121,79 @@ class ImportFacturaLibre(object):
|
||||||
return data
|
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):
|
def print_ticket(data, info):
|
||||||
p = PrintTicket(info)
|
p = PrintTicket(info)
|
||||||
return p.printer(data)
|
return p.printer(data)
|
||||||
|
@ -3176,12 +3257,3 @@ def get_log(name):
|
||||||
return data, 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'}
|
|
||||||
|
|
||||||
|
|
|
@ -66,8 +66,18 @@ def desconectar():
|
||||||
|
|
||||||
|
|
||||||
def upload_file(rfc, opt, file_obj):
|
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)
|
result = util.upload_file(rfc, opt, file_obj)
|
||||||
if result['ok']:
|
if result['ok']:
|
||||||
|
|
||||||
names = ('bdfl', 'employees', 'nomina', 'products', 'invoiceods')
|
names = ('bdfl', 'employees', 'nomina', 'products', 'invoiceods')
|
||||||
if not opt in names:
|
if not opt in names:
|
||||||
Configuracion.add({opt: file_obj.filename})
|
Configuracion.add({opt: file_obj.filename})
|
||||||
|
@ -4029,6 +4039,121 @@ class Facturas(BaseModel):
|
||||||
|
|
||||||
return result
|
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):
|
class PreFacturas(BaseModel):
|
||||||
cliente = ForeignKeyField(Socios)
|
cliente = ForeignKeyField(Socios)
|
||||||
|
|
Loading…
Reference in New Issue