diff --git a/source/app/controllers/cfdi_xml.py b/source/app/controllers/cfdi_xml.py index dee3bfa..f680f58 100644 --- a/source/app/controllers/cfdi_xml.py +++ b/source/app/controllers/cfdi_xml.py @@ -39,6 +39,25 @@ SAT = { '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', }, + 'locales': { + 'version': '1.0', + 'prefix': 'implocal', + 'xmlns': 'http://www.sat.gob.mx/implocal', + 'schema': ' http://www.sat.gob.mx/implocal http://www.sat.gob.mx/sitio_internet/cfd/implocal/implocal.xsd', + }, + 'donativo': { + 'version': '1.1', + 'prefix': 'donat', + 'xmlns': 'http://www.sat.gob.mx/donat', + 'schema': ' http://www.sat.gob.mx/donat http://www.sat.gob.mx/sitio_internet/cfd/donat/donat11.xsd', + 'leyenda': 'Este comprobante ampara un donativo, el cual será destinado por la donataria a los fines propios de su objeto social. En el caso de que los bienes donados hayan sido deducidos previamente para los efectos del impuesto sobre la renta, este donativo no es deducible. La reproducción no autorizada de este comprobante constituye un delito en los términos de las disposiciones fiscales.', + }, + 'ine': { + 'version': '1.1', + 'prefix': 'ine', + 'xmlns': 'http://www.sat.gob.mx/ine', + 'schema': ' http://www.sat.gob.mx/ine http://www.sat.gob.mx/sitio_internet/cfd/ine/ine11.xsd', + }, } @@ -49,6 +68,10 @@ class CFDI(object): self._xsi = SAT['xsi'] self._pre = self._sat_cfdi['prefix'] self._cfdi = None + self._complemento = None + self._impuestos_locales = False + self._donativo = False + self._ine = False self.error = '' def _now(self): @@ -64,10 +87,15 @@ class CFDI(object): self._receptor(datos['receptor']) self._conceptos(datos['conceptos']) self._impuestos(datos['impuestos']) + self._locales(datos['impuestos']) + self._donatarias(datos['donativo']) + self._complementos(datos['complementos']) + if 'nomina' in datos: self._nomina(datos['nomina']) - if 'complementos' in datos: - self._complementos(datos['complementos']) + #~ if 'complementos' in datos: + #~ self._complementos(datos['complementos']) + return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8')) def add_sello(self, sello): @@ -80,6 +108,16 @@ 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['donativo']: + self._donativo = True + + if 'ine' in datos['complementos']: + self._ine = True + if 'nomina' in datos: return self._validate_nomina(datos) return True @@ -105,7 +143,27 @@ class CFDI(object): attributes = {} attributes['xmlns:{}'.format(self._pre)] = self._sat_cfdi['xmlns'] attributes['xmlns:xsi'] = self._xsi - attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + + schema_locales = '' + if self._impuestos_locales: + name = 'xmlns:{}'.format(SAT['locales']['prefix']) + attributes[name] = SAT['locales']['xmlns'] + schema_locales = SAT['locales']['schema'] + + schema_donativo = '' + if self._donativo: + name = 'xmlns:{}'.format(SAT['donativo']['prefix']) + attributes[name] = SAT['donativo']['xmlns'] + schema_donativo = SAT['donativo']['schema'] + + schema_ine = '' + if self._ine: + name = 'xmlns:{}'.format(SAT['ine']['prefix']) + attributes[name] = SAT['ine']['xmlns'] + schema_donativo = SAT['ine']['schema'] + + attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \ + schema_locales + schema_donativo +schema_ine attributes.update(datos) if not 'Version' in attributes: @@ -257,8 +315,58 @@ class CFDI(object): ET.SubElement(deducciones, '{}:Deduccion'.format(pre), row) return + def _locales(self, datos): + if not self._impuestos_locales: + return + + if self._complemento is None: + self._complemento = ET.SubElement( + self._cfdi, '{}:Complemento'.format(self._pre)) + + attributes = {} + attributes['version'] = SAT['locales']['version'] + attributes['TotaldeTraslados'] = datos['total_locales_trasladados'] + attributes['TotaldeRetenciones'] = datos['total_locales_retenciones'] + + node = ET.SubElement( + self._complemento, 'implocal:ImpuestosLocales', attributes) + + for retencion in datos['locales_retenciones']: + ET.SubElement(node, 'implocal:RetencionesLocales', retencion) + for traslado in datos['locales_trasladados']: + ET.SubElement(node, 'implocal:TrasladosLocales', traslado) + return + + def _donatarias(self, datos): + if not datos: + return + + if self._complemento is None: + self._complemento = ET.SubElement( + self._cfdi, '{}:Complemento'.format(self._pre)) + + attributes = {} + attributes['version'] = SAT['donativo']['version'] + attributes['leyenda'] = SAT['donativo']['leyenda'] + attributes.update(datos) + + node = ET.SubElement(self._complemento, 'donat:Donatarias', attributes) + + return + def _complementos(self, datos): - complemento = ET.SubElement(self._cfdi, '{}:Complemento'.format(self._pre)) + if not datos: + return + + if self._complemento is None: + self._complemento = ET.SubElement( + self._cfdi, '{}:Complemento'.format(self._pre)) + + if 'ine' in datos: + atributos = {'Version': SAT['ine']['version']} + atributos.update(datos['ine']) + ET.SubElement(self._complemento, 'ine:INE', atributos) + if 'ce' in datos: pre = 'cce11' datos = datos.pop('ce') diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 2afb7a0..bc3ac45 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -18,9 +18,10 @@ class AppLogin(object): session = req.env['beaker.session'] values = req.params values['rfc'] = values['rfc'].upper() - result = self._db.authenticate(values) + result, user = self._db.authenticate(values) if result['login']: session.save() + session['userobj'] = user session['user'] = result['user'] session['rfc'] = values['rfc'] req.context['result'] = result @@ -68,7 +69,11 @@ class AppValues(object): def on_get(self, req, resp, table): values = req.params - req.context['result'] = self._db.get_values(table, values) + if table == 'admin': + session = req.env['beaker.session'] + req.context['result'] = session['userobj'].es_admin + else: + req.context['result'] = self._db.get_values(table, values) resp.status = falcon.HTTP_200 def on_delete(self, req, resp, table): @@ -140,7 +145,6 @@ class AppPartners(object): def on_get(self, req, resp): values = req.params - #~ print ('VALUES', values) req.context['result'] = self._db.get_partners(values) resp.status = falcon.HTTP_200 @@ -287,9 +291,16 @@ class AppMovimientosBanco(object): def on_post(self, req, resp): values = req.params - req.context['result'] = self._db.cuentasbanco(values) + req.context['result'] = self._db.add_movbanco(values) resp.status = falcon.HTTP_200 + def on_delete(self, req, resp): + values = req.params + if self._db.delete('movbanco', values['id']): + resp.status = falcon.HTTP_200 + else: + resp.status = falcon.HTTP_204 + class AppFolios(object): diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 1a1e82d..bc4d446 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -36,7 +36,7 @@ from dateutil import parser from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \ PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \ - PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO + PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES #~ def _get_hash(password): @@ -816,7 +816,7 @@ class LIBO(object): image.GraphicURL = data['path_cbb'] pd.add(image) s = Size() - s.Width = 4250 + s.Width = 4150 s.Height = 4500 image.setSize(s) image.Anchor = self._set_cell('{timbre.cbb}') @@ -1270,6 +1270,10 @@ def get_bool(value): return False +def get_float(value): + return round(float(value), DECIMALES) + + class ImportFacturaLibre(object): def __init__(self, path, rfc): diff --git a/source/app/models/db.py b/source/app/models/db.py index c41252e..4b8c81d 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -14,6 +14,12 @@ class StorageEngine(object): def get_values(self, table, values=None): return getattr(self, '_get_{}'.format(table))(values) + def _get_configtimbrar(self, values): + return main.config_timbrar() + + def _get_saldocuenta(self, values): + return main.CuentasBanco.get_saldo(values['id']) + def _get_validartimbrar(self, values): return main.validar_timbrar() @@ -166,6 +172,8 @@ class StorageEngine(object): return main.SATImpuestos.remove(id) if table == 'cuentasbanco': return main.CuentasBanco.remove(id) + if table == 'movbanco': + return main.MovimientosBanco.remove(id) return False def _get_client(self, values): @@ -225,6 +233,9 @@ class StorageEngine(object): def cuentasbanco(self, values): return main.CuentasBanco.add(values) + def add_movbanco(self, values): + return main.MovimientosBanco.add(values) + def get_cuentasbanco(self, values): return main.CuentasBanco.get_(values) diff --git a/source/app/models/main.py b/source/app/models/main.py index 4c3c0f2..c927365 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -102,6 +102,21 @@ def validar_timbrar(): return {'ok': True, 'msg': msg} +def config_timbrar(): + try: + obj = Emisor.select()[0] + except IndexError: + return {'cfdi_donativo': False} + + conf = { + 'cfdi_anticipo': Configuracion.get_('chk_config_anticipo'), + 'cfdi_donativo': obj.es_ong, + 'cfdi_ine': Configuracion.get_('chk_config_ine'), + } + + return conf + + class Configuracion(BaseModel): clave = TextField(unique=True) valor = TextField(default='') @@ -136,9 +151,19 @@ class Configuracion(BaseModel): ) elif keys['fields'] == 'templates': fields = ( - 'txt_plantilla_factura_32', - 'txt_plantilla_factura_33', - 'txt_plantilla_factura_33j') + 'txt_plantilla_factura_32', + 'txt_plantilla_factura_33', + 'txt_plantilla_factura_33j' + ) + data = (Configuracion + .select() + .where(Configuracion.clave.in_(fields)) + ) + elif keys['fields'] == 'configotros': + fields = ( + 'chk_config_anticipo', + 'chk_config_ine', + ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) @@ -151,6 +176,7 @@ class Configuracion(BaseModel): def add(cls, values): try: for k, v in values.items(): + #~ print (k, v) obj, created = Configuracion.get_or_create(clave=k) obj.valor = v obj.save() @@ -193,6 +219,28 @@ class Usuarios(BaseModel): order_by = ('nombre', 'apellidos') +class Registro(BaseModel): + usuario = TextField() + accion = TextField(default='') + tabla = TextField(default='') + fecha = DateTimeField(default=util.now) + + def __str__(self): + t = '{} {}-{} ({})' + return t.format(self.usuario, self.accion, self.tabla, self.fecha) + + class Meta: + order_by = ('usuario', 'fecha') + + @classmethod + def add(cls, values): + try: + Registro.create(**values) + return + except: + return + + class SATRegimenes(BaseModel): key = TextField(index=True, unique=True) name = TextField(index=True) @@ -928,6 +976,20 @@ class CuentasBanco(BaseModel): def __str__(self): return '{} ({})'.format(self.banco.name, self.cuenta[-4:]) + @classmethod + def actualizar_saldo(cls, id, saldo): + fields = {'saldo': saldo} + q = CuentasBanco.update(**fields).where(CuentasBanco.id==id) + return bool(q.execute()) + + @classmethod + def get_saldo(cls, id): + try: + obj = CuentasBanco.get(CuentasBanco.id==id) + return obj.saldo + except CuentasBanco.DoesNotExist: + return 0 + @classmethod def remove(cls, id): try: @@ -1023,7 +1085,6 @@ class CuentasBanco(BaseModel): nuevo_mov= { 'cuenta': obj.id, 'fecha': fecha_deposito, - 'movimiento': 1, 'descripcion': 'Saldo inicial', 'forma_pago': SATFormaPago.get_by_key('99'), 'deposito': values['saldo'], @@ -1054,7 +1115,6 @@ class CuentasBanco(BaseModel): class MovimientosBanco(BaseModel): cuenta = ForeignKeyField(CuentasBanco) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) - movimiento = IntegerField(default=0) descripcion = TextField(default='') forma_pago = ForeignKeyField(SATFormaPago) retiro = DecimalField(default=0.0, max_digits=20, decimal_places=6, @@ -1063,6 +1123,8 @@ class MovimientosBanco(BaseModel): auto_round=True) saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) + cancelado = BooleanField(default=False) + conciliado = BooleanField(default=False) moneda = TextField(default='MXN') # Complemento de pagos tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6, auto_round=True) @@ -1079,12 +1141,62 @@ class MovimientosBanco(BaseModel): class Meta: order_by = ('fecha',) - indexes = ( - (('cuenta', 'movimiento'), True), + + def _ultimo_saldo(self, cuenta, fecha): + query = (MovimientosBanco + .select() + .where( + (MovimientosBanco.cuenta==cuenta) & + (MovimientosBanco.fecharow.fecha) & + (MovimientosBanco.cancelado==False)) + ) + + saldo = round(Decimal(row.saldo), DECIMALES) + for mov in query: + mov.saldo = saldo + mov.deposito - mov.retiro + mov.save() + saldo = mov.saldo + CuentasBanco.actualizar_saldo(row.cuenta, saldo) + return saldo @classmethod def add(cls, values): + ids = values.pop('ids', '') + actualizar = False + if not 'saldo' in values: + actualizar = True + hora = values.pop('hora') + values['fecha'] = '{}T{}'.format(values['fecha'][:10], hora) + values['cuenta'] = int(values['cuenta']) + values['retiro'] = util.get_float(values['retiro']) + values['deposito'] = util.get_float(values['deposito']) + values['forma_pago'] = int(values['forma_pago']) + + ultimo_saldo = cls._ultimo_saldo( + cls, values['cuenta'], values['fecha']) + values['saldo'] = \ + ultimo_saldo - values['retiro'] + values['deposito'] + with database_proxy.transaction(): try: obj = MovimientosBanco.create(**values) @@ -1092,7 +1204,31 @@ class MovimientosBanco(BaseModel): msg = 'Este movimiento ya existe' return {'ok': False, 'msg': msg} - return {'ok': True} + if actualizar: + saldo = cls._actualizar_saldos(cls, obj) + if ids: + FacturasPagos.add(obj, util.loads(ids)) + + return {'ok': True, 'saldo': saldo} + + @classmethod + def remove(cls, id): + try: + obj = MovimientosBanco.get(MovimientosBanco.id==id) + except MovimientosBanco.DoesNotExist: + return False + + if obj.conciliado or obj.cancelado: + return False + + with database_proxy.transaction(): + obj.cancelado = True + obj.save() + + obj = cls._movimiento_anterior(cls, obj.cuenta, obj.fecha) + cls._actualizar_saldos(cls, obj) + + return True @classmethod def con(cls, id): @@ -1115,7 +1251,10 @@ class MovimientosBanco(BaseModel): fd = (MovimientosBanco.fecha.between( util.get_date(rango['start']), util.get_date(rango['end'], True))) - filtros = (fd & (MovimientosBanco.cuenta==cuenta)) + filtros = (fd & + (MovimientosBanco.cuenta==cuenta) & + (MovimientosBanco.cancelado==False) + ) else: year = int(values['year']) mes = int(values['mes']) @@ -1127,7 +1266,10 @@ class MovimientosBanco(BaseModel): fm = (MovimientosBanco.fecha.month > 0) else: fm = (MovimientosBanco.fecha.month == mes) - filtros = (fy & fm & (MovimientosBanco.cuenta==cuenta)) + filtros = (fy & fm & + (MovimientosBanco.cuenta==cuenta) & + (MovimientosBanco.cancelado==False) + ) rows = tuple(MovimientosBanco .select( @@ -1147,6 +1289,8 @@ class MovimientosBanco(BaseModel): class CfdiPagos(BaseModel): movimiento = ForeignKeyField(MovimientosBanco) + fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) + fecha_timbrado = DateTimeField(null=True) xml = TextField(default='') uuid = UUIDField(null=True) estatus = TextField(default='Guardado') @@ -1181,6 +1325,12 @@ class SATUsoCfdi(BaseModel): return return SATUsoCfdi.get(SATUsoCfdi.key==key).id + @classmethod + def get_key(self, id): + if id is None: + return + return SATUsoCfdi.get(SATUsoCfdi.id==id).key + @classmethod def get_activos(cls): rows = (SATUsoCfdi @@ -1278,12 +1428,12 @@ class Socios(BaseModel): @classmethod def get_(cls, values): - print ('values', values) + #~ print ('values', values) id = values.get('id', 0) if id: id = int(values['id']) row = Socios.select().where(Socios.id==id).dicts()[0] - row['uso_cfdi_socio'] = row.pop('uso_cfdi') + row['uso_cfdi_socio'] = SATUsoCfdi.get_key(row.pop('uso_cfdi')) if not row['condicion_pago'] is None: row['condicion_pago'] = \ str(CondicionesPago.get(id=row['condicion_pago'])) @@ -1397,7 +1547,16 @@ class Socios(BaseModel): return bool(q.execute()) +class Almacenes(BaseModel): + nombre = TextField(default='') + ubicacion = TextField(default='') + + class Meta: + order_by = ('nombre',) + + class Productos(BaseModel): + almacen = ForeignKeyField(Almacenes, null=True) categoria = ForeignKeyField(Categorias, null=True) clave = TextField(unique=True, index=True) clave_sat = TextField(default='') @@ -1443,6 +1602,7 @@ class Productos(BaseModel): .select( Productos.id, Productos.clave, + Productos.clave_sat, Productos.descripcion, SATUnidades.name.alias('unidad'), Productos.valor_unitario, @@ -1463,8 +1623,12 @@ class Productos(BaseModel): if name: rows = (Productos .select( - Productos.id, Productos.clave, Productos.descripcion, - SATUnidades.name.alias('unidad'), Productos.valor_unitario) + Productos.id, + Productos.clave, + Productos.clave_sat, + Productos.descripcion, + SATUnidades.name.alias('unidad'), + Productos.valor_unitario) .join(SATUnidades) .switch(Productos) .where(Productos.descripcion.contains(name)) @@ -1623,6 +1787,8 @@ class Facturas(BaseModel): estatus_sat = TextField(default='Vigente') regimen_fiscal = TextField(default='') notas = TextField(default='') + saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, + auto_round=True) pagada = BooleanField(default=False) cancelada = BooleanField(default=False) fecha_cancelacion = DateTimeField(null=True) @@ -1873,7 +2039,42 @@ class Facturas(BaseModel): return (Facturas.folio.between(folio1, folio2)) + def _get_por_pagar(self, ids): + filtros = ( + (Facturas.cancelada==False) & + (Facturas.uuid.is_null(False)) & + (Facturas.tipo_comprobante=='I') & + (Facturas.saldo>0) + ) + if ids: + filtros &= (Facturas.id.not_in(ids)) + + rows = tuple(Facturas + .select( + Facturas.id, + Facturas.serie, + Facturas.folio, + Facturas.uuid, + Facturas.fecha, + Facturas.tipo_comprobante, + Facturas.estatus, + Socios.nombre.alias('cliente'), + Facturas.total, + Facturas.saldo, + ) + .where(filtros) + .join(Socios) + .switch(Facturas) + .dicts() + ) + return {'ok': True, 'rows': rows} + + return + def _get_opt(self, values): + if values['opt'] == 'porpagar': + return self._get_por_pagar(self, util.loads(values['ids'])) + cfdis = util.loads(values['cfdis']) if values['year'] == '-1': @@ -1984,6 +2185,8 @@ class Facturas(BaseModel): total_trasladados = None total_retenciones = None total_iva = 0 + locales_traslados = 0 + locales_retenciones = 0 for product in products: id_product = product.pop('id') @@ -2015,10 +2218,8 @@ class Facturas(BaseModel): for tax in p.impuestos: if tax.id in totals_tax: - #~ totals_tax[tax.id].importe += product['importe'] totals_tax[tax.id].importe += importe else: - #~ tax.importe = product['importe'] tax.importe = importe totals_tax[tax.id] = tax @@ -2027,7 +2228,10 @@ class Facturas(BaseModel): continue import_tax = round(float(tax.tasa) * tax.importe, DECIMALES) - total_trasladados = (total_trasladados or 0) + import_tax + if tax.key == '000': + locales_traslados += import_tax + else: + total_trasladados = (total_trasladados or 0) + import_tax if tax.name == 'IVA': total_iva += import_tax @@ -2046,7 +2250,10 @@ class Facturas(BaseModel): import_tax = round(float(tax.tasa) * total_iva, DECIMALES) else: import_tax = round(float(tax.tasa) * tax.importe, DECIMALES) - total_retenciones = (total_retenciones or 0) + import_tax + if tax.key == '000': + locales_retenciones += import_tax + else: + total_retenciones = (total_retenciones or 0) + import_tax invoice_tax = { 'factura': invoice.id, @@ -2056,7 +2263,9 @@ class Facturas(BaseModel): } FacturasImpuestos.create(**invoice_tax) - total = subtotal + (total_trasladados or 0) - (total_retenciones or 0) + total = subtotal + \ + (total_trasladados or 0) - (total_retenciones or 0) \ + + locales_traslados - locales_retenciones total_mn = round(total * invoice.tipo_cambio, DECIMALES) data = { 'subtotal': subtotal + descuento, @@ -2077,26 +2286,42 @@ class Facturas(BaseModel): FacturasRelacionadas.create(**data) return + def _guardar_ine(self, invoice, valores): + if not valores: + return + + data = { + 'factura': invoice, + 'nombre': 'ine', + 'valores': valores, + } + FacturasComplementos.create(**data) + return + @classmethod def add(cls, values): productos = util.loads(values.pop('productos')) relacionados = util.loads(values.pop('relacionados')) + ine = values.pop('ine', {}) emisor = Emisor.select()[0] values['folio'] = cls._get_folio(cls, values['serie']) values['tipo_cambio'] = float(values['tipo_cambio']) values['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal values['anticipo'] = util.get_bool(values['anticipo']) + values['donativo'] = util.get_bool(values['donativo']) with database_proxy.atomic() as txn: obj = Facturas.create(**values) totals = cls._calculate_totals(cls, obj, productos) cls._guardar_relacionados(cls, obj, relacionados) + cls._guardar_ine(cls, obj, ine) obj.subtotal = totals['subtotal'] obj.descuento = totals['descuento'] obj.total_trasladados = totals['total_trasladados'] obj.total_retenciones = totals['total_retenciones'] obj.total = totals['total'] + obj.saldo = totals['total'] obj.total_mn = totals['total_mn'] obj.save() @@ -2118,8 +2343,16 @@ class Facturas(BaseModel): def _make_xml(self, invoice): emisor = Emisor.select()[0] certificado = Certificado.select()[0] + comprobante = {} relacionados = {} + donativo = {} + complementos = FacturasComplementos.get_(invoice) + + if invoice.donativo: + donativo['noAutorizacion'] = emisor.autorizacion + donativo['fechaAutorizacion'] = str(emisor.fecha_autorizacion) + if invoice.serie: comprobante['Serie'] = invoice.serie if invoice.condiciones_pago: @@ -2162,7 +2395,6 @@ class Facturas(BaseModel): 'UsoCFDI': invoice.uso_cfdi, } - #~ descuento = 0 conceptos = [] rows = FacturasDetalle.select().where(FacturasDetalle.factura==invoice) for row in rows: @@ -2178,7 +2410,6 @@ class Facturas(BaseModel): } if row.descuento: concepto['Descuento'] = FORMAT.format(row.descuento) - #~ descuento += row.descuento taxes = {} traslados = [] @@ -2187,6 +2418,10 @@ class Facturas(BaseModel): for impuesto in row.producto.impuestos: if impuesto.tipo == 'E': continue + + if impuesto.key == '000': + continue + base = row.importe - row.descuento import_tax = round(impuesto.tasa * base, DECIMALES) tipo_factor = 'Tasa' @@ -2211,12 +2446,13 @@ class Facturas(BaseModel): concepto['impuestos'] = taxes conceptos.append(concepto) - #~ if descuento: - #~ comprobante['Descuento'] = FORMAT.format(descuento) - impuestos = {} traslados = [] retenciones = [] + total_locales_trasladados = 0 + total_locales_retenciones = 0 + locales_trasladados = [] + locales_retenciones = [] if not invoice.total_trasladados is None: impuestos['TotalImpuestosTrasladados'] = \ FORMAT.format(invoice.total_trasladados) @@ -2228,6 +2464,27 @@ class Facturas(BaseModel): .select() .where(FacturasImpuestos.factura==invoice)) for tax in taxes: + if tax.impuesto.key == '000': + tasa = str(round(tax.impuesto.tasa * 100, 2)) + simporte = FORMAT.format(tax.importe) + if tax.impuesto.tipo == 'T': + traslado = { + 'ImpLocTrasladado': tax.impuesto.name, + 'TasadeTraslado': tasa, + 'Importe': simporte, + } + locales_trasladados.append(traslado) + total_locales_trasladados += tax.importe + else: + retencion = { + 'ImpLocRetenido': tax.impuesto.name, + 'TasadeRetencion': tasa, + 'Importe': simporte, + } + locales_retenciones.append(retencion) + total_locales_retenciones += tax.importe + continue + tipo_factor = 'Tasa' if tax.impuesto.factor != 'T': tipo_factor = 'Cuota' @@ -2248,6 +2505,16 @@ class Facturas(BaseModel): impuestos['traslados'] = traslados impuestos['retenciones'] = retenciones + impuestos['total_locales_trasladados'] = '' + if total_locales_trasladados: + impuestos['total_locales_trasladados'] = \ + FORMAT.format(total_locales_trasladados) + impuestos['total_locales_retenciones'] = '' + if total_locales_retenciones: + impuestos['total_locales_retenciones'] = \ + FORMAT.format(total_locales_retenciones) + impuestos['locales_trasladados'] = locales_trasladados + impuestos['locales_retenciones'] = locales_retenciones data = { 'comprobante': comprobante, @@ -2256,6 +2523,8 @@ class Facturas(BaseModel): 'receptor': receptor, 'conceptos': conceptos, 'impuestos': impuestos, + 'donativo': donativo, + 'complementos': complementos, } return util.make_xml(data, certificado) @@ -2713,15 +2982,39 @@ class FacturasRelacionadas(BaseModel): return [str(r.factura_origen.uuid) for r in query] -class CfdiPagosFacturas(BaseModel): - pago = ForeignKeyField(CfdiPagos) +class FacturasComplementos(BaseModel): factura = ForeignKeyField(Facturas) + nombre = TextField(default='') + valores = TextField(default='') class Meta: - order_by = ('pago',) - indexes = ( - (('pago', 'factura'), True), + order_by = ('factura',) + + @classmethod + def get_(cls, factura): + query = (FacturasComplementos + .select() + .where(FacturasComplementos.factura==factura) ) + return {r.nombre: util.loads(r.valores) for r in query} + + +# ~ class CfdiPagosFacturas(BaseModel): + # ~ pago = ForeignKeyField(CfdiPagos) + # ~ factura = ForeignKeyField(Facturas) + # ~ numero = IntegerField(default=1) + # ~ saldo_anterior = DecimalField(default=0.0, max_digits=20, decimal_places=6, + # ~ auto_round=True) + # ~ importe = DecimalField(default=0.0, max_digits=18, decimal_places=6, + # ~ auto_round=True) + # ~ saldo = DecimalField(default=0.0, max_digits=18, decimal_places=6, + # ~ auto_round=True) + + # ~ class Meta: + # ~ order_by = ('pago',) + # ~ indexes = ( + # ~ (('pago', 'factura', 'numero'), True), + # ~ ) class PreFacturasRelacionadas(BaseModel): @@ -2867,6 +3160,7 @@ class FacturasImpuestos(BaseModel): class FacturasPagos(BaseModel): + movimiento = ForeignKeyField(MovimientosBanco) factura = ForeignKeyField(Facturas) numero = IntegerField(default=1) saldo_anterior = DecimalField(default=0.0, max_digits=20, decimal_places=6, @@ -2882,6 +3176,12 @@ class FacturasPagos(BaseModel): (('factura', 'numero'), True), ) + @classmethod + def add(cls, mov, ids): + print (mov) + print (ids) + return + class PreFacturasImpuestos(BaseModel): factura = ForeignKeyField(PreFacturas) @@ -2942,8 +3242,8 @@ def authenticate(args): respuesta['login'] = True respuesta['user'] = str(obj) respuesta['super'] = obj.es_superusuario - #~ desconectar() - return respuesta + #~ respuesta['admin'] = obj.es_superusuario or obj.es_admin + return respuesta, obj def get_cp(cp): @@ -3033,15 +3333,15 @@ def _init_values(rfc): def _crear_tablas(rfc): tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion, - Folios, + Folios, Registro, Emisor, Facturas, FacturasDetalle, FacturasImpuestos, FacturasPagos, - FacturasRelacionadas, Productos, + FacturasRelacionadas, FacturasComplementos, Almacenes, Productos, PreFacturas, PreFacturasDetalle, PreFacturasImpuestos, PreFacturasRelacionadas, SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes, SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos, Socios, Tags, Usuarios, CuentasBanco, TipoCambio, MovimientosBanco, - CfdiPagos, CfdiPagosFacturas, + CfdiPagos, Emisor.regimenes.get_through_model(), Socios.tags.get_through_model(), Productos.impuestos.get_through_model(), diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 17377c0..3a75a1a 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -39,6 +39,8 @@ var controllers = { tb_options.attachEvent('onChange', tab_options_change) $$('txt_plantilla_factura_32').attachEvent('onItemClick', txt_plantilla_factura_32_click) $$('txt_plantilla_factura_33').attachEvent('onItemClick', txt_plantilla_factura_33_click) + $$('chk_config_anticipo').attachEvent('onItemClick', chk_config_item_click) + $$('chk_config_ine').attachEvent('onItemClick', chk_config_item_click) } } @@ -297,6 +299,7 @@ function get_config_values(opt){ }, success: function(text, data, xhr) { var values = data.json() + //~ showvar(values) Object.keys(values).forEach(function(key){ $$(key).setValue(values[key]) }) @@ -808,6 +811,7 @@ function txt_plantilla_factura_32_click(e){ function tab_options_change(nv, ov){ var cv = { Plantillas: 'templates', + Otros: 'configotros', } get_config_values(cv[nv]) } @@ -1185,3 +1189,23 @@ function cmd_emisor_eliminar_cuenta_click(){ } }) } + + +function chk_config_item_click(id, e){ + var values = {} + values[id] = $$(id).getValue() + + webix.ajax().sync().post('/config', values, { + error: function(text, data, xhr) { + msg = 'Error al guardar la configuración' + msg_error(msg) + }, + success: function(text, data, xhr) { + var values = data.json(); + if (!values.ok){ + msg_error(values.msg) + } + } + }) + +} diff --git a/source/static/js/controller/bancos.js b/source/static/js/controller/bancos.js index 5647674..836593b 100644 --- a/source/static/js/controller/bancos.js +++ b/source/static/js/controller/bancos.js @@ -1,10 +1,21 @@ var msg = '' +var msg_importe = '' + var bancos_controllers = { init: function(){ $$('lst_cuentas_banco').attachEvent('onChange', lst_cuentas_banco_change) $$('cmd_agregar_retiro').attachEvent('onItemClick', cmd_agregar_retiro_click) $$('cmd_agregar_deposito').attachEvent('onItemClick', cmd_agregar_deposito_click) + $$('cmd_guardar_retiro').attachEvent('onItemClick', cmd_guardar_retiro_click) + $$('cmd_guardar_deposito').attachEvent('onItemClick', cmd_guardar_deposito_click) + $$('cmd_cancelar_movimiento').attachEvent('onItemClick', cmd_cancelar_movimiento_click) + $$('txt_retiro_importe').attachEvent('onChange', txt_retiro_importe_change) + $$('txt_deposito_importe').attachEvent('onChange', txt_deposito_importe_change) + $$('grid_cfdi_este_deposito').attachEvent('onAfterDrop', grid_cfdi_este_deposito_after_drop) + $$('grid_cfdi_por_pagar').attachEvent('onAfterDrop', grid_cfdi_por_pagar_after_drop) + $$('grid_cfdi_este_deposito').attachEvent('onBeforeEditStop', grid_cfdi_este_deposito_before_edit_stop) + $$('grid_cfdi_este_deposito').attachEvent('onAfterEditStop', grid_cfdi_este_deposito_after_edit_stop) set_year_month() } } @@ -31,7 +42,6 @@ function set_year_month(){ m.unblockEvent() } }) - } @@ -91,16 +101,504 @@ function get_estado_cuenta(rango){ } +function get_saldo_cuenta(){ + var id = $$('lst_cuentas_banco').getValue() + webix.ajax().get('/values/saldocuenta', {id: id}, function(text, data){ + var value = data.json() + if(value){ + $$('txt_cuenta_saldo').setValue(value) + }else{ + msg = 'No se pudo consultar el saldo' + msg_error(msg) + } + }) +} + + function lst_cuentas_banco_change(nv, ov){ show('Cuenta change') } +function get_bancos_forma_pago(retiro){ + webix.ajax().get('/values/formapago', {}, function(text, data){ + var values = data.json() + if(retiro){ + $$('lst_retiro_forma_pago').getList().parse(values) + }else{ + $$('lst_deposito_forma_pago').getList().parse(values) + } + }) +} + + +function get_facturas_por_pagar(){ + var grid1 = $$('grid_cfdi_este_deposito') + var grid2 = $$('grid_cfdi_por_pagar') + + var ids = [] + grid1.data.each(function(obj){ + ids.push(obj.id) + }) + + webix.ajax().get('/invoices', {'opt': 'porpagar', 'ids': ids}, { + error:function(text, data, XmlHttpRequest){ + msg = 'Ocurrio un error, consulta a soporte técnico' + msg_error(msg) + }, + success:function(text, data, XmlHttpRequest){ + var values = data.json() + grid2.clearAll() + if (values.ok){ + grid2.parse(values.rows, 'json') + } + } + }) +} + + function cmd_agregar_retiro_click(){ - show('Retiro') + get_bancos_forma_pago(true) + $$('multi_bancos').setValue('banco_retiro') } function cmd_agregar_deposito_click(){ - show('Depósito') + msg_importe = '' + get_bancos_forma_pago(false) + get_facturas_por_pagar() + $$('multi_bancos').setValue('banco_deposito') } + + +function validate_retiro(values){ + var importe = values.retiro_importe.replace('$', '').replace(',', '').trim() + + if(!importe){ + msg = 'El importe es requerido' + msg_error(msg) + return false + } + importe = parseFloat(importe).round(2) + if(importe <= 0){ + msg = 'El importe debe ser mayor a cero' + msg_error(msg) + return false + } + + if(!values.retiro_descripcion.trim()){ + msg = 'La descripción es requerida' + msg_error(msg) + return false + } + + var today = new Date() + if(values.retiro_fecha > today){ + msg = 'Fecha inválida, es una fecha futura' + msg_error(msg) + return + } + + var horas = $$('time_retiro').getText().split(':') + var seg = parseInt(horas[2]) + var min = parseInt(horas[1]) + var horas = parseInt(horas[0]) + + if(horas > 23){ + focus('time_retiro') + msg = 'Hora inválida' + msg_error(msg) + return false + } + if(min > 59){ + focus('time_retiro') + msg = 'Hora inválida' + msg_error(msg) + return false + } + if(seg > 59){ + focus('time_retiro') + msg = 'Hora inválida' + msg_error(msg) + return false + } + + return true +} + + +function guardar_retiro(values){ + var form = $$('form_banco_retiro') + + var importe = get_float(values.retiro_importe) + var data = new Object() + data['cuenta'] = $$('lst_cuentas_banco').getValue() + data['fecha'] = values.retiro_fecha + data['hora'] = $$('time_retiro').getText() + data['numero_operacion'] = values.retiro_referencia.trim() + data['forma_pago'] = $$('lst_retiro_forma_pago').getValue() + data['retiro'] = importe + data['deposito'] = 0.0 + data['descripcion'] = values.retiro_descripcion + + webix.ajax().post('/movbanco', data, { + error:function(text, data, XmlHttpRequest){ + msg = 'Ocurrio un error, consulta a soporte técnico' + msg_error(msg) + }, + success:function(text, data, XmlHttpRequest){ + var values = data.json() + if(values.ok){ + $$('txt_cuenta_saldo').setValue(values.saldo) + get_estado_cuenta() + $$('multi_bancos').setValue('banco_home') + form.setValues({}) + }else{ + msg_error(values.msg) + } + } + }) +} + + +function cmd_guardar_retiro_click(){ + var form = $$('form_banco_retiro') + + if(!form.validate()) { + msg_error('Valores inválidos') + return + } + + var values = form.getValues() + if(!validate_retiro(values)){ + return + } + + msg = 'Todos los datos son correctos.

¿Deseas agregar este retiro?' + webix.confirm({ + title: 'Guardar Retiro', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if(result){ + guardar_retiro(values) + } + } + }) +} + + +function txt_retiro_importe_change(new_value, old_value){ + if(!isFinite(new_value)){ + this.config.value = old_value + this.refresh() + } +} + +function txt_deposito_importe_change(new_value, old_value){ + if(!isFinite(new_value)){ + this.config.value = old_value + this.refresh() + } +} + + +function actualizar_deposito(grid){ + grid.sort("#fecha#", "desc", "date") + + var suma = 0 + var descripcion = '' + grid.data.each(function(obj){ + descripcion += 'Pago de la factura: ' + obj.serie + obj.folio + ' del ' + descripcion += 'cliente: ' + obj.cliente + '\n' + if(obj.importe == undefined){ + obj.importe = obj.saldo + } + suma += obj.importe.to_float() + }) + $$('txt_deposito_importe').setValue(suma) + $$('deposito_descripcion').setValue(descripcion.slice(0, -1)) + grid.refresh() +} + + +function grid_cfdi_por_pagar_after_drop(context, native_event){ + var grid = $$('grid_cfdi_este_deposito') + actualizar_deposito(grid) +} + + +function grid_cfdi_este_deposito_after_drop(context, native_event){ + var grid = $$('grid_cfdi_este_deposito') + actualizar_deposito(grid) +} + + +function grid_cfdi_este_deposito_after_edit_stop(state, editor, ignoreUpdate){ + var grid = $$('grid_cfdi_este_deposito') + + var suma = 0 + grid.data.each(function(obj){ + suma += obj.importe.to_float() + }) + $$('txt_deposito_importe').setValue(suma) +} + + +function grid_cfdi_este_deposito_before_edit_stop(state, editor){ + var grid = $$('grid_cfdi_este_deposito') + var row = grid.getItem(editor.row) + + if(editor.column == 'importe'){ + var importe = parseFloat(state.value) + if(isNaN(importe)){ + msg = 'El importe a pagar debe ser un número' + msg_error(msg) + grid.blockEvent() + state.value = state.old + grid.editCancel() + grid.unblockEvent() + return true + } + if(importe <= 0){ + msg = 'El importe a pagar debe ser mayor a cero' + msg_error(msg) + grid.blockEvent() + state.value = state.old + grid.editCancel() + grid.unblockEvent() + return true + } + var saldo = row['saldo'].to_float() + if(importe > saldo){ + msg = 'El importe a pagar no puede ser mayor al saldo de la factura' + msg_error(msg) + grid.blockEvent() + state.value = state.old + grid.editCancel() + grid.unblockEvent() + return true + } + } +} + + +function validate_deposito(values){ + var grid = $$('grid_cfdi_este_deposito') + var importe = values.deposito_importe.to_float() + + if(!importe){ + msg = 'El importe es requerido' + msg_error(msg) + return false + } + + if(importe <= 0){ + msg = 'El importe debe ser mayor a cero' + msg_error(msg) + return false + } + + if(!values.deposito_descripcion.trim()){ + msg = 'La descripción es requerida' + msg_error(msg) + return false + } + + var today = new Date() + if(values.deposito_fecha > today){ + msg = 'Fecha inválida, es una fecha futura' + msg_error(msg) + return + } + + var horas = $$('time_deposito').getText().split(':') + var seg = parseInt(horas[2]) + var min = parseInt(horas[1]) + var horas = parseInt(horas[0]) + + if(horas > 23){ + focus('time_deposito') + msg = 'Hora inválida' + msg_error(msg) + return false + } + if(min > 59){ + focus('time_deposito') + msg = 'Hora inválida' + msg_error(msg) + return false + } + if(seg > 59){ + focus('time_deposito') + msg = 'Hora inválida' + msg_error(msg) + return false + } + + if(grid.count()){ + var suma = 0 + grid.data.each(function(obj){ + var tmp = obj.importe.to_float() + if(tmp <= 0){ + msg = 'El importe de la factura: ' + obj.serie + obj.folio + ' no puede ser menor a cero' + msg_error(msg) + return false + } + suma += tmp + }) + if(suma > importe){ + msg = 'La suma del pago de facturas, no puede ser mayor al deposito' + msg_error(msg) + return false + } + if(suma < importe){ + msg_importe = 'El importe del depósito en mayor a la suma de facturas. ' + msg_importe += 'Asegurate de que esto sea correcto' + } + } + + return true +} + + +function guardar_deposito(values){ + var form = $$('form_banco_deposito') + var grid = $$('grid_cfdi_este_deposito') + + var data = new Object() + data['cuenta'] = $$('lst_cuentas_banco').getValue() + data['fecha'] = values.deposito_fecha + data['hora'] = $$('time_deposito').getText() + data['numero_operacion'] = values.deposito_referencia.trim() + data['forma_pago'] = $$('lst_deposito_forma_pago').getValue() + data['deposito'] = values.deposito_importe.to_float() + data['retiro'] = 0.0 + data['descripcion'] = values.deposito_descripcion + + if(grid.count()){ + var ids = new Object() + grid.data.each(function(obj){ + ids[obj.id] = obj.importe.to_float() + }) + data['ids'] = ids + } + + webix.ajax().post('/movbanco', data, { + error:function(text, data, XmlHttpRequest){ + msg = 'Ocurrio un error, consulta a soporte técnico' + msg_error(msg) + }, + success:function(text, data, XmlHttpRequest){ + var values = data.json() + if(values.ok){ + $$('txt_cuenta_saldo').setValue(values.saldo) + get_estado_cuenta() + $$('multi_bancos').setValue('banco_home') + form.setValues({}) + grid.clearAll() + }else{ + msg_error(values.msg) + } + } + }) +} + + +function cmd_guardar_deposito_click(){ + var form = $$('form_banco_deposito') + var grid = $$('grid_cfdi_este_deposito') + + if(!form.validate()) { + msg_error('Valores inválidos') + return + } + + var values = form.getValues() + if(!validate_deposito(values)){ + return + } + + if(!grid.count()){ + msg = 'Todos los datos son correctos
br>' + msg = 'El depósito no tiene facturas relacionadas

¿Estás ' + msg += ' seguro de guardar del depósito sin facturas relacionadas?' + webix.confirm({ + title: 'Guardar depósito', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if(result){ + guardar_deposito(values) + } + } + }) + }else{ + if(!msg_importe){ + msg_importe = 'Se van a relacionar ' + grid.count() + ' facturas.' + } + msg = 'Todos los datos son correctos.

' + msg_importe + '

' + msg += '¿Deseas agregar este depósito?' + webix.confirm({ + title: 'Guardar depósito', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if(result){ + guardar_deposito(values) + } + } + }) + } +} + + +function cancelar_movimiento(id){ + var grid = $$('grid_cuentabanco') + + webix.ajax().del('/movbanco', {id: id}, function(text, xml, xhr){ + if(xhr.status == 200){ + get_estado_cuenta() + get_saldo_cuenta() + msg_sucess('Movimiento cancelado correctamente') + }else{ + msg_error('No se pudo eliminar') + } + }) +} + + +function cmd_cancelar_movimiento_click(){ + var grid = $$('grid_cuentabanco') + + var row = grid.getSelectedItem() + if(row == undefined){ + msg_error('Selecciona un movimiento') + return + } + if(row.descripcion == 'Saldo inicial'){ + msg_error('No es posible eliminar el saldo inicial') + return + } + + var msg = '¿Estás seguro de cancelar el movimiento seleccionado?' + msg += '

ESTA ACCIÓN NO SE PUEDE DESHACER

' + webix.confirm({ + title:'Cancelar Movimiento', + ok:'Si', + cancel:'No', + type:'confirm-error', + text:msg, + callback:function(result){ + if (result){ + cancelar_movimiento(row['id']) + } + } + }) +} \ No newline at end of file diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 593cab9..7520e67 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -56,7 +56,6 @@ function get_monedas(){ $$('lst_moneda').setValue(pre.id) if(values.length == 1){ $$('fs_moneda').hide() - //~ $$('fs_moneda').refresh() } }) } @@ -77,7 +76,6 @@ function get_regimen_fiscal(){ $$('lst_regimen_fiscal').setValue(pre.id) if(values.length == 1){ $$('fs_regimen_fiscal').hide() - //~ $$('fs_regimen_fiscal').refresh() } }) } @@ -109,6 +107,17 @@ function default_config(){ $$('cmd_timbrar').enable() } }) + + webix.ajax().sync().get('/values/configtimbrar', function(text, data){ + var values = data.json() + show('chk_cfdi_anticipo', values.cfdi_anticipo) + show('chk_cfdi_donativo', values.cfdi_donativo) + if(!values.cfdi_ine || values.cfdi_ine == '0'){ + $$('tv_invoice').getTabbar().hideOption('INE') + }else{ + $$('tv_invoice').getTabbar().showOption('INE') + } + }) } @@ -260,6 +269,13 @@ function validate_invoice(values){ return false } + var r = grid.data.getRange() + if(r[0].clave_sat != CLAVE_ANTICIPOS){ + msg = 'La clave del SAT para anticipos debe ser: ' + CLAVE_ANTICIPOS + msg_error(msg) + return false + } + query = table_relaciones.chain().data() if(query.length > 0){ msg = 'Los anticipos no deben llevar CFDI relacionados' @@ -268,6 +284,45 @@ function validate_invoice(values){ } } + donativo = $$('chk_cfdi_donativo').getValue() + if(donativo){ + query = table_totals.chain().data() + for(var t of query){ + tax = table_taxes.findOne({'id': t.tax}) + if(tax.tipo != 'E'){ + msg = 'Los donativos deben de ser exentos' + msg_error(msg) + return false + } + } + } + + usar_ine = $$('chk_cfdi_usar_ine').getValue() + if(usar_ine){ + var id_contabilidad = $$('txt_ine_idcontabilidad').getValue().trim() + if(!id_contabilidad){ + $$('tv_invoice').getTabbar().setValue('INE') + msg = 'El ID de contabilidad es requerido si se usa el complemento INE' + msg_error(msg) + return false + } + + if(!id_contabilidad.is_number()){ + $$('tv_invoice').getTabbar().setValue('INE') + msg = 'El ID de contabilidad deben ser solo digitos' + msg_error(msg) + return False + } + + if(id_contabilidad.length != 6){ + $$('tv_invoice').getTabbar().setValue('INE') + msg = 'El ID de contabilidad deben ser 6 digitos' + msg_error(msg) + return False + } + + } + return true } @@ -354,6 +409,7 @@ function save_invoice(data){ if(values.ok){ msg_sucess('Factura guardada correctamente. Enviando a timbrar') update_grid_invoices(values) + gi.select(values.row['id'], false) send_timbrar(values.row['id']) result = true }else{ @@ -419,6 +475,7 @@ function guardar_y_timbrar(values){ for (i = 0; i < rows.length; i++) { delete rows[i]['delete'] delete rows[i]['clave'] + delete rows[i]['clave_sat'] delete rows[i]['unidad'] delete rows[i]['importe'] rows[i]['valor_unitario'] = parseFloat(rows[i]['valor_unitario']) @@ -441,6 +498,17 @@ function guardar_y_timbrar(values){ data['relacionados'] = ids data['tipo_relacion'] = tipo_relacion data['anticipo'] = anticipo + data['donativo'] = $$('chk_cfdi_donativo').getValue() + + var usar_ine = $$('chk_cfdi_usar_ine').getValue() + if(usar_ine){ + var valores = { + TipoProceso: $$('lst_ine_tipo_proceso').getValue(), + TipoComite: $$('lst_ine_tipo_comite').getValue(), + IdContabilidad: $$('txt_ine_idcontabilidad').getValue(), + } + data['ine'] = valores + } if(!save_invoice(data)){ return @@ -450,6 +518,7 @@ function guardar_y_timbrar(values){ tipo_relacion = '' anticipo = false $$('chk_cfdi_anticipo').setValue(0) + $$('chk_cfdi_usar_ine').getValue(0) $$('form_invoice').setValues({id_partner: 0, lbl_partner: 'Ninguno'}) $$('multi_invoices').setValue('invoices_home') @@ -639,7 +708,7 @@ function set_product(values){ values['importe'] = (precio_final * values['cantidad']).round(DECIMALES) grid.updateItem(row.id, values) } - form.setValues({search_product_id:'', search_product_name:''}, true) + form.setValues({search_product_id: '', search_product_name: ''}, true) for(var v of taxes){ var pt = table_pt.findOne(v) @@ -800,7 +869,7 @@ function grid_details_header_click(id){ function cmd_refacturar_click(){ - show('Refacturar') + showvar('Refacturar') } @@ -1006,7 +1075,7 @@ function cmd_invoice_sat_click(){ webix.ajax().get('/values/statussat', {id: row.id}, function(text, data){ var values = data.json() - show(values) + showvar(values) }) } diff --git a/source/static/js/controller/main.js b/source/static/js/controller/main.js index d44948d..68e0815 100644 --- a/source/static/js/controller/main.js +++ b/source/static/js/controller/main.js @@ -1,10 +1,25 @@ var gi = null +function configuracion_inicial(){ + webix.ajax().get('/values/admin', function(text, data){ + var values = data.json() + $$('cmd_ir_al_admin').show(values) + }) +} + + +function cmd_ir_al_admin_click(){ + window.location = '/admin' +} + + var controllers = { init: function(){ //~ Main $$('menu_user').attachEvent('onMenuItemClick', menu_user_click); + configuracion_inicial() + //~ Partner $$('cmd_new_partner').attachEvent('onItemClick', cmd_new_partner_click); $$('cmd_new_contact').attachEvent('onItemClick', cmd_new_contact_click); @@ -170,7 +185,7 @@ function multi_change(prevID, nextID){ if(nextID == 'app_bancos'){ active = $$('multi_bancos').getActiveId() - if(active == 'bancos_home'){ + if(active == 'banco_home'){ get_cuentas_banco() } return diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index ac5ca32..7d53233 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -3,6 +3,7 @@ var RFC_PUBLICO = "XAXX010101000"; var RFC_EXTRANJERO = "XEXX010101000"; var PAIS = "México"; var DECIMALES = 2; +var CLAVE_ANTICIPOS = '84111506'; var db = new loki('data.db'); @@ -32,11 +33,28 @@ var months = [ ] -function show(values){ +function focus(name){ + webix.UIManager.setFocus(name) +} + + +function showvar(values){ webix.message(JSON.stringify(values, null, 2)) } +function show(nombre, value){ + if(value == '0'){ + value = false + } + if(value){ + $$(nombre).show() + }else{ + $$(nombre).hide() + } +} + + function msg_error(msg){ webix.message({type: 'error', text: msg}) } @@ -57,6 +75,16 @@ String.prototype.is_number = function(){ } +String.prototype.to_float = function(){ + return get_float(this) +} + + +function get_float(value){ + return parseFloat(value.replace('$', '').replace(',', '').trim()).round(2) +} + + webix.protoUI({ $cssName: "text", name: "currency", diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index 6a5862d..3ec846f 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -430,6 +430,20 @@ var options_templates = [ {}] +var options_admin_otros = [ + {maxHeight: 15}, + {cols: [{maxWidth: 15}, + {view: 'checkbox', id: 'chk_config_anticipo', labelWidth: 0, + labelRight: 'Ayuda para generar anticipos'}, + {}]}, + {cols: [{maxWidth: 15}, + {view: 'checkbox', id: 'chk_config_ine', labelWidth: 0, + labelRight: 'Mostrar el complemento INE al facturar'}, + {}]}, + {maxHeight: 20}, +{}] + + var tab_options = { view: 'tabview', id: 'tab_options', @@ -440,7 +454,7 @@ var tab_options = { animate: true, cells: [ {id: 'Plantillas', rows: options_templates}, - {id: 'Otros', rows: [{}]}, + {id: 'Otros', rows: options_admin_otros}, {}, ], } diff --git a/source/static/js/ui/bancos.js b/source/static/js/ui/bancos.js index a17157a..5426d9e 100644 --- a/source/static/js/ui/bancos.js +++ b/source/static/js/ui/bancos.js @@ -17,10 +17,17 @@ var toolbar_filtro_cuenta = [ {view: 'daterangepicker', id: 'filtro_cuenta_fechas', label: 'Fechas', labelAlign: 'right', width: 300}, {}, +] + + +var toolbar_movimientos_banco = [ {view: 'button', id: 'cmd_agregar_retiro', label: 'Retiro', type: 'iconButton', autowidth: true, icon: 'minus'}, {view: 'button', id: 'cmd_agregar_deposito', label: 'Depósito', type: 'iconButton', autowidth: true, icon: 'plus'}, + {}, + {view: 'button', id: 'cmd_cancelar_movimiento', label: 'Cancelar', + type: 'iconButton', autowidth: true, icon: 'ban'}, ] @@ -51,16 +58,210 @@ var grid_cuentabanco = { } +var grid_cfdi_por_pagar_cols = [ + {id: 'index', header: '#', adjust: 'data', css: 'right'}, + {id: 'id', header: 'ID', hidden: true}, + {id: 'serie', header: 'Serie', adjust: 'data'}, + {id: 'folio', header: 'Folio', adjust: 'data', css: 'right'}, + {id: 'uuid', header: 'UUID', width: 250, hidden: true}, + {id: 'fecha', header: 'Fecha y Hora', width: 150, sort: 'date'}, + {id: 'tipo_comprobante', header: 'Tipo', adjust: 'data'}, + {id: 'estatus', header: 'Estatus', adjust: 'header'}, + {id: 'cliente', header: ['Razón Social', {content: 'selectFilter'}], + fillspace:true, sort: 'string'}, + {id: 'total', header: ['Total'], width: 125, sort: 'int', + format: webix.i18n.priceFormat, css: 'right'}, + {id: 'saldo', header: ['Saldo'], width: 125, sort: 'int', + format: webix.i18n.priceFormat, css: 'right'}, +] + + +var grid_cfdi_este_deposito_cols = [ + {id: 'index', header: '#', adjust: 'data', css: 'right'}, + {id: 'id', header: 'ID', hidden: true}, + {id: 'serie', header: 'Serie', adjust: 'data'}, + {id: 'folio', header: 'Folio', adjust: 'data', css: 'right'}, + {id: 'uuid', header: 'UUID', width: 250, hidden: true}, + {id: 'fecha', header: 'Fecha y Hora', width: 150, sort: 'date'}, + {id: 'tipo_comprobante', header: 'Tipo', adjust: 'data'}, + {id: 'estatus', header: 'Estatus', adjust: 'header'}, + {id: 'cliente', header: ['Razón Social'], fillspace: true}, + {id: 'total', header: ['Total'], width: 125, sort: 'int', + format: webix.i18n.priceFormat, css: 'right'}, + {id: 'saldo', header: ['Saldo'], width: 125, sort: 'int', + format: webix.i18n.priceFormat, css: 'right'}, + {id: 'importe', header: ['Este pago'], width: 125, sort: 'int', + format: webix.i18n.priceFormat, css: 'right', editor: 'text'}, +] + + +var grid_cfdi_por_pagar = { + view: 'datatable', + id: 'grid_cfdi_por_pagar', + select: 'row', + autoConfig: false, + adjust: true, + height: 250, + resizeColumn: true, + headermenu: true, + drag: true, + columns: grid_cfdi_por_pagar_cols, + on:{ + 'data->onStoreUpdated':function(){ + this.data.each(function(obj, i){ + obj.index = i + 1 + }) + } + }, +} + + +var grid_cfdi_este_deposito = { + view: 'datatable', + id: 'grid_cfdi_este_deposito', + select: 'row', + autoConfig: false, + adjust: true, + height: 200, + resizeColumn: true, + headermenu: true, + drag: true, + editable: true, + columns: grid_cfdi_este_deposito_cols, + on:{ + 'data->onStoreUpdated':function(){ + this.data.each(function(obj, i){ + obj.index = i + 1 + }) + } + }, +} + + +var toolbar_banco_retiro = [ + {view: 'label', label: 'Agregar retiro de banco'}, + {}, + {view: 'button', id: 'cmd_guardar_retiro', label: 'Guardar Retiro', + type: 'iconButton', autowidth: true, icon: 'minus'}, + {view: 'icon', click: '$$("multi_bancos").setValue("banco_home")', + icon: 'times-circle'} +] + + +var toolbar_banco_deposito = [ + {view: 'label', label: 'Agregar depósito de banco'}, + {}, + {view: 'button', id: 'cmd_guardar_deposito', label: 'Guardar Depósito', + type: 'iconButton', autowidth: true, icon: 'plus'}, + {view: 'icon', click: '$$("multi_bancos").setValue("banco_home")', + icon: 'times-circle'} +] + + +var controls_banco_retiro = [ + {view: 'toolbar', elements: toolbar_banco_retiro}, + {cols: [ + {view: 'datepicker', id: 'date_retiro', name: 'retiro_fecha', + label: 'Fecha', format: '%d-%M-%Y', labelAlign: 'right', + required: true, invalidMessage: 'Selecciona una fecha', + labelWidth: 125}, + {view: 'search', id: 'time_retiro', name: 'retiro_hora', + label: 'Hora', icon: 'clock-o', labelAlign: 'right', + pattern:{mask: '##:##:##', allow:/[0-9]/g}, required: true, + invalidMessage: 'Captura una hora'}, + {view: 'text', id: 'retiro_referencia', name: 'retiro_referencia', + label: 'Referencia', labelAlign: 'right'}, + ]}, + {cols: [ + {view: 'richselect', id: 'lst_retiro_forma_pago', + name: 'retiro_forma_pago', label: 'Forma de Pago', required: true, + options: [], labelWidth: 125, labelAlign: 'right'}, + {view: 'currency', type: 'text', id: 'txt_retiro_importe', + name: 'retiro_importe', label: 'Importe', labelAlign: 'right', + required: true, invalidMessage: 'Captura un valor númerico', + inputAlign: 'right', value: ''} + ]}, + {cols: [ + {view: 'text', id: 'retiro_descripcion', name: 'retiro_descripcion', + label: 'Descripción', labelAlign: 'right', required: true, + labelWidth: 125}, + ]}, +] + + +var controls_banco_deposito = [ + {view: 'toolbar', elements: toolbar_banco_deposito}, + {cols: [ + {view: 'datepicker', id: 'date_deposito', name: 'deposito_fecha', + label: 'Fecha', format: '%d-%M-%Y', labelAlign: 'right', + required: true, invalidMessage: 'Selecciona una fecha', + labelWidth: 125}, + {view: 'search', id: 'time_deposito', name: 'deposito_hora', + label: 'Hora', icon: 'clock-o', labelAlign: 'right', + pattern:{mask: '##:##:##', allow:/[0-9]/g}, required: true, + invalidMessage: 'Captura una hora'}, + {view: 'text', id: 'deposito_referencia', name: 'deposito_referencia', + label: 'Referencia', labelAlign: 'right'}, + ]}, + {cols: [ + {view: 'richselect', id: 'lst_deposito_forma_pago', + name: 'deposito_forma_pago', label: 'Forma de Pago', required: true, + options: [], labelWidth: 125, labelAlign: 'right'}, + {view: 'currency', type: 'text', id: 'txt_deposito_importe', + name: 'deposito_importe', label: 'Importe', labelAlign: 'right', + required: true, invalidMessage: 'Captura un valor númerico', + inputAlign: 'right', value: ''} + ]}, + {cols: [ + {view: 'textarea', id: 'deposito_descripcion', label: 'Descripción', + name: 'deposito_descripcion', labelAlign: 'right', required: true, + labelWidth: 125, height: 70}, + ]}, + {view: 'label', label: 'Facturas por pagar: '}, + grid_cfdi_por_pagar, + {view: 'label', label: 'Facturas a pagar en este depósito: '}, + grid_cfdi_este_deposito, +] + + +var form_banco_retiro = { + type: 'space', + responsive: true, + cols: [{ + view: 'form', + id: 'form_banco_retiro', + complexData: true, + scroll: true, + elements: controls_banco_retiro, + }], +} + + +var form_banco_deposito = { + type: 'space', + responsive: true, + cols: [{ + view: 'form', + id: 'form_banco_deposito', + complexData: true, + scroll: true, + elements: controls_banco_deposito, + }], +} + + var multi_bancos = { id: 'multi_bancos', animate: true, cells:[ - {id: 'bancos_home', rows:[ + {id: 'banco_home', rows:[ {view: 'toolbar', elements: toolbar_banco}, {view: 'toolbar', elements: toolbar_filtro_cuenta}, + {view: 'toolbar', elements: toolbar_movimientos_banco}, grid_cuentabanco, - ]} - //~ {id: 'partners_new', rows:[form_partner]} + ]}, + {id: 'banco_retiro', rows: [form_banco_retiro]}, + {id: 'banco_deposito', rows: [form_banco_deposito]} ], } diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index 6fc27f3..5d94855 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -163,8 +163,11 @@ var toolbar_invoices_util = [ var toolbar_invoices_generate = {view: 'toolbar', elements: [{}, {view: 'button', id: 'cmd_cfdi_relacionados', label: 'CFDI Relacionados', type: 'iconButton', autowidth: true, icon: 'file-o'}, - {view: 'checkbox', id: 'chk_cfdi_anticipo', label: 'Es Anticipo', width: 100}, -]} + {view: 'checkbox', id: 'chk_cfdi_anticipo', labelRight: 'Es Anticipo', + labelWidth: 0, width: 100, hidden: true}, + {view: 'checkbox', id: 'chk_cfdi_donativo', labelRight: 'Es Donativo', + labelWidth: 0, width: 100, hidden: true}, +{}]} var toolbar_invoices_filter = [ @@ -225,7 +228,7 @@ var grid_invoices = { obj.index = i + 1 }) } - } + }, } @@ -233,6 +236,7 @@ var grid_details_cols = [ {id: "id", header:"ID", hidden: true}, {id: 'delete', header: '', width: 30, css: 'delete'}, {id: "clave", header:{text: 'Clave', css: 'center'}, width: 100}, + {id: "clave_sat", hidden: true}, {id: "descripcion", header:{text: 'Descripción', css: 'center'}, fillspace: true, editor: 'text'}, {id: "unidad", header:{text: 'Unidad', css: 'center'}, width: 100}, @@ -257,7 +261,7 @@ var grid_details = { autoheight: true, editable: true, columns: grid_details_cols, - data: [] + data: [], } @@ -280,7 +284,7 @@ var grid_totals = { footer: true, autoheight: true, columns: grid_totals_cols, - data: [{id: 1, concepto: 'SubTotal', importe: 0}] + data: [{id: 1, concepto: 'SubTotal', importe: 0}], } @@ -520,14 +524,56 @@ var controls_prefactura = [ ] +var opt_tipo_proceso = [ + {id: 'Ordinario', value: 'Ordinario'}, + {id: 'Precampaña', value: 'Precampaña'}, + {id: 'Campaña', value: 'Campaña'}, +] + + +var opt_tipo_comite = [ + {id: 'Ejecutivo Nacional', value: 'Ejecutivo Nacional'}, + {id: 'Ejecutivo Estatal', value: 'Ejecutivo Estatal'}, + {id: 'Directivo Estatal', value: 'Directivo Estatal'}, +] + + +var controles_ine = [ + {maxHeight: 15}, + {cols: [{maxWidth: 15}, + {view: 'checkbox', id: 'chk_cfdi_usar_ine', labelWidth: 0, + labelRight: 'Usar el complemento INE'}, + {}]}, + {maxHeight: 10}, + {cols: [{maxWidth: 15}, + {view: 'richselect', id: 'lst_ine_tipo_proceso', labelWidth: 150, + label: 'Tipo de Proceso', options: opt_tipo_proceso, + value: 'Ordinario'}, + {}]}, + {maxHeight: 10}, + {cols: [{maxWidth: 15}, + {view: 'richselect', id: 'lst_ine_tipo_comite', labelWidth: 150, + label: 'Tipo de Comite', options: opt_tipo_comite, + value: 'Ejecutivo Nacional'}, + {}]}, + {maxHeight: 10}, + {cols: [{maxWidth: 15}, + {view: 'text', id: 'txt_ine_idcontabilidad', name: 'ine_idcontabilidad', + label: 'ID de Contabilidad: ', labelWidth: 150}, + {}]}, +] + + var controls_invoices = [ { view: 'tabview', id: 'tv_invoice', - tabbar: {options: ['Generar', 'PreFacturas']}, animate: true, + //~ tabbar: {options: ['Generar', 'PreFacturas', 'INE']}, + animate: true, cells: [ {id: 'Generar', rows: controls_generate}, {id: 'PreFacturas', rows: controls_prefactura}, + {id: 'INE', rows: controles_ine}, ] }, ] diff --git a/source/static/js/ui/main.js b/source/static/js/ui/main.js index 2e61ebb..5034328 100644 --- a/source/static/js/ui/main.js +++ b/source/static/js/ui/main.js @@ -67,7 +67,10 @@ var ui_main = { {}, menu_user, {view: 'button', type: 'icon', width: 45, css: 'app_button', - icon: 'bell-o', badge: 0} + icon: 'bell-o', badge: 0}, + {view: 'button', type: 'icon', width: 45, css: 'app_button', + icon: 'cogs', id: 'cmd_ir_al_admin', hidden: true, + click: 'cmd_ir_al_admin_click'} ] }, { diff --git a/source/xslt/cadena.xslt b/source/xslt/cadena.xslt index 2b81c70..be01a88 100644 --- a/source/xslt/cadena.xslt +++ b/source/xslt/cadena.xslt @@ -8,12 +8,12 @@ - + + + + + + + + + + + + diff --git a/source/xslt/implocal.xslt b/source/xslt/implocal.xslt new file mode 100644 index 0000000..dd95023 --- /dev/null +++ b/source/xslt/implocal.xslt @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/xslt/ine11.xslt b/source/xslt/ine11.xslt new file mode 100644 index 0000000..06def84 --- /dev/null +++ b/source/xslt/ine11.xslt @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file