Timbrar y cancelar nómina
This commit is contained in:
parent
6a66b15f56
commit
1cfea6978b
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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']):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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')},
|
||||
]
|
||||
|
||||
|
|
Loading…
Reference in New Issue