Importar nómina

This commit is contained in:
Mauricio Baeza 2018-01-28 03:12:35 -06:00
parent 776c90a467
commit 6a66b15f56
3 changed files with 431 additions and 29 deletions

View File

@ -1054,26 +1054,126 @@ class LIBO(object):
msg = 'Empleados importados correctamente'
return rows, msg
def _get_nomina(self, doc):
rows, msg = self._get_data(doc, 'Nomina')
if len(rows) == 2:
msg = 'Sin datos para importar'
return {}, msg
fields = (
'rfc',
'tipo_nomina',
'fecha_pago',
'fecha_inicial_pago',
'fecha_final_pago',
)
data = tuple([dict(zip(fields, r[1:])) for r in rows[2:]])
return data, ''
def _get_percepciones(self, doc, count):
rows, msg = self._get_data(doc, 'Percepciones')
if len(rows) == 2:
msg = 'Sin Percepciones'
return {}, msg
if len(rows[0][2:]) % 2:
msg = 'Las Percepciones deben ir en pares: Gravado y Exento'
return {}, msg
data = tuple([r[2:] for r in rows[:count+2]])
return data, ''
def _get_deducciones(self, doc, count):
rows, msg = self._get_data(doc, 'Deducciones')
if len(rows) == 2:
msg = 'Sin Deducciones'
return {}, msg
data = tuple([r[2:] for r in rows[:count+2]])
return data, ''
def _get_otros_pagos(self, doc, count):
rows, msg = self._get_data(doc, 'OtrosPagos')
if len(rows) == 2:
msg = 'Sin Otros Pagos'
return {}, msg
data = tuple([r[2:] for r in rows[:count+2]])
return data, ''
def _get_horas_extras(self, doc, count):
rows, msg = self._get_data(doc, 'HorasExtras')
if len(rows) == 2:
msg = 'Sin Horas Extras'
return {}, msg
if len(rows[1][1:]) % 4:
msg = 'Las Horas Extras deben ir grupos de 4 columnas'
return {}, msg
data = tuple([r[1:] for r in rows[:count+2]])
return data, ''
def _get_incapacidades(self, doc, count):
rows, msg = self._get_data(doc, 'Incapacidades')
if len(rows) == 2:
msg = 'Sin Incapacidades'
return {}, msg
if len(rows[1][1:]) % 3:
msg = 'Las Incapacidades deben ir grupos de 3 columnas'
return {}, msg
data = tuple([r[1:] for r in rows[:count+2]])
return data, ''
def nomina(self, path):
options = {'AsTemplate': True, 'Hidden': True}
doc = self._doc_open(path, options)
if doc is None:
return ()
msg = 'No se pudo abrir la plantilla'
return {}, msg
data = {}
nomina, msg = self._get_nomina(doc)
if msg:
doc.close(True)
return {}, msg
percepciones, msg = self._get_percepciones(doc, len(nomina))
if msg:
doc.close(True)
return {}, msg
deducciones, msg = self._get_deducciones(doc, len(nomina))
if msg:
doc.close(True)
return {}, msg
otros_pagos, msg = self._get_otros_pagos(doc, len(nomina))
if msg:
doc.close(True)
return {}, msg
horas_extras, msg = self._get_horas_extras(doc, len(nomina))
if msg:
doc.close(True)
return {}, msg
incapacidades, msg = self._get_incapacidades(doc, len(nomina))
if msg:
doc.close(True)
return {}, msg
data, msg = self._get_data(doc, 'Nomina')
doc.close(True)
data['nomina'] = nomina
data['percepciones'] = percepciones
data['deducciones'] = deducciones
data['otros_pagos'] = otros_pagos
data['horas_extras'] = horas_extras
data['incapacidades'] = incapacidades
if len(data) == 1:
msg = 'Sin datos para importar'
return (), msg
fields = (
'num_empleado',
'rfc',
)
rows = tuple([dict(zip(fields, r)) for r in data[1:]])
msg = 'Nomina importada correctamente'
return rows, msg
return data, ''
def invoice(self, path):
options = {'AsTemplate': True, 'Hidden': True}
@ -2846,3 +2946,20 @@ def import_invoice(rfc):
def calc_to_date(value):
return datetime.date.fromordinal(int(value) + 693594)
def get_days(start, end):
return (end - start).days + 1
def log_file(name, msg='', kill=False):
path = _join(PATH_MEDIA, 'tmp', '{}.log'.format(name))
if kill:
_kill(path)
return
with open(path, 'a') as fh:
line = '{} : {}'.format(str(now()), msg)
fh.write(line)
return

