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 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'}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue