Timbrar y cancelar nómina

This commit is contained in:
Mauricio Baeza 2018-01-28 21:35:10 -06:00
parent 6a66b15f56
commit 1cfea6978b
7 changed files with 700 additions and 74 deletions

View File

@ -33,9 +33,9 @@ SAT = {
'xmlns': 'http://www.sat.gob.mx/nomina',
'schema': 'http://www.sat.gob.mx/nomina http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina11.xsd',
},
'nomina12': {
'nomina': {
'version': '1.2',
'prefix': 'nomina',
'prefix': 'nomina12',
'xmlns': 'http://www.sat.gob.mx/nomina12',
'schema': 'http://www.sat.gob.mx/nomina12 http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina12.xsd',
},
@ -72,6 +72,7 @@ class CFDI(object):
self._impuestos_locales = False
self._donativo = False
self._ine = False
self._is_nomina = False
self.error = ''
def _now(self):
@ -93,8 +94,6 @@ class CFDI(object):
if 'nomina' in datos:
self._nomina(datos['nomina'])
#~ if 'complementos' in datos:
#~ self._complementos(datos['complementos'])
return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8'))
@ -108,35 +107,25 @@ class CFDI(object):
return xml
def _validate(self, datos):
if datos['impuestos']['total_locales_trasladados'] or \
datos['impuestos']['total_locales_retenciones']:
self._impuestos_locales = True
if datos['impuestos']:
if datos['impuestos']['total_locales_trasladados'] or \
datos['impuestos']['total_locales_retenciones']:
self._impuestos_locales = True
if datos['donativo']:
self._donativo = True
if 'ine' in datos['complementos']:
self._ine = True
if datos['complementos']:
if 'ine' in datos['complementos']:
self._ine = True
if 'nomina' in datos:
self._is_nomina = True
return self._validate_nomina(datos)
return True
def _validate_nomina(self, datos):
comprobante = datos['comprobante']
validators = (
('MetodoDePago', 'NA'),
('TipoCambio', '1'),
('Moneda', 'MXN'),
('TipoDeComprobante', 'egreso'),
)
for f, v in validators:
if f in comprobante:
if v != comprobante[f]:
msg = 'El atributo: {}, debe ser: {}'.format(f, v)
self.error = msg
return False
return True
def _comprobante(self, datos):
@ -160,10 +149,16 @@ class CFDI(object):
if self._ine:
name = 'xmlns:{}'.format(SAT['ine']['prefix'])
attributes[name] = SAT['ine']['xmlns']
schema_donativo = SAT['ine']['schema']
schema_ine = SAT['ine']['schema']
schema_nomina = ''
if self._nomina:
name = 'xmlns:{}'.format(SAT['nomina']['prefix'])
attributes[name] = SAT['nomina']['xmlns']
schema_nomina = SAT['nomina']['schema']
attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \
schema_locales + schema_donativo +schema_ine
schema_locales + schema_donativo + schema_ine + schema_nomina
attributes.update(datos)
if not 'Version' in attributes:
@ -263,6 +258,9 @@ class CFDI(object):
return
def _impuestos(self, datos):
if self._is_nomina:
return
if not datos:
node_name = '{}:Impuestos'.format(self._pre)
ET.SubElement(self._cfdi, node_name)
@ -288,38 +286,62 @@ class CFDI(object):
return
def _nomina(self, datos):
sat_nomina = SAT[NOMINA_ACTUAL]
pre = sat_nomina['prefix']
complemento = ET.SubElement(self._cfdi, '{}:Complemento'.format(self._pre))
pre = SAT['nomina']['prefix']
emisor = datos.pop('Emisor', None)
receptor = datos.pop('Receptor', None)
percepciones = datos.pop('Percepciones', None)
deducciones = datos.pop('Deducciones', None)
if self._complemento is None:
self._complemento = ET.SubElement(
self._cfdi, '{}:Complemento'.format(self._pre))
attributes = {}
attributes['xmlns:{}'.format(pre)] = sat_nomina['xmlns']
attributes['xsi:schemaLocation'] = sat_nomina['schema']
attributes.update(datos)
emisor = datos.pop('emisor', None)
receptor = datos.pop('receptor', None)
percepciones = datos.pop('percepciones', None)
deducciones = datos.pop('deducciones', None)
otros_pagos = datos.pop('otros_pagos', ())
incapacidades = datos.pop('incapacidades', ())
if not 'Version' in attributes:
attributes['Version'] = sat_nomina['version']
nomina = ET.SubElement(
self._complemento, '{}:Nomina'.format(pre), datos['nomina'])
nomina = ET.SubElement(complemento, '{}:Nomina'.format(pre), attributes)
if emisor:
ET.SubElement(nomina, '{}:Emisor'.format(pre), emisor)
if receptor:
ET.SubElement(nomina, '{}:Receptor'.format(pre), receptor)
node = ET.SubElement(nomina, '{}:Receptor'.format(pre), receptor)
if percepciones:
detalle = percepciones.pop('detalle', None)
percepciones = ET.SubElement(nomina, '{}:Percepciones'.format(pre), percepciones)
for row in detalle:
ET.SubElement(percepciones, '{}:Percepcion'.format(pre), row)
details = percepciones.pop('details', None)
hours_extra = percepciones.pop('hours_extra', None)
separacion = percepciones.pop('separacion', None)
if details:
node = ET.SubElement(nomina, '{}:Percepciones'.format(pre), percepciones)
for row in details:
nodep = ET.SubElement(node, '{}:Percepcion'.format(pre), row)
if row['TipoPercepcion'] == '019' and hours_extra:
for he in hours_extra:
ET.SubElement(nodep, '{}:HorasExtra'.format(pre), he)
hours_extra = None
if separacion:
ET.SubElement(node, '{}:SeparacionIndemnizacion'.format(pre), separacion)
if deducciones:
detalle = deducciones.pop('detalle', None)
deducciones = ET.SubElement(nomina, '{}:Deducciones'.format(pre), deducciones)
for row in detalle:
ET.SubElement(deducciones, '{}:Deduccion'.format(pre), row)
details = deducciones.pop('details', None)
if details:
deducciones = ET.SubElement(nomina, '{}:Deducciones'.format(pre), deducciones)
for row in details:
ET.SubElement(deducciones, '{}:Deduccion'.format(pre), row)
if otros_pagos:
node = ET.SubElement(nomina, '{}:OtrosPagos'.format(pre))
for row in otros_pagos:
subsidio = row.pop('subsidio', None)
subnode = ET.SubElement(node, '{}:OtroPago'.format(pre), row)
if subsidio:
ET.SubElement(subnode, '{}:SubsidioAlEmpleo'.format(pre), subsidio)
if incapacidades:
node = ET.SubElement(nomina, '{}:Incapacidades'.format(pre))
for row in incapacidades:
ET.SubElement(node, '{}:Incapacidad'.format(pre), row)
return
def _locales(self, datos):

View File

@ -441,7 +441,7 @@ class AppNomina(object):
def on_delete(self, req, resp):
values = req.params
if self._db.delete('nomina', values):
if self._db.delete('nomina', values['id']):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_204

View File

@ -174,8 +174,8 @@ class Finkok(object):
return
def _check_result(self, method, result):
#~ print ('CODE', result.CodEstatus)
#~ print ('INCIDENCIAS', result.Incidencias)
# ~ print ('CODE', result.CodEstatus)
# ~ print ('INCIDENCIAS', result.Incidencias)
self.message = ''
MSG = {
'OK': 'Comprobante timbrado satisfactoriamente',
@ -184,8 +184,8 @@ class Finkok(object):
status = result.CodEstatus
if status is None and result.Incidencias:
for i in result.Incidencias['Incidencia']:
self.error += 'Error: {}\n{}'.format(
i['CodigoError'], i['MensajeIncidencia'])
self.error += 'Error: {}\n{}\n{}'.format(
i['CodigoError'], i['MensajeIncidencia'], i['ExtraInfo'])
return ''
if method == 'timbra' and status in (MSG['OK'], MSG['307']):

View File

@ -17,6 +17,11 @@ class StorageEngine(object):
def get_nomina(self, values):
return main.CfdiNomina.get_by(values)
def nomina(self, values):
opt = values.pop('opt')
if opt == 'cancel':
return main.CfdiNomina.cancel(int(values['id']))
def empresa_agregar(self, values):
return main.empresa_agregar(values['alta_rfc'], False)
@ -104,6 +109,9 @@ class StorageEngine(object):
def _get_filteryearsticket(self, values):
return main.Tickets.filter_years()
def _get_filteryearsnomina(self, values):
return main.CfdiNomina.filter_years()
def _get_cuentayears(self, values):
return main.CuentasBanco.get_years()
@ -252,6 +260,8 @@ class StorageEngine(object):
return main.Configuracion.remove(id)
if table == 'employee':
return main.Empleados.remove(id)
if table == 'nomina':
return main.CfdiNomina.remove(id)
return False
def _get_client(self, values):

View File

@ -22,6 +22,7 @@ from settings import log, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, \
FORMAT = '{0:.2f}'
FORMAT3 = '{0:.3f}'
FORMAT_TAX = '{0:.4f}'
@ -184,6 +185,8 @@ def get_doc(type_doc, id, rfc):
data, file_name = PreFacturas.get_pdf(id)
elif type_doc == 'tpdf':
data, file_name = Tickets.get_pdf(id)
elif type_doc == 'xmlnom':
data, file_name = CfdiNomina.get_xml(id)
return data, file_name, content_type
@ -654,7 +657,6 @@ class SATRegimenes(BaseModel):
class Emisor(BaseModel):
rfc = TextField(unique=True)
curp = TextField(unique=True)
nombre = TextField(default='')
nombre_comercial = TextField(default='')
calle = TextField(default='')
@ -3625,8 +3627,8 @@ class Facturas(BaseModel):
comprobante['Serie'] = invoice.serie
if invoice.condiciones_pago:
comprobante['CondicionesDePago'] = invoice.condiciones_pago
if invoice.descuento:
comprobante['Descuento'] = invoice.descuento
# ~ if invoice.descuento:
# ~ comprobante['Descuento'] = invoice.descuento
comprobante['Folio'] = str(invoice.folio)
comprobante['Fecha'] = invoice.fecha.isoformat()[:19]
@ -5367,7 +5369,7 @@ class Empleados(BaseModel):
es_activo = BooleanField(default=True)
es_extranjero = BooleanField(default=False)
fecha_alta = DateField(default=util.now)
fecha_ingreso = DateField(default=util.now)
fecha_ingreso = DateField(null=True)
imss = TextField(default='')
tipo_contrato = ForeignKeyField(SATTipoContrato)
es_sindicalizado = BooleanField(default=False)
@ -5399,7 +5401,8 @@ class Empleados(BaseModel):
data = row.copy()
data['nombre_completo'] = '{} {} {}'.format(
row['nombre'], row['paterno'], row['materno']).strip()
data['fecha_ingreso'] = util.calc_to_date(row['fecha_ingreso'])
if row['fecha_ingreso']:
data['fecha_ingreso'] = util.calc_to_date(row['fecha_ingreso'])
data['tipo_contrato'] = SATTipoContrato.get_by_key(row['tipo_contrato'])
data['es_sindicalizado'] = sn.get(row['es_sindicalizado'].lower(), False)
data['tipo_jornada'] = SATTipoJornada.get_by_key(row['tipo_jornada'])
@ -5501,7 +5504,7 @@ class CfdiNomina(BaseModel):
max_digits=20, decimal_places=6, auto_round=True, null=True)
xml = TextField(default='')
uuid = UUIDField(null=True)
estatus = TextField(default='Guardada')
estatus = TextField(default='Guardado')
estatus_sat = TextField(default='Vigente')
regimen_fiscal = TextField(default='')
notas = TextField(default='')
@ -5546,7 +5549,7 @@ class CfdiNomina(BaseModel):
if inicio is None:
new = 1
else:
new += 1
new = inicio + 1
if folio > new:
new = folio
@ -5797,6 +5800,11 @@ class CfdiNomina(BaseModel):
totals['total'] = round(totals['subtotal'] -
totals['descuento'], DECIMALES)
new_nomina['subtotal'] = totals['subtotal']
new_nomina['descuento'] = totals['descuento']
new_nomina['total'] = totals['total']
new_nomina['total_mn'] = totals['total']
with database_proxy.transaction():
obj = CfdiNomina.create(**new_nomina)
for row in new_percepciones:
@ -5829,30 +5837,402 @@ class CfdiNomina(BaseModel):
msg = 'Nómina importada correctamente'
return {'ok': True, 'msg': msg}
def _get(self):
rows = (nomina
def _get(self, where=''):
if not where:
where = ((CfdiNomina.uuid.is_null(True)) & (CfdiNomina.cancelada==False))
rows = (CfdiNomina
.select(
Nomina.id,
Nomina.serie,
Nomina.folio,
Nomina.fecha,
Nomina.status,
Nomina.fecha_pago,
Nomina.total,
Nomina.empleado.nombre_completo
CfdiNomina.id,
CfdiNomina.serie,
CfdiNomina.folio,
CfdiNomina.fecha,
CfdiNomina.estatus,
CfdiNomina.fecha_pago,
CfdiNomina.total,
Empleados.nombre_completo.alias('empleado')
)
.where(where)
.join(Empleados)
.switch(CfdiNomina)
.dicts()
)
return {'ok': True, 'rows': tuple(rows)}
def _make_xml(self, cfdi, auth):
emisor = Emisor.select()[0]
empleado = cfdi.empleado
certificado = Certificado.select()[0]
totals = CfdiNominaTotales.select().where(CfdiNominaTotales.cfdi==cfdi)[0]
comprobante = {}
relacionados = {}
complementos = None
comprobante['Serie'] = cfdi.serie
comprobante['Folio'] = str(cfdi.folio)
comprobante['Fecha'] = cfdi.fecha.isoformat()[:19]
comprobante['FormaPago'] = cfdi.forma_pago
comprobante['NoCertificado'] = certificado.serie
comprobante['Certificado'] = certificado.cer_txt
comprobante['SubTotal'] = FORMAT.format(cfdi.subtotal)
comprobante['Moneda'] = cfdi.moneda
comprobante['Total'] = FORMAT.format(cfdi.total)
comprobante['TipoDeComprobante'] = cfdi.tipo_comprobante
comprobante['MetodoPago'] = cfdi.metodo_pago
comprobante['LugarExpedicion'] = cfdi.lugar_expedicion
if cfdi.descuento:
comprobante['Descuento'] = FORMAT.format(cfdi.descuento)
# ~ if invoice.tipo_relacion:
# ~ relacionados = {
# ~ 'tipo': invoice.tipo_relacion,
# ~ 'cfdis': FacturasRelacionadas.get_(invoice),
# ~ }
cfdi_emisor = {
'Rfc': emisor.rfc,
'Nombre': emisor.nombre,
'RegimenFiscal': cfdi.regimen_fiscal,
}
receptor = {
'Rfc': cfdi.empleado.rfc,
'Nombre': cfdi.empleado.nombre_completo,
'UsoCFDI': cfdi.uso_cfdi,
}
conceptos = []
rows = CfdiNominaDetalle.select().where(CfdiNominaDetalle.cfdi==cfdi)
for row in rows:
concepto = {
'ClaveProdServ': row.clave_sat,
'Cantidad': '1',
'ClaveUnidad': row.clave_unidad,
'Descripcion': row.descripcion,
'ValorUnitario': FORMAT.format(row.valor_unitario),
'Importe': FORMAT.format(row.importe),
}
if row.descuento:
concepto['Descuento'] = FORMAT.format(row.descuento)
conceptos.append(concepto)
nomina = {
'Version': cfdi.version_nomina,
'TipoNomina': cfdi.tipo_nomina.key,
'FechaPago': str(cfdi.fecha_pago),
'FechaInicialPago': str(cfdi.fecha_inicial_pago),
'FechaFinalPago': str(cfdi.fecha_final_pago),
'NumDiasPagados': FORMAT3.format(cfdi.dias_pagados),
}
if totals.total_percepciones:
nomina['TotalPercepciones'] = FORMAT.format(totals.total_percepciones)
if totals.total_deducciones:
nomina['TotalDeducciones'] = FORMAT.format(totals.total_deducciones)
if totals.total_otros_pagos:
nomina['TotalOtrosPagos'] = FORMAT.format(totals.total_otros_pagos)
nomina_emisor = {}
if emisor.curp:
nomina_emisor['Curp'] = emisor.curp
if emisor.registro_patronal:
nomina_emisor['RegistroPatronal'] = emisor.registro_patronal
nomina_receptor = {
'Curp': empleado.curp,
'TipoContrato': empleado.tipo_contrato.key,
'Sindicalizado': {True: 'Si', False: 'No'}.get(empleado.es_sindicalizado),
'TipoJornada': empleado.tipo_jornada.key,
'TipoRegimen': empleado.tipo_regimen.key,
'NumEmpleado': str(empleado.num_empleado),
'RiesgoPuesto': empleado.riesgo_puesto.key,
'PeriodicidadPago': empleado.periodicidad_pago.key,
'ClaveEntFed': empleado.estado.key,
}
if empleado.imss:
nomina_receptor['NumSeguridadSocial'] = empleado.imss.replace('-', '')
if empleado.fecha_ingreso:
nomina_receptor['FechaInicioRelLaboral'] = str(empleado.fecha_ingreso)
days = util.get_days(empleado.fecha_ingreso, cfdi.fecha_final_pago)
weeks = days // 7
if weeks:
ant = 'P{}W'.format(weeks)
else:
ant = 'P{}D'.format(days)
nomina_receptor['Antigüedad'] = ant
if empleado.puesto:
if empleado.puesto.departamento:
nomina_receptor['Departamento'] = empleado.puesto.departamento.nombre
nomina_receptor['Puesto'] = empleado.puesto.nombre
if empleado.clabe:
nomina_receptor['CuentaBancaria'] = empleado.clabe
elif empleado.cuenta_bancaria:
nomina_receptor['CuentaBancaria'] = empleado.cuenta_bancaria
nomina_receptor['Banco'] = empleado.banco.key
if empleado.salario_base:
nomina_receptor['SalarioBaseCotApor'] = FORMAT.format(empleado.salario_base)
if empleado.salario_diario:
nomina_receptor['SalarioDiarioIntegrado'] = FORMAT.format(empleado.salario_diario)
percepciones = {
'TotalSueldos': FORMAT.format(totals.total_sueldos),
'TotalGravado': FORMAT.format(totals.total_gravado),
'TotalExento': FORMAT.format(totals.total_exento),
}
if totals.total_separacion:
percepciones['TotalSeparacionIndemnizacion'] = FORMAT.format(totals.total_separacion)
if totals.total_jubilacion:
percepciones['TotalJubilacionPensionRetiro'] = FORMAT.format(totals.total_jubilacion)
rows = CfdiNominaPercepciones.select().where(
CfdiNominaPercepciones.cfdi==cfdi)
details = []
for row in rows:
concepto = row.concepto or row.tipo_percepcion.nombre or row.tipo_percepcion.name
p = {
'TipoPercepcion': row.tipo_percepcion.key,
'Clave': row.tipo_percepcion.clave or row.tipo_percepcion.key,
'Concepto': concepto[:100],
'ImporteGravado': FORMAT.format(row.importe_gravado),
'ImporteExento': FORMAT.format(row.importe_exento),
}
details.append(p)
percepciones['details'] = details
rows = CfdiNominaHorasExtra.select().where(CfdiNominaHorasExtra.cfdi==cfdi)
details = []
for row in rows:
n = {
'Dias': str(row.dias),
'TipoHoras': row.tipos_horas.key,
'HorasExtra': str(row.horas_extra),
'ImportePagado': FORMAT.format(row.importe_pagado),
}
details.append(n)
percepciones['hours_extra'] = details
deducciones = {
'TotalOtrasDeducciones': FORMAT.format(totals.total_otras_deducciones),
'TotalImpuestosRetenidos': FORMAT.format(totals.total_retenciones),
}
rows = CfdiNominaDeducciones.select().where(CfdiNominaDeducciones.cfdi==cfdi)
details = []
for row in rows:
concepto = row.concepto or row.tipo_deduccion.nombre or row.tipo_deduccion.name
p = {
'TipoDeduccion': row.tipo_deduccion.key,
'Clave': row.tipo_deduccion.clave or row.tipo_deduccion.key,
'Concepto': concepto[:100],
'Importe': FORMAT.format(row.importe),
}
details.append(p)
deducciones['details'] = details
rows = CfdiNominaOtroPago.select().where(CfdiNominaOtroPago.cfdi==cfdi)
otros_pagos = []
for row in rows:
concepto = row.concepto or row.tipo_otro_pago.nombre or row.tipo_otro_pago.name
p = {
'TipoOtroPago': row.tipo_otro_pago.key,
'Clave': row.tipo_otro_pago.clave or row.tipo_otro_pago.key,
'Concepto': concepto[:100],
'Importe': FORMAT.format(row.importe),
}
if row.tipo_otro_pago.key == '002' and row.subsidio_causado:
p['subsidio'] = {
'SubsidioCausado': FORMAT.format(row.subsidio_causado)
}
otros_pagos.append(p)
rows = CfdiNominaIncapacidad.select().where(CfdiNominaIncapacidad.cfdi==cfdi)
incapacidades = []
for row in rows:
n = {
'DiasIncapacidad': str(row.dias),
'TipoIncapacidad': row.tipo.key,
'ImporteMonetario': FORMAT.format(row.importe),
}
incapacidades.append(n)
nomina = {
'nomina': nomina,
'emisor': nomina_emisor,
'receptor': nomina_receptor,
'percepciones': percepciones,
'deducciones': deducciones,
'otros_pagos': otros_pagos,
'incapacidades': incapacidades,
}
data = {
'comprobante': comprobante,
'relacionados': relacionados,
'emisor': cfdi_emisor,
'receptor': receptor,
'conceptos': conceptos,
'complementos': complementos,
'nomina': nomina,
'impuestos': {},
'donativo': {},
}
return util.make_xml(data, certificado, auth)
def _stamp_id(self, id):
auth = Emisor.get_auth()
obj = CfdiNomina.get(CfdiNomina.id==id)
obj.xml = self._make_xml(self, obj, auth)
obj.estatus = 'Generado'
obj.save()
result = util.timbra_xml(obj.xml, auth)
# ~ print (result)
if result['ok']:
obj.xml = result['xml']
obj.uuid = result['uuid']
obj.fecha_timbrado = result['fecha']
obj.estatus = 'Timbrado'
obj.error = ''
obj.save()
# ~ cls._sync(cls, id, auth)
else:
msg = result['error']
obj.estatus = 'Error'
obj.error = msg
obj.save()
return result['ok'], obj.error
def _stamp(self):
where = ((CfdiNomina.uuid.is_null(True)) & (CfdiNomina.cancelada==False))
rows = CfdiNomina.select().where(where)
util.log_file('nomina', kill=True)
msg_error = ''
ok_stamp = 0
for row in rows:
msg = 'Timbrando el recibo: {}-{}'.format(row.serie, row.folio)
util.log_file('nomina', msg)
result, msg = self._stamp_id(self, row.id)
if result:
msg = 'Recibo: {}-{}, timbrado correctamente'.format(row.serie, row.folio)
ok_stamp += 1
else:
msg_error = msg
break
ok = False
if ok_stamp:
msg = 'Se timbraron {} recibos'.format(ok_stamp)
ok = True
error = False
if msg_error:
error = True
return {'ok': ok, 'msg_ok': msg, 'error': error, 'msg_error': msg_error}
@classmethod
def get_by(cls, values):
if not 'opt' in values:
if not values:
return cls._get(cls)
if values['opt'] == 'dates':
dates = util.loads(values['range'])
filters = CfdiNomina.fecha.between(
util.get_date(dates['start']),
util.get_date(dates['end'], True)
)
return cls._get(cls, filters)
if values['opt'] == 'yearmonth':
if values['year'] == '-1':
fy = (CfdiNomina.fecha.year > 0)
else:
fy = (CfdiNomina.fecha.year == int(values['year']))
if values['month'] == '-1':
fm = (CfdiNomina.fecha.month > 0)
else:
fm = (CfdiNomina.fecha.month == int(values['month']))
filters = (fy & fm)
return cls._get(cls, filters)
if values['opt'] == 'import':
return cls._import(cls)
if values['opt'] == 'stamp':
return cls._stamp(cls)
@classmethod
def remove(cls, id):
obj = CfdiNomina.get(CfdiNomina.id==id)
if obj.uuid:
return False
q = CfdiNominaDetalle.delete().where(CfdiNominaDetalle.cfdi==obj)
q.execute()
q = CfdiNominaTotales.delete().where(CfdiNominaTotales.cfdi==obj)
q.execute()
q = CfdiNominaPercepciones.delete().where(CfdiNominaPercepciones.cfdi==obj)
q.execute()
q = CfdiNominaDeducciones.delete().where(CfdiNominaDeducciones.cfdi==obj)
q.execute()
q = CfdiNominaOtroPago.delete().where(CfdiNominaOtroPago.cfdi==obj)
q.execute()
q = CfdiNominaHorasExtra.delete().where(CfdiNominaHorasExtra.cfdi==obj)
q.execute()
q = CfdiNominaIncapacidad.delete().where(CfdiNominaIncapacidad.cfdi==obj)
q.execute()
return bool(obj.delete_instance())
@classmethod
def get_xml(cls, id):
obj = CfdiNomina.get(CfdiNomina.id==id)
name = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.empleado.rfc)
# ~ cls._sync_xml(cls, obj)
return obj.xml, name
@classmethod
def filter_years(cls):
data = [{'id': -1, 'value': 'Todos'}]
rows = (CfdiNomina
.select(CfdiNomina.fecha.year.alias('year'))
.group_by(CfdiNomina.fecha.year)
.order_by(CfdiNomina.fecha.year)
)
if not rows is None:
data += [{'id': int(r.year), 'value': int(r.year)} for r in rows]
return tuple(data)
@classmethod
def cancel(cls, id):
msg = 'Recibo cancelado correctamente'
auth = Emisor.get_auth()
certificado = Certificado.select()[0]
obj = CfdiNomina.get(CfdiNomina.id==id)
if obj.uuid is None:
msg = 'Solo se pueden cancelar recibos timbrados'
return {'ok': False, 'msg': msg}
data, result = util.cancel_xml(auth, obj.uuid, certificado)
if data['ok']:
data['msg'] = 'Recibo cancelado correctamente'
data['row']['estatus'] = 'Cancelado'
obj.estatus = data['row']['estatus']
obj.error = ''
obj.cancelada = True
obj.fecha_cancelacion = result['Fecha']
obj.acuse = result['Acuse']
else:
obj.error = data['msg']
obj.save()
return data
class CfdiNominaDetalle(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)

View File

@ -9,13 +9,22 @@ var nomina_controllers = {
$$('cmd_close_empleados').attachEvent('onItemClick', cmd_close_empleados_click)
$$('cmd_delete_empleado').attachEvent('onItemClick', cmd_delete_empleado_click)
$$('cmd_import_empleados').attachEvent('onItemClick', cmd_import_empleados_click)
$$('cmd_nomina_without_stamp').attachEvent('onItemClick', cmd_nomina_without_stamp_click)
$$('cmd_nomina_delete').attachEvent('onItemClick', cmd_nomina_delete_click)
$$('cmd_nomina_timbrar').attachEvent('onItemClick', cmd_nomina_timbrar_click)
$$('cmd_nomina_cancel').attachEvent('onItemClick', cmd_nomina_cancel_click)
$$('grid_nomina').attachEvent('onItemClick', grid_nomina_click)
$$('filter_year_nomina').attachEvent('onChange', filter_year_nomina_change)
$$('filter_month_nomina').attachEvent('onChange', filter_month_nomina_change)
$$('filter_dates_nomina').attachEvent('onChange', filter_dates_nomina_change)
webix.extend($$('grid_nomina'), webix.ProgressBar)
}
}
function default_config_nomina(){
current_dates_nomina()
get_nomina()
}
@ -39,8 +48,26 @@ function current_dates_nomina(){
}
function get_nomina(){
function get_nomina(filters){
var grid = $$('grid_nomina')
grid.showProgress({type: 'icon'})
webix.ajax().get('/nomina', filters, {
error: function(text, data, xhr) {
msg = 'Error al consultar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json();
if (values.ok){
grid.clearAll();
grid.parse(values.rows, 'json');
}else{
msg_error(values.msg)
}
}
})
}
@ -259,4 +286,186 @@ function cmd_delete_empleado_click(){
}
}
})
}
function cmd_nomina_without_stamp_click(){
get_nomina()
}
function cmd_nomina_delete_click(){
var row = $$('grid_nomina').getSelectedItem()
if (row == undefined){
msg = 'Selecciona un registro'
msg_error(msg)
return
}
msg = '¿Estás seguro de eliminar el registro?<BR><BR>'
msg += row['empleado'] + ' (' + row['fecha_pago'] + ')'
msg += '<BR><BR>ESTA ACCIÓN NO SE PUEDE DESHACER<BR><BR>'
webix.confirm({
title: 'Eliminar Nomina',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if (result){
delete_nomina(row['id'])
}
}
})
}
function delete_nomina(id){
webix.ajax().del('/nomina', {id: id}, function(text, xml, xhr){
var msg = 'Registro eliminado correctamente'
if (xhr.status == 200){
$$('grid_nomina').remove(id);
msg_ok(msg)
} else {
msg = 'No se pudo eliminar.'
msg_error(msg)
}
})
}
function cmd_nomina_timbrar_click(){
get_nomina()
msg = 'Se enviarán a timbrar todos los recibos sin timbrar<BR><BR>'
msg += '¿Estás seguro de continuar?<BR><BR>'
webix.confirm({
title: 'Enviar a timbrar',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if (result){
timbrar_nomina()
}
}
})
}
function timbrar_nomina(){
webix.ajax().get('/nomina', {opt: 'stamp'}, {
error: function(text, data, xhr) {
msg = 'Error al timbrar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json();
if(values.ok){
get_nomina()
msg_ok(values.msg_ok)
}
if(values.error){
webix.alert({
title: 'Error al Timbrar',
text: values.msg_error,
type: 'alert-error'
})
}
}
})
}
function grid_nomina_click(id, e, node){
var row = this.getItem(id)
if(id.column == 'xml'){
location = '/doc/xmlnom/' + row.id
}else if(id.column == 'pdf'){
//~ get_momina_pdf(row.id)
//~ }else if(id.column == 'email'){
//~ enviar_correo(row)
}
}
function filter_year_nomina_change(nv, ov){
var fm = $$('filter_month_nomina')
filters = {'opt': 'yearmonth', 'year': nv, 'month': fm.getValue()}
get_nomina(filters)
}
function filter_month_nomina_change(nv, ov){
var fy = $$('filter_year_nomina')
filters = {'opt': 'yearmonth', 'year': fy.getValue(), 'month': nv}
get_nomina(filters)
}
function filter_dates_nomina_change(range){
if(range.start != null && range.end != null){
filters = {'opt': 'dates', 'range': range}
get_nomina(filters)
}
}
function cmd_nomina_cancel_click(){
var row = $$('grid_nomina').getSelectedItem()
if(row == undefined){
msg = 'Selecciona un registro'
msg_error(msg)
return
}
if(row['estatus'] != 'Timbrado'){
msg = 'Solo se pueden cancelar recibos timbrados'
msg_error(msg)
return
}
msg = '¿Estás seguro de cancelar el recibo?<BR><BR>'
msg += row['empleado'] + ' (' + row['serie'] + '-' + row['folio'] + ')'
msg += '<BR><BR>ESTA ACCIÓN NO SE PUEDE DESHACER<BR><BR>'
webix.confirm({
title: 'Cancelar Nomina',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if (result){
cancel_nomina(row['id'])
}
}
})
}
function cancel_nomina(id){
var grid = $$('grid_nomina')
var data = new Object()
data['opt'] = 'cancel'
data['id'] = id
webix.ajax().sync().post('nomina', data, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
values = data.json();
if(values.ok){
grid.updateItem(id, values.row)
msg_ok(values.msg)
}else{
msg_error(values.msg)
}
}
})
}