View File

@ -5421,6 +5421,8 @@ class Empleados(BaseModel):
en = 0
ea = 0
for row in rows:
# ~ if row['rfc'] == 'BASM740115RW0':
# ~ continue
data = self._validate_import(self, row)
w = (Empleados.rfc==row['rfc'])
with database_proxy.transaction():
@ -5471,7 +5473,7 @@ class Empleados(BaseModel):
class CfdiNomina(BaseModel):
empleado = ForeignKeyField(Empleados)
version = TextField(default=CURRENT_CFDI)
serie = TextField(default='')
serie = TextField(default='N')
folio = IntegerField(default=0)
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
fecha_timbrado = DateTimeField(null=True)
@ -5488,7 +5490,7 @@ class CfdiNomina(BaseModel):
auto_round=True)
total_mn = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
tipo_comprobante = TextField(default='I')
tipo_comprobante = TextField(default='N')
metodo_pago = TextField(default='PUE')
lugar_expedicion = TextField(default='')
confirmacion = TextField(default='')
@ -5511,7 +5513,7 @@ class CfdiNomina(BaseModel):
acuse = TextField(default='')
tipo_relacion = TextField(default='')
error = TextField(default='')
version = TextField(default=CURRENT_CFDI_NOMINA)
version_nomina = TextField(default=CURRENT_CFDI_NOMINA)
registro_patronal = TextField(default='')
rfc_patron_origen = TextField(default='')
tipo_nomina = ForeignKeyField(SATTipoNomina)
@ -5527,27 +5529,304 @@ class CfdiNomina(BaseModel):
class Meta:
order_by = ('fecha',)
def _validate_import(self, row):
def _get_serie(self):
serie = Configuracion.get_('chk_config_serie_nomina')
if not serie:
serie = DEFAULT_SAT_NOMINA['SERIE']
return serie
def _get_folio(self, serie):
folio = int(Configuracion.get_('chk_config_folio_nomina') or '0')
inicio = (CfdiNomina
.select(fn.Max(CfdiNomina.folio).alias('mf'))
.where(CfdiNomina.serie==serie)
.order_by(SQL('mf'))
.scalar())
if inicio is None:
new = 1
else:
new += 1
if folio > new:
new = folio
return new
def _validate_nomina(self, row):
sn = {'si': True, 'no': False}
data = row.copy()
return data
rfc = data.pop('rfc')
try:
data['empleado'] = Empleados.get(Empleados.rfc==rfc)
except Empleados.DoesNotExist:
msg = 'No existe el Empleado con RFC: {}'.format(rfc)
return {}, msg
tipo_nomina = SATTipoNomina.get_by_key(row['tipo_nomina'])
if tipo_nomina is None:
msg = 'RFC: {}, Tipo de Nómina no existe: {}'.format(row['tipo_nomina'])
return {}, msg
data['serie'] = self._get_serie(self)
data['folio'] = self._get_folio(self, data['serie'])
data['forma_pago'] = DEFAULT_SAT_NOMINA['FORMA_PAGO']
data['uso_cfdi'] = DEFAULT_SAT_NOMINA['USO_CFDI']
data['tipo_nomina'] = tipo_nomina
data['fecha_pago'] = util.calc_to_date(row['fecha_pago'])
data['fecha_inicial_pago'] = util.calc_to_date(row['fecha_inicial_pago'])
data['fecha_final_pago'] = util.calc_to_date(row['fecha_final_pago'])
data['dias_pagados'] = util.get_days(data['fecha_inicial_pago'], data['fecha_final_pago'])
return data, ''
def _validate_percepciones(self, headers, row):
total_gravado = 0.0
total_exento = 0.0
total_jubilacion = 0.0
total_separacion = 0.0
data = []
for i, key in enumerate(headers[::2]):
gravado = round(row[i * 2], DECIMALES)
exento = round(row[i * 2 + 1], DECIMALES)
if not gravado and not exento:
continue
tp = SATTipoPercepcion.get_by_key(key)
if tp is None:
continue
total_gravado += gravado
total_exento += exento
if key in ('039', '044'):
total_jubilacion += gravado + exento
elif key in ('022', '023', '025'):
total_separacion += gravado + exento
new = {
'tipo_percepcion': tp,
'importe_gravado': gravado,
'importe_exento': exento,
}
data.append(new)
total_sueldos = round(total_gravado + total_exento, DECIMALES)
totals = {
'total_gravado': total_gravado,
'total_exento': total_exento,
'total_jubilacion': total_jubilacion,
'total_separacion': total_separacion,
'total_sueldos': total_sueldos,
'total_percepciones': round(
total_sueldos + total_jubilacion + total_separacion, DECIMALES)
}
return data, totals, ''
def _validate_deducciones(self, headers, row):
total_retenciones = 0.0
total_otras_deducciones = 0.0
data = []
for i, value in enumerate(row):
key = headers[0][i]
importe = round(value, DECIMALES)
if not importe:
continue
td = SATTipoDeduccion.get_by_key(key)
if td is None:
continue
if key == '002':
total_retenciones += importe
else:
total_otras_deducciones += importe
new = {
'tipo_deduccion': td,
'importe': importe,
}
data.append(new)
totals = {
'total_retenciones': total_retenciones,
'total_otras_deducciones': total_otras_deducciones,
'total_deducciones': round(
total_retenciones + total_otras_deducciones, DECIMALES)
}
return data, totals, ''
def _validate_otros_pagos(self, headers, row):
total_otros_pagos = 0.0
data = []
subsidio_causado = round(row[0], DECIMALES)
for i, value in enumerate(row):
if not i:
continue
key = headers[0][i]
importe = round(value, DECIMALES)
if not importe:
continue
td = SATTipoOtroPago.get_by_key(key)
if td is None:
continue
total_otros_pagos += importe
new = {
'tipo_otro_pago': td,
'importe': importe,
}
if key == '002':
new['subsidio_causado'] = subsidio_causado
data.append(new)
totals = {'total_otros_pagos': total_otros_pagos}
return data, totals, ''
def _validate_horas_extras(self, row):
data = []
for i, key in enumerate(row[::4]):
days = int(row[i * 4])
key = row[i * 4 + 1]
the = SATTipoHoras.get_by_key(key)
if the is None:
continue
hours = int(row[i * 4 + 2])
importe = round(row[i * 4 + 3], DECIMALES)
if not hours or not importe:
continue
new = {
'dias': days,
'tipos_horas': the,
'horas_extra': hours,
'importe_pagado': importe,
}
data.append(new)
return data, ''
def _validate_incapacidades(self, row):
data = []
for i, key in enumerate(row[::3]):
key = row[i * 3]
ti = SATTipoIncapacidad.get_by_key(key)
if ti is None:
continue
days = int(row[i * 4 + 1])
importe = round(row[i * 4 + 2], DECIMALES)
if not days or not importe:
continue
new = {
'dias': ti,
'tipo': days,
'importe': importe,
}
data.append(new)
return data, ''
def _import(self):
util.log_file('nomina', kill=True)
emisor = Emisor.select()[0]
rows, msg = util.import_nomina(emisor.rfc)
if not rows:
data, msg = util.import_nomina(emisor.rfc)
if not data:
return {'ok': False, 'msg': msg}
for row in rows:
data = self._validate_import(self, row)
# ~ w = (Nomina.empleado.rfc==row['rfc'])
hp = data['percepciones'][0]
percepciones = data['percepciones'][2:]
hd = data['deducciones'][:1]
deducciones = data['deducciones'][2:]
ho = data['otros_pagos'][:1]
otros_pagos = data['otros_pagos'][2:]
horas_extras = data['horas_extras'][2:]
incapacidades = data['incapacidades'][2:]
for i, row in enumerate(data['nomina']):
row['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
row['regimen_fiscal'] = emisor.regimenes[0].key
row['registro_patronal'] = emisor.registro_patronal
new_nomina, msg = self._validate_nomina(self, row)
if msg:
util.log_file('nomina', msg)
continue
new_percepciones, total_percepciones, msg = \
self._validate_percepciones(self, hp, percepciones[i])
if msg:
util.log_file('nomina', msg)
continue
new_deducciones, total_deducciones, msg = \
self._validate_deducciones(self, hd, deducciones[i])
if msg:
util.log_file('nomina', msg)
continue
new_otros_pagos, total_otros_pagos, msg = \
self._validate_otros_pagos(self, ho, otros_pagos[i])
if msg:
util.log_file('nomina', msg)
continue
new_horas_extras, msg = self._validate_horas_extras(self, horas_extras[i])
if msg:
util.log_file('nomina', msg)
continue
new_incapacidades, msg = self._validate_incapacidades(self, incapacidades[i])
if msg:
util.log_file('nomina', msg)
continue
totals = total_percepciones.copy()
totals.update(total_deducciones)
totals.update(total_otros_pagos)
totals['subtotal'] = round(totals['total_percepciones'] +
totals['total_otros_pagos'], DECIMALES)
totals['descuento'] = totals['total_deducciones']
totals['total'] = round(totals['subtotal'] -
totals['descuento'], DECIMALES)
with database_proxy.transaction():
pass
# ~ if Empleados.select().where(w).exists():
# ~ q = Empleados.update(**data).where(w)
# ~ q.execute()
# ~ else:
# ~ obj = Empleados.create(**data)
obj = CfdiNomina.create(**new_nomina)
for row in new_percepciones:
row['cfdi'] = obj
CfdiNominaPercepciones.create(**row)
for row in new_deducciones:
row['cfdi'] = obj
CfdiNominaDeducciones.create(**row)
for row in new_otros_pagos:
row['cfdi'] = obj
CfdiNominaOtroPago.create(**row)
for row in new_horas_extras:
row['cfdi'] = obj
CfdiNominaHorasExtra.create(**row)
for row in new_incapacidades:
row['cfdi'] = obj
CfdiNominaIncapacidad.create(**row)
concepto = {
'cfdi': obj,
'valor_unitario': totals['subtotal'],
'importe': totals['subtotal'],
'descuento': totals['total_deducciones'],
}
CfdiNominaDetalle.create(**concepto)
totals['cfdi'] = obj
CfdiNominaTotales.create(**totals)
msg = 'Nómina importada correctamente'
return {'ok': True, 'msg': msg}
def _get(self):
@ -5653,6 +5932,7 @@ class CfdiNominaSeparacion(BaseModel):
class CfdiNominaPercepciones(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
tipo_percepcion = ForeignKeyField(SATTipoPercepcion)
concepto = TextField(default='')
importe_gravado = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
importe_exento = DecimalField(default=0.0, max_digits=20, decimal_places=6,
@ -5666,6 +5946,7 @@ class CfdiNominaPercepciones(BaseModel):
class CfdiNominaDeducciones(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
tipo_deduccion = ForeignKeyField(SATTipoDeduccion)
concepto = TextField(default='')
importe = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
@ -5673,6 +5954,7 @@ class CfdiNominaDeducciones(BaseModel):
class CfdiNominaOtroPago(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
tipo_otro_pago = ForeignKeyField(SATTipoOtroPago)
concepto = TextField(default='')
importe = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
subsidio_causado = DecimalField(default=0.0, max_digits=20, decimal_places=6,

View File

@ -137,6 +137,9 @@ USAR_TOKEN = False
CANCEL_SIGNATURE = False
PUBLIC = 'Público en general'
DEFAULT_SAT_NOMINA = {
'SERIE': 'N',
'FORMA_PAGO': '99',
'USO_CFDI': 'P01',
'CLAVE': '84111505',
'UNIDAD': 'ACT',
'DESCRIPCION': 'Pago de nómina',