View File

@ -31,6 +31,9 @@ var toolbar_nomina_filter = [
labelAlign: 'right', labelWidth: 50, width: 200, options: months},
{view: 'daterangepicker', id: 'filter_dates_nomina', label: 'Fechas',
labelAlign: 'right', width: 300},
{},
{view: 'button', id: 'cmd_nomina_without_stamp', label: 'Sin Timbrar',
type: 'iconButton', autowidth: true, icon: 'filter'},
]
@ -44,12 +47,14 @@ var grid_cols_nomina = [
{id: "fecha", header: ["Fecha y Hora"], adjust: "data", sort: "string"},
{id: "estatus", header: ["Estatus", {content: "selectFilter"}],
adjust: "data", sort:"string"},
{id: "fecha_pago", header: ["Fecha de Pago"], adjust: "data", sort: "string"},
{id: 'fecha_pago', header: ['Fecha de Pago', {content: 'selectFilter'}],
adjust: 'data', sort: 'string'},
{id: 'total', header: ['Total', {content: 'numberFilter'}], width: 150,
sort: 'int', format: webix.i18n.priceFormat, css: 'right',
footer: {content: 'summActive', css: 'right'}},
{id: "empleado", header: ["Empleado", {content: "selectFilter"}],
fillspace:true, sort:"string"},
{id: 'xml', header: 'XML', adjust: 'data', template: get_icon('xml')},
{id: 'pdf', header: 'PDF', adjust: 'data', template: get_icon('pdf')},
]