diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 6cca2f6..d863ba9 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -269,6 +269,22 @@ class AppPreInvoices(object): resp.status = falcon.HTTP_204 +class AppTickets(object): + + def __init__(self, db): + self._db = db + + def on_get(self, req, resp): + values = req.params + req.context['result'] = self._db.get_tickets(values) + resp.status = falcon.HTTP_200 + + def on_post(self, req, resp): + values = req.params + req.context['result'] = self._db.tickets(values) + resp.status = falcon.HTTP_200 + + class AppEmisor(object): def __init__(self, db): diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 6077607..fec29c8 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -235,6 +235,10 @@ def now(): return datetime.datetime.now().replace(microsecond=0) +def today(): + return datetime.date.today() + + def get_token(): return _get_hash(uuid.uuid4().hex) diff --git a/source/app/main.py b/source/app/main.py index 7398382..db2f193 100644 --- a/source/app/main.py +++ b/source/app/main.py @@ -16,7 +16,7 @@ from controllers.main import (AppEmpresas, AppLogin, AppLogout, AppAdmin, AppEmisor, AppConfig, AppMain, AppValues, AppPartners, AppProducts, AppInvoices, AppFolios, AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco, - AppMovimientosBanco + AppMovimientosBanco, AppTickets ) @@ -46,11 +46,12 @@ api.add_route('/partners', AppPartners(db)) api.add_route('/products', AppProducts(db)) api.add_route('/invoices', AppInvoices(db)) api.add_route('/preinvoices', AppPreInvoices(db)) +api.add_route('/tickets', AppTickets(db)) api.add_route('/cuentasbanco', AppCuentasBanco(db)) api.add_route('/movbanco', AppMovimientosBanco(db)) -# ~ Activa si usas waitress +# ~ Activa si usas waitress y NO estas usando servidor web # ~ api.add_sink(static, '/static') diff --git a/source/app/models/db.py b/source/app/models/db.py index 303ca61..7a48236 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -77,6 +77,9 @@ class StorageEngine(object): years2 = main.PreFacturas.filter_years() return [years1, years2] + def _get_filteryearsticket(self, values): + return main.Tickets.filter_years() + def _get_cuentayears(self, values): return main.CuentasBanco.get_years() @@ -221,6 +224,9 @@ class StorageEngine(object): def _get_product(self, values): return main.Productos.get_by(values) + def _get_productokey(self, values): + return main.Productos.get_by_key(values) + def get_partners(self, values): return main.Socios.get_(values) @@ -251,6 +257,18 @@ class StorageEngine(object): #~ return main.PreFacturas.actualizar(values, id) return main.PreFacturas.add(values) + def tickets(self, values): + opt = values.pop('opt') + if opt == 'add': + return main.Tickets.add(values) + if opt == 'cancel': + return main.Tickets.cancel(values) + if opt == 'invoice': + return main.Tickets.invoice(values) + + def get_tickets(self, values): + return main.Tickets.get_by(values) + def get_invoices(self, values): return main.Facturas.get_(values) diff --git a/source/app/models/main.py b/source/app/models/main.py index a2b89a4..46a1683 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -110,8 +110,11 @@ def config_main(): except IndexError: obj = None + punto_de_venta = util.get_bool(Configuracion.get_('chk_usar_punto_de_venta')) data = { 'empresa': 'Empresa Libre', + 'punto_de_venta': punto_de_venta + } if not obj is None: titulo = 'Empresa Libre - {}' @@ -126,10 +129,15 @@ def config_timbrar(): except IndexError: return {'cfdi_donativo': False} + mp = not util.get_bool(Configuracion.get_('chk_config_ocultar_metodo_pago')) + cp = not util.get_bool( + Configuracion.get_('chk_config_ocultar_condiciones_pago')) conf = { - 'cfdi_anticipo': Configuracion.get_('chk_config_anticipo'), 'cfdi_donativo': obj.es_ong, + 'cfdi_anticipo': Configuracion.get_('chk_config_anticipo'), 'cfdi_ine': Configuracion.get_('chk_config_ine'), + 'cfdi_metodo_pago': mp, + 'cfdi_condicion_pago': cp, } return conf @@ -153,6 +161,21 @@ class Configuracion(BaseModel): return data[0].valor return '' + if keys['fields'] == 'productos': + fields = ( + 'chk_config_cuenta_predial', + 'chk_config_codigo_barras', + 'chk_config_precio_con_impuestos', + ) + data = (Configuracion + .select() + .where(Configuracion.clave.in_(fields)) + ) + values = {r.clave: r.valor for r in data} + values['default_tax'] = SATImpuestos.select()[0].id + values['default_unidad'] = SATUnidades.get_default() + return values + if keys['fields'] == 'correo': fields = ('correo_servidor', 'correo_puerto', 'correo_ssl', 'correo_usuario', 'correo_contra', 'correo_copia', @@ -180,17 +203,25 @@ class Configuracion(BaseModel): ) elif keys['fields'] == 'configotros': fields = ( + 'chk_config_ocultar_metodo_pago', + 'chk_config_ocultar_condiciones_pago', 'chk_config_anticipo', 'chk_config_cuenta_predial', + 'chk_config_codigo_barras', + 'chk_config_precio_con_impuestos', 'chk_config_ine', + 'chk_usar_punto_de_venta', ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) - elif keys['fields'] == 'productos': + elif keys['fields'] == 'timbrar': fields = ( - 'chk_config_cuenta_predial', + 'chk_config_ocultar_metodo_pago', + 'chk_config_ocultar_condiciones_pago', + 'chk_config_anticipo', + 'chk_config_ine', ) data = (Configuracion .select() @@ -844,6 +875,13 @@ class SATUnidades(BaseModel): return {'ok': result} + @classmethod + def get_default(cls): + obj = SATUnidades.select()[0] + if obj.default: + return obj.id + return 0 + @classmethod def get_activos(cls): rows = (SATUnidades @@ -1752,7 +1790,7 @@ class Socios(BaseModel): fields = cls._clean(cls, values) try: obj = Socios.create(**fields) - except IntegrityError: + except IntegrityError as e: msg = 'Ya existe el RFC y Razón Social' data = {'ok': False, 'row': {}, 'new': True, 'msg': msg} return data @@ -1973,6 +2011,34 @@ class Productos(BaseModel): value += 1 return {'value': value} + @classmethod + def get_by_key(cls, values): + clave = values.get('key', '') + row = (Productos + .select( + Productos.id, + Productos.clave, + Productos.clave_sat, + Productos.descripcion, + SATUnidades.name.alias('unidad'), + Productos.valor_unitario, + Productos.descuento) + .join(SATUnidades).switch(Productos) + .where((Productos.clave==clave) | (Productos.codigo_barras==clave)) + .dicts() + ) + if len(row): + id = row[0]['id'] + model_pt = Productos.impuestos.get_through_model() + taxes = tuple(model_pt + .select( + model_pt.productos_id.alias('product'), + model_pt.satimpuestos_id.alias('tax')) + .where(model_pt.productos_id==id).dicts()) + return {'ok': True, 'row': row[0], 'taxes': taxes} + + return {'ok': False} + @classmethod def get_by(cls, values): clave = values.get('id', '') @@ -2723,17 +2789,9 @@ class Facturas(BaseModel): totals_tax[tax.id] = tax for tax in totals_tax.values(): - # ~ if tax.tipo == 'E' or tax.tipo == 'R': if tax.tipo == 'E': continue - # ~ import_tax = round(float(tax.tasa) * tax.importe, DECIMALES) - # ~ 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 invoice_tax = { 'factura': invoice.id, @@ -2743,26 +2801,6 @@ class Facturas(BaseModel): } FacturasImpuestos.create(**invoice_tax) - # ~ for tax in totals_tax.values(): - # ~ if tax.tipo == 'E' or tax.tipo == 'T': - # ~ continue - # ~ if tax.tasa == round(Decimal(2/3), 6): - # ~ import_tax = round(float(tax.tasa) * total_iva, DECIMALES) - # ~ else: - # ~ import_tax = round(float(tax.tasa) * tax.importe, DECIMALES) - # ~ if tax.key == '000': - # ~ locales_retenciones += import_tax - # ~ else: - # ~ total_retenciones = (total_retenciones or 0) + import_tax - - # ~ invoice_tax = { - # ~ 'factura': invoice.id, - # ~ 'impuesto': tax.id, - # ~ 'base': tax.base, - # ~ 'importe': tax.suma_impuestos, - # ~ } - # ~ FacturasImpuestos.create(**invoice_tax) - total = subtotal - descuento_cfdi + \ (total_trasladados or 0) - (total_retenciones or 0) \ + locales_traslados - locales_retenciones @@ -3834,6 +3872,339 @@ class Tickets(BaseModel): class Meta: order_by = ('fecha',) + def _get_folio(self, serie): + inicio = (Tickets + .select(fn.Max(Tickets.folio).alias('mf')) + .where(Tickets.serie==serie) + .order_by(SQL('mf')) + .scalar()) + + if inicio is None: + inicio = 1 + else: + inicio += 1 + + return inicio + + def _calcular_totales(self, ticket, productos): + subtotal = 0 + descuento_cfdi = 0 + totals_tax = {} + total_trasladados = None + + for producto in productos: + id_producto = producto.pop('id') + p = Productos.get(Productos.id==id_producto) + producto['descripcion'] = p.descripcion + producto['ticket'] = ticket.id + producto['producto'] = id_producto + + cantidad = float(producto['cantidad']) + valor_unitario = float(p.valor_unitario) + descuento = float(producto['descuento']) + precio_final = valor_unitario - descuento + importe = round(cantidad * precio_final, DECIMALES) + + producto['cantidad'] = cantidad + producto['valor_unitario'] = valor_unitario + producto['descuento'] = descuento + producto['precio_final'] = precio_final + producto['importe'] = importe + + descuento_cfdi += descuento + subtotal += importe + + TicketsDetalle.create(**producto) + + base = producto['importe'] + for tax in p.impuestos: + impuesto_producto = round(float(tax.tasa) * base, DECIMALES) + if tax.tipo == 'T' and tax.key != '000': + total_trasladados = (total_trasladados or 0) + impuesto_producto + + if tax.id in totals_tax: + totals_tax[tax.id].base += base + totals_tax[tax.id].importe += impuesto_producto + else: + tax.base = base + tax.importe = impuesto_producto + totals_tax[tax.id] = tax + + + for tax in totals_tax.values(): + if tax.tipo == 'E': + continue + + ticket_tax = { + 'ticket': ticket.id, + 'impuesto': tax.id, + 'base': tax.base, + 'importe': tax.importe, + } + TicketsImpuestos.create(**ticket_tax) + + total = subtotal + (total_trasladados or 0) + data = { + 'subtotal': subtotal + descuento, + 'descuento': descuento_cfdi, + 'total': total, + 'total_trasladados': total_trasladados, + } + return data + + @classmethod + def add(cls, values): + productos = util.loads(values.pop('productos')) + + values['serie'] = 'T' + values['folio'] = cls._get_folio(cls, values['serie']) + + with database_proxy.atomic() as txn: + obj = Tickets.create(**values) + totals = cls._calcular_totales(cls, obj, productos) + obj.subtotal = totals['subtotal'] + obj.descuento = totals['descuento'] + obj.total_trasladados = totals['total_trasladados'] + obj.total = totals['total'] + obj.save() + + row = { + 'id': obj.id, + 'folio': obj.folio, + 'fecha': obj.fecha, + 'estatus': obj.estatus, + 'total': obj.total, + } + data = {'ok': True, 'row': row} + return data + + def _get_folio_invoice(self, serie): + inicio = (Facturas + .select(fn.Max(Facturas.folio).alias('mf')) + .where(Facturas.serie==serie) + .order_by(SQL('mf')) + .scalar()) + + if inicio is None: + inicio = 1 + else: + inicio += 1 + + return inicio + + def _cancel_tickets(self, invoice, tickets): + query = (Tickets + .update(estatus='Facturado', cancelado=True, factura=invoice) + .where(Tickets.id.in_(tickets)) + ) + result = query.execute() + print (result) + return + + def _calculate_totals_invoice(self, invoice, tickets): + subtotal = 0 + descuento_cfdi = 0 + totals_tax = {} + total_trasladados = None + total_retenciones = None + + details = TicketsDetalle.select().where(TicketsDetalle.ticket.in_(tickets)) + + for detail in details: + product = {} + p = detail.producto + product['unidad'] = p.unidad.key + product['clave'] = p.clave + product['clave_sat'] = p.clave_sat + + product['factura'] = invoice.id + product['producto'] = p.id + product['descripcion'] = detail.descripcion + + cantidad = float(detail.cantidad) + valor_unitario = float(detail.valor_unitario) + descuento = float(detail.descuento) + precio_final = valor_unitario - descuento + importe = round(cantidad * precio_final, DECIMALES) + + product['cantidad'] = cantidad + product['valor_unitario'] = valor_unitario + product['descuento'] = round(descuento * cantidad, DECIMALES) + product['precio_final'] = precio_final + product['importe'] = round(cantidad * valor_unitario, DECIMALES) + + descuento_cfdi += product['descuento'] + subtotal += product['importe'] + + FacturasDetalle.create(**product) + + base = product['importe'] - product['descuento'] + for tax in p.impuestos: + impuesto_producto = round(float(tax.tasa) * base, DECIMALES) + if tax.tipo == 'T' and tax.key != '000': + total_trasladados = (total_trasladados or 0) + impuesto_producto + elif tax.tipo == 'R' and tax.key != '000': + total_retenciones = (total_retenciones or 0) + impuesto_producto + elif tax.tipo == 'T' and tax.key == '000': + locales_traslados += impuesto_producto + elif tax.tipo == 'R' and tax.key == '000': + locales_retenciones += impuesto_producto + + if tax.id in totals_tax: + totals_tax[tax.id].base += base + totals_tax[tax.id].suma_impuestos += impuesto_producto + else: + tax.base = base + tax.suma_impuestos = impuesto_producto + totals_tax[tax.id] = tax + + for tax in totals_tax.values(): + if tax.tipo == 'E': + continue + + + invoice_tax = { + 'factura': invoice.id, + 'impuesto': tax.id, + 'base': tax.base, + 'importe': tax.suma_impuestos, + } + FacturasImpuestos.create(**invoice_tax) + + total = subtotal - descuento_cfdi + \ + (total_trasladados or 0) - (total_retenciones or 0) + total_mn = round(total * invoice.tipo_cambio, DECIMALES) + data = { + 'subtotal': subtotal, + 'descuento': descuento_cfdi, + 'total': total, + 'total_mn': total_mn, + 'total_trasladados': total_trasladados, + 'total_retenciones': total_retenciones, + } + return data + + @classmethod + def invoice(cls, values): + is_invoice_day = util.get_bool(values['is_invoice_day']) + id_client = int(values['client']) + tickets = util.loads(values['tickets']) + + if is_invoice_day: + filters = ( + Socios.rfc == 'XAXX010101000' and + Socios.slug == 'publico_en_general') + try: + client = Socios.get(filters) + except Socios.DoesNotExist: + msg = 'No existe el cliente Público en General. Agregalo primero.' + data = {'ok': False, 'msg': msg} + return data + else: + client = Socios.get(Socios.id==id_client) + + emisor = Emisor.select()[0] + data = {} + data['cliente'] = client + data['serie'] = 'T' + data['folio'] = cls._get_folio_invoice(cls, data['serie']) + data['forma_pago'] = client.forma_pago.key + data['tipo_cambio'] = 1.00 + data['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal + if client.uso_cfdi is None: + data['uso_cfdi'] = 'P01' + else: + data['uso_cfdi'] = client.uso_cfdi.key + data['regimen_fiscal'] = emisor.regimenes[0].key + + with database_proxy.atomic() as txn: + obj = Facturas.create(**data) + totals = cls._calculate_totals_invoice(cls, obj, tickets) + 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() + cls._cancel_tickets(cls, obj, tickets) + + msg = 'Factura generada correctamente.

Enviando a timbrar' + data = {'ok': True, 'msg': msg, 'id': obj.id} + return data + + @classmethod + def cancel(cls, values): + id = int(values['id']) + msg = 'Ticket cancelado correctamente' + u = {'cancelado': True, 'estatus': 'Cancelado'} + obj = Tickets.update(**u).where(Tickets.id==id) + result = bool(obj.execute()) + row = {'estatus': 'Cancelado'} + return {'ok': result, 'row': row, 'msg': msg} + + def _get_filters(self, values): + opt = values.get('opt', '') + if not opt: + return + + if opt == 'active': + filters = (Tickets.cancelado==False) + return filters + + if opt == 'today': + t = util.today() + filters = ( + (Tickets.fecha.day == t.day) & + (Tickets.fecha.month == t.month) & + (Tickets.fecha.year == t.year) + ) + return filters + + if opt == 'yearmonth': + if values['year'] == '-1': + fy = (Tickets.fecha.year > 0) + else: + fy = (Tickets.fecha.year == int(values['year'])) + if values['month'] == '-1': + fm = (Tickets.fecha.month > 0) + else: + fm = (Tickets.fecha.month == int(values['month'])) + filters = (fy & fm) + return filters + + return + + @classmethod + def get_by(cls, values): + filters = cls._get_filters(cls, values) + + rows = tuple(Tickets + .select( + Tickets.id, + Tickets.serie, + Tickets.folio, + Tickets.fecha, + Tickets.estatus, + Tickets.total) + .where(filters) + .dicts() + ) + return {'ok': True, 'rows': rows} + + @classmethod + def filter_years(cls): + data = [{'id': -1, 'value': 'Todos'}] + rows = (Tickets + .select(Tickets.fecha.year.alias('year')) + .group_by(Tickets.fecha.year) + .order_by(Tickets.fecha.year) + ) + if not rows is None: + data += [{'id': int(r.year), 'value': int(r.year)} for r in rows] + return tuple(data) + class TicketsDetalle(BaseModel): ticket = ForeignKeyField(Tickets) diff --git a/source/static/css/app.css b/source/static/css/app.css index 1634313..0bd1d39 100644 --- a/source/static/css/app.css +++ b/source/static/css/app.css @@ -24,6 +24,23 @@ font-size: 125%; color: DarkRed; } +.right_footer2 { + text-align: right; + font-weight: bold; + font-size: 150%; + color: DarkRed; +} +.right_footer3 { + text-align: right; + font-weight: bold; + font-size: 120%; + color: DarkBlue; +} +.footer3 { + font-weight: bold; + font-size: 120%; + color: DarkBlue; +} .lbl_partner { font-weight: bold; @@ -37,6 +54,10 @@ color: red; } +.cancel { + color: red; +} + .cmd_close_partner div button { background-color: red !important; diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 52bd83a..4e63fde 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -50,9 +50,14 @@ var controllers = { $$('txt_plantilla_factura_33').attachEvent('onItemClick', txt_plantilla_factura_33_click) $$('txt_plantilla_factura_33j').attachEvent('onItemClick', txt_plantilla_factura_33j_click) $$('txt_plantilla_donataria').attachEvent('onItemClick', txt_plantilla_donataria_click) + $$('chk_config_ocultar_metodo_pago').attachEvent('onItemClick', chk_config_item_click) + $$('chk_config_ocultar_condiciones_pago').attachEvent('onItemClick', chk_config_item_click) $$('chk_config_anticipo').attachEvent('onItemClick', chk_config_item_click) $$('chk_config_ine').attachEvent('onItemClick', chk_config_item_click) $$('chk_config_cuenta_predial').attachEvent('onItemClick', chk_config_item_click) + $$('chk_config_codigo_barras').attachEvent('onItemClick', chk_config_item_click) + $$('chk_config_precio_con_impuestos').attachEvent('onItemClick', chk_config_item_click) + $$('chk_usar_punto_de_venta').attachEvent('onItemClick', chk_config_item_click) $$('cmd_subir_bdfl').attachEvent('onItemClick', cmd_subir_bdfl_click) $$('up_bdfl').attachEvent('onUploadComplete', up_bdfl_upload_complete) diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 2e03ff9..0de74b5 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -41,14 +41,6 @@ function get_series(){ } -function get_forma_pago(){ - webix.ajax().get('/values/formapago', {key: true}, function(text, data){ - var values = data.json() - $$('lst_forma_pago').getList().parse(values) - }) -} - - function get_monedas(){ webix.ajax().get('/values/monedas', function(text, data){ var values = data.json() @@ -105,7 +97,7 @@ function default_config(){ table_taxes.insert(values) }) get_series() - get_forma_pago() + get_forma_pago('lst_forma_pago') get_monedas() get_uso_cfdi() get_regimen_fiscal() @@ -116,8 +108,11 @@ function default_config(){ webix.ajax().sync().get('/values/configtimbrar', function(text, data){ var values = data.json() + //~ showvar(values) show('chk_cfdi_anticipo', values.cfdi_anticipo) show('chk_cfdi_donativo', values.cfdi_donativo) + show('lst_metodo_pago', values.cfdi_metodo_pago) + show('txt_condicion_pago', values.cfdi_condicion_pago) if(!values.cfdi_ine || values.cfdi_ine == '0'){ $$('tv_invoice').getTabbar().hideOption('INE') }else{ diff --git a/source/static/js/controller/main.js b/source/static/js/controller/main.js index 1dfb30f..4d76373 100644 --- a/source/static/js/controller/main.js +++ b/source/static/js/controller/main.js @@ -9,7 +9,16 @@ function configuracion_inicial(){ webix.ajax().get('/values/main', function(text, data){ var values = data.json() $$('lbl_title_main').setValue(values.empresa) + //~ showvar() + if(values.punto_de_venta){ + var node = { + id: 'app_tickets', + icon: 'money', + value: 'Punto de venta'} + $$('main_sidebar').add(node, 4) + } }) + } @@ -40,15 +49,7 @@ var controllers = { $$("es_proveedor").attachEvent( "onChange", is_supplier_change) $$("rfc").attachEvent( "onBlur", rfc_lost_focus) $$('multi').attachEvent('onViewChange', multi_change) - //~ Products - $$("cmd_new_product").attachEvent("onItemClick", cmd_new_product_click) - $$("cmd_edit_product").attachEvent("onItemClick", cmd_edit_product_click) - $$("cmd_delete_product").attachEvent("onItemClick", cmd_delete_product_click) - $$("cmd_save_product").attachEvent("onItemClick", cmd_save_product_click) - $$("cmd_cancel_product").attachEvent("onItemClick", cmd_cancel_product_click) - $$("chk_automatica").attachEvent("onChange", chk_automatica_change) - $$("valor_unitario").attachEvent("onChange", valor_unitario_change) - $$("clave_sat").attachEvent('onSearchIconClick', clave_sat_icon_click) + //~ Invoices $$('cmd_new_invoice').attachEvent("onItemClick", cmd_new_invoice_click) $$('cmd_refacturar').attachEvent("onItemClick", cmd_refacturar_click) @@ -86,7 +87,9 @@ var controllers = { webix.extend($$('grid_invoices'), webix.ProgressBar) + products_controllers.init() bancos_controllers.init() + tickets_controllers.init() } } @@ -195,6 +198,14 @@ function multi_change(prevID, nextID){ return } + if(nextID == 'app_tickets'){ + active = $$('multi_tickets').getActiveId() + if(active == 'tickets_home'){ + configuracion_inicial_ticket() + } + return + } + if(nextID == 'app_invoices'){ active = $$('multi_invoices').getActiveId() if(active == 'invoices_home'){ diff --git a/source/static/js/controller/partners.js b/source/static/js/controller/partners.js index 93b4346..74185e4 100644 --- a/source/static/js/controller/partners.js +++ b/source/static/js/controller/partners.js @@ -155,7 +155,7 @@ function cmd_save_partner_click(id, e, node){ if (values.ok) { update_grid_partner(values) } else { - msg_error(msg) + msg_error(values.msg) } } }) diff --git a/source/static/js/controller/products.js b/source/static/js/controller/products.js index 0db84e9..62a30e5 100644 --- a/source/static/js/controller/products.js +++ b/source/static/js/controller/products.js @@ -1,5 +1,21 @@ +var products_controllers = { + init: function(){ + $$('cmd_new_product').attachEvent('onItemClick', cmd_new_product_click) + $$("cmd_edit_product").attachEvent("onItemClick", cmd_edit_product_click) + $$("cmd_delete_product").attachEvent("onItemClick", cmd_delete_product_click) + $$("cmd_save_product").attachEvent("onItemClick", cmd_save_product_click) + $$("cmd_cancel_product").attachEvent("onItemClick", cmd_cancel_product_click) + $$("chk_automatica").attachEvent("onChange", chk_automatica_change) + $$("valor_unitario").attachEvent("onChange", valor_unitario_change) + //~ $$("clave_sat").attachEvent('onSearchIconClick', clave_sat_icon_click) + $$('precio_con_impuestos').attachEvent('onChange', precio_con_impuestos_change) + $$('precio_con_impuestos').attachEvent('onTimedKeyPress', precio_con_impuestos_key_up); + } +} + + function configurar_productos(){ webix.ajax().get('/config', {'fields': 'productos'}, { error: function(text, data, xhr) { @@ -10,6 +26,10 @@ function configurar_productos(){ var values = data.json() //~ showvar(values) show('cuenta_predial', values.chk_config_cuenta_predial) + show('codigo_barras', values.chk_config_codigo_barras) + show('precio_con_impuestos', values.chk_config_precio_con_impuestos) + $$('unidad').setValue(values.default_unidad) + $$('grid_product_taxes').select(values.default_tax) } }) } @@ -228,6 +248,49 @@ function valor_unitario_change(new_value, old_value){ } -function clave_sat_icon_click(){ - show('Buscar SAT') +function precio_con_impuestos_change(new_value, old_value){ + if(!isFinite(new_value)){ + this.config.value = old_value + this.refresh() + } } + + +//~ function clave_sat_icon_click(){ + //~ show('Buscar SAT') +//~ } + +function calcular_sin_impuestos(value, taxes){ + var vu = $$('valor_unitario') + var precio = value + + taxes.forEach(function(tax){ + var tasa = 1.00 + tax.tasa.to_float() + if(tax.tipo == 'T' && tax.name == 'IVA'){ + precio = (value / tasa).round(DECIMALES) + } + }) + vu.setValue(precio) +} + +function precio_con_impuestos_key_up(){ + var value = this.getValue() + + if(!value){ + return + } + + var taxes = $$('grid_product_taxes').getSelectedItem(true) + if (taxes.length == 0){ + msg = 'Selecciona al menos un impuesto' + msg_error(msg) + return + } + + if(!isFinite(value)){ + msg = 'Captura un valor válido' + msg_error(msg) + return + } + calcular_sin_impuestos(parseFloat(value), taxes) +} \ No newline at end of file diff --git a/source/static/js/controller/tickets.js b/source/static/js/controller/tickets.js new file mode 100644 index 0000000..741b5f1 --- /dev/null +++ b/source/static/js/controller/tickets.js @@ -0,0 +1,617 @@ +var query = [] +var msg = '' + + +var tickets_controllers = { + init: function(){ + $$('cmd_nuevo_ticket').attachEvent('onItemClick', cmd_nuevo_ticket_click) + $$('cmd_ticket_to_invoice').attachEvent('onItemClick', cmd_ticket_to_invoice_click) + $$('cmd_generar_ticket').attachEvent('onItemClick', cmd_generar_ticket_click) + $$('cmd_cerrar_ticket').attachEvent('onItemClick', cmd_cerrar_ticket_click) + $$('cmd_new_invoice_from_ticket').attachEvent('onItemClick', cmd_new_invoice_from_ticket_click) + $$('cmd_close_ticket_invoice').attachEvent('onItemClick', cmd_cerrar_ticket_click) + $$('cmd_cancelar_ticket').attachEvent('onItemClick', cmd_cancelar_ticket_click) + $$('cmd_move_tickets_right').attachEvent('onItemClick', cmd_move_tickets_right_click) + $$('cmd_move_tickets_left').attachEvent('onItemClick', cmd_move_tickets_left_click) + $$('tsearch_product_key').attachEvent('onKeyPress', tsearch_product_key_press) + $$('grid_tdetails').attachEvent('onItemClick', grid_ticket_details_click) + $$('grid_tdetails').attachEvent('onBeforeEditStop', grid_tickets_details_before_edit_stop) + $$('gt_productos_found').attachEvent('onValueSuggest', gt_productos_found_click) + $$('filter_year_ticket').attachEvent('onChange', filter_year_ticket_change) + $$('filter_month_ticket').attachEvent('onChange', filter_month_ticket_change) + $$('chk_is_invoice_day').attachEvent('onChange', chk_is_invoice_day_change) + $$('grid_tickets_active').attachEvent('onItemDblClick', grid_tickets_active_double_click) + $$('grid_tickets_invoice').attachEvent('onItemDblClick', grid_tickets_invoice_double_click) + $$('tsearch_client_key').attachEvent('onKeyPress', tsearch_client_key_press) + $$('grid_ticket_clients_found').attachEvent('onValueSuggest', grid_ticket_clients_found_click) + + webix.extend($$('grid_tickets'), webix.ProgressBar) + webix.extend($$('grid_tickets_active'), webix.ProgressBar) + } +} + + +function current_dates_tickets(){ + var fy = $$('filter_year_ticket') + var fm = $$('filter_month_ticket') + var d = new Date() + + fy.blockEvent() + fm.blockEvent() + + fm.setValue(d.getMonth() + 1) + webix.ajax().sync().get('/values/filteryearsticket', function(text, data){ + var values = data.json() + fy.getList().parse(values) + fy.setValue(d.getFullYear()) + }) + + fy.unblockEvent() + fm.unblockEvent() +} + + +function get_tickets(filters){ + if(filters == undefined){ + filters = {'opt': 'today'} + } + + var grid = $$('grid_tickets') + grid.showProgress({type: 'icon'}) + + webix.ajax().get('/tickets', filters, { + error: function(text, data, xhr) { + msg_error('Error al consultar') + }, + success: function(text, data, xhr) { + var values = data.json(); + grid.clearAll(); + if (values.ok){ + grid.parse(values.rows, 'json') + } + } + }) +} + + +function filter_year_ticket_change(nv, ov){ + var fm = $$('filter_month_ticket') + filters = {'opt': 'yearmonth','year': nv, 'month': fm.getValue()} + get_tickets(filters) +} + + +function filter_month_ticket_change(nv, ov){ + var fy = $$('filter_year_ticket') + filters = {'opt': 'yearmonth','year': fy.getValue(), 'month': nv} + get_tickets(filters) +} + + +function configuracion_inicial_ticket(){ + current_dates_tickets() + get_tickets() +} + + +function get_active_tickets(grid){ + filters = {'opt': 'active'} + grid.showProgress({type: 'icon'}) + + webix.ajax().get('/tickets', filters, { + error: function(text, data, xhr) { + msg_error('Error al consultar') + }, + success: function(text, data, xhr) { + var values = data.json(); + grid.clearAll(); + if (values.ok){ + grid.parse(values.rows, 'json') + } + } + }) +} + + +function configuracion_inicial_ticket_to_invoice(){ + var grid = $$('grid_tickets_active') + var gridt = $$('grid_tickets_invoice') + var form = $$('form_ticket_invoice') + + get_active_tickets(grid) + form.setValues({id_partner: 0, lbl_tclient: 'Ninguno'}) + gridt.attachEvent('onAfterAdd', function(id, index){ + gridt.adjustColumn('index') + gridt.adjustColumn('folio', 'all') + gridt.adjustColumn('fecha', 'all') + }); + gridt.clearAll() +} + + +function configuracion_inicial_nuevo_ticket(){ + var grid = $$('grid_tdetails') + + webix.ajax().sync().get('/values/taxes', function(text, data){ + var values = data.json() + table_taxes.clear() + table_taxes.insert(values) + }) + get_forma_pago('lst_ticket_forma_pago') + grid.clearAll() + table_pt.clear() + table_totals.clear() +} + + +function cmd_nuevo_ticket_click(){ + configuracion_inicial_nuevo_ticket() + $$('multi_tickets').setValue('tickets_new') +} + + +function cmd_ticket_to_invoice_click(){ + configuracion_inicial_ticket_to_invoice() + $$('multi_tickets').setValue('tickets_invoice') +} + + +function validar_ticket(){ + var grid = $$('grid_tdetails') + + if(!grid.count()){ + webix.UIManager.setFocus('tsearch_product_key') + msg = 'Agrega al menos un producto' + msg_error(msg) + return false + } + + return true +} + + +function guardar_ticket(values){ + var gd = $$('grid_tdetails') + var grid = $$('grid_tickets') + + var rows = gd.data.getRange() + 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']) + rows[i]['descuento'] = parseFloat(rows[i]['descuento']) + } + + var data = new Object() + data['opt'] = 'add' + data['productos'] = rows + data['forma_pago'] = values.forma_pago + + webix.ajax().sync().post('tickets', 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){ + msg_ok('Ticket generado correctamente') + $$('form_new_ticket').setValues({}) + gd.clearAll() + grid.add(values.row) + $$('multi_tickets').setValue('tickets_home') + }else{ + msg_error(values.msg) + } + } + }) + +} + + +function cmd_generar_ticket_click(){ + var form = this.getFormView(); + + if(!form.validate()) { + msg_error('Valores inválidos') + return + } + + var values = form.getValues() + if(!validar_ticket()){ + return + } + + msg = '¿Todos los datos son correctos?

' + msg += '¿Estás seguro de generar este Ticket?' + + webix.confirm({ + title: 'Generar Ticket', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if(result){ + guardar_ticket(values) + } + } + }) +} + + +function cmd_cerrar_ticket_click(){ + $$('multi_tickets').setValue('tickets_home') +} + + +function calcular_precio_con_impuestos(precio, taxes){ + var precio_final = precio + + for(var tax of taxes){ + impuesto = table_taxes.findOne({'id': tax.tax}) + if(impuesto.tipo == 'E'){ + continue + } + var base = precio + if(impuesto.tipo == 'R'){ + base = (precio * -1).round(DECIMALES) + } + precio_final += (impuesto.tasa * base).round(DECIMALES) + } + + return precio_final +} + + +function agregar_producto(values){ + var taxes = values.taxes + var producto = values.row + var form = $$('form_new_ticket') + var grid = $$('grid_tdetails') + var row = grid.getItem(producto.id) + var precio_final = 0.0 + + if(row == undefined){ + producto['cantidad'] = 1 + producto['valor_unitario'] = calcular_precio_con_impuestos( + parseFloat(producto['valor_unitario']), taxes) + producto['importe'] = producto['valor_unitario'] + grid.add(producto) + }else{ + producto['cantidad'] = parseFloat(row.cantidad) + 1 + producto['descuento'] = parseFloat(row.descuento) + producto['valor_unitario'] = parseFloat(row.valor_unitario) + precio_final = producto['valor_unitario'] - producto['descuento'] + producto['importe'] = (precio_final * producto['cantidad']).round(DECIMALES) + grid.updateItem(row.id, producto) + } + form.setValues({tsearch_product_key: '', tsearch_product_name: ''}, true) +} + + +function buscar_producto_key(key){ + webix.ajax().get('/values/productokey', {'key': key}, { + error: function(text, data, xhr) { + msg_error('Error al consultar') + }, + success: function(text, data, xhr){ + var values = data.json() + if (values.ok){ + agregar_producto(values) + } else { + msg = 'No se encontró la clave

' + key + msg_error(msg) + } + } + }) + +} + + +function tsearch_product_key_press(code, e){ + var value = this.getValue() + if(code == 13 && value.trim().length > 0){ + buscar_producto_key(value.trim()) + } +} + + + +function grid_ticket_details_click(id, e, node){ + if(id.column != 'delete'){ + return + } + var grid = $$('grid_tdetails') + grid.remove(id.row) +} + + +function grid_tickets_details_before_edit_stop(state, editor){ + var grid = $$('grid_tdetails') + var row = grid.getItem(editor.row) + + if(editor.column == 'cantidad'){ + var cantidad = parseFloat(state.value) + if(isNaN(cantidad)){ + msg = 'La cantidad debe ser un número' + msg_error(msg) + grid.blockEvent() + state.value = state.old + grid.editCancel() + grid.unblockEvent() + return true + } + var valor_unitario = parseFloat(row['valor_unitario']) + var descuento = parseFloat(row['descuento']) + } + + if(editor.column == 'valor_unitario'){ + var valor_unitario = parseFloat(state.value) + if(isNaN(valor_unitario)){ + msg = 'El valor unitario debe ser un número' + msg_error(msg) + grid.blockEvent() + state.value = state.old + grid.editCancel() + grid.unblockEvent() + return true + } + var cantidad = parseFloat(row['cantidad']) + var descuento = parseFloat(row['descuento']) + } + + if(editor.column == 'descuento'){ + var descuento = parseFloat(state.value) + if(isNaN(descuento)){ + msg = 'El descuento debe ser un número' + msg_error(msg) + grid.blockEvent() + state.value = state.old + grid.editCancel() + grid.unblockEvent() + return true + } + var cantidad = parseFloat(row['cantidad']) + var valor_unitario = parseFloat(row['valor_unitario']) + } + + var precio_final = valor_unitario - descuento + row['importe'] = (cantidad * precio_final).round(DECIMALES) + + grid.refresh() +} + + +function gt_productos_found_click(obj){ + buscar_producto_key(obj.clave) +} + + +function cancel_ticket(id){ + var grid = $$('grid_tickets') + var data = new Object() + data['opt'] = 'cancel' + data['id'] = id + + webix.ajax().sync().post('tickets', 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) + } + } + }) +} + + +function cmd_cancelar_ticket_click(){ + var grid = $$('grid_tickets') + + if(grid.count() == 0){ + return + } + + var row = grid.getSelectedItem() + if (row == undefined){ + msg_error('Selecciona un ticket') + return + } + + msg = '¿Estás seguro de cancelar el siguiente Ticket?

' + msg += 'Folio: ' + row['folio'] + msg += '

ESTA ACCIÓN NO SE PUEDE DESHACER' + webix.confirm({ + title:'Cancelar Ticket', + ok:'Si', + cancel:'No', + type:'confirm-error', + text:msg, + callback:function(result){ + if (result){ + cancel_ticket(row['id']) + } + } + }) + +} + + +function chk_is_invoice_day_change(new_value, old_value){ + var value = Boolean(new_value) + show('fs_ticket_search_client', !value) +} + + +function send_timbrar_invoice(id){ + webix.ajax().get('/values/timbrar', {id: id}, function(text, data){ + var values = data.json() + if(values.ok){ + msg_ok(values.msg) + }else{ + webix.alert({ + title: 'Error al Timbrar', + text: values.msg, + type: 'alert-error' + }) + } + }) + +} + + +function save_ticket_to_invoice(data){ + webix.ajax().sync().post('tickets', 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){ + msg_ok(values.msg) + send_timbrar_invoice(values.id) + $$('multi_tickets').setValue('tickets_home') + }else{ + msg_error(values.msg) + } + } + }) +} + + +function cmd_new_invoice_from_ticket_click(){ + var form = this.getFormView(); + var chk = $$('chk_is_invoice_day') + var grid = $$('grid_tickets_invoice') + var values = form.getValues() + var tickets = [] + + if(!chk.getValue()){ + if(values.id_partner == 0){ + webix.UIManager.setFocus('tsearch_client_name') + msg = 'Selecciona un cliente' + msg_error(msg) + return false + } + } + + if(!grid.count()){ + msg = 'Agrega al menos un ticket a facturar' + msg_error(msg) + return false + } + + grid.eachRow(function(row){ + tickets.push(row) + }) + + var data = new Object() + data['client'] = values.id_partner + data['tickets'] = tickets + data['is_invoice_day'] = chk.getValue() + data['opt'] = 'invoice' + + msg = 'Todos los datos son correctos.

¿Estás seguro de generar esta factura?' + webix.confirm({ + title: 'Generar Factura', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if(result){ + save_ticket_to_invoice(data) + } + } + }) +} + + +function grid_tickets_active_double_click(id, e, node){ + this.move(id.row, -1, $$('grid_tickets_invoice')) +} + + +function grid_tickets_invoice_double_click(id, e, node){ + this.move(id.row, -1, $$('grid_tickets_active')) +} + + +function cmd_move_tickets_right_click(){ + $$('grid_tickets_active').eachRow( + function(row){ + this.copy(row, -1, $$('grid_tickets_invoice')) + } + ) + $$('grid_tickets_active').clearAll() +} + + +function cmd_move_tickets_left_click(){ + $$('grid_tickets_invoice').eachRow( + function(row){ + this.copy(row, -1, $$('grid_tickets_active')) + } + ) + $$('grid_tickets_invoice').clearAll() +} + + +function ticket_set_client(row){ + var form = $$('form_ticket_invoice') + var html = '' + form.setValues({ + id_partner: row.id, + tsearch_client_key: '', + tsearch_client_name: ''}, true) + html += row.nombre + ' (' + row.rfc + ')' + $$('lbl_tclient').setValue(html) +} + + +function ticket_search_client_by_id(id){ + webix.ajax().get('/values/client', {'id': id}, { + error: function(text, data, xhr) { + msg_error('Error al consultar') + }, + success: function(text, data, xhr){ + var values = data.json() + if (values.ok){ + ticket_set_client(values.row) + }else{ + msg = 'No se encontró un cliente con la clave: ' + id + msg_error(msg) + } + } + }) + +} + + +function tsearch_client_key_press(code, e){ + var value = this.getValue() + if(code == 13 && value.length > 0){ + var id = parseInt(value, 10) + if (isNaN(id)){ + msg_error('Captura una clave válida') + }else{ + ticket_search_client_by_id(id) + } + } +} + + +function grid_ticket_clients_found_click(obj){ + ticket_set_client(obj) +} \ No newline at end of file diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index 8e8c4f1..0beb469 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -33,6 +33,18 @@ var months = [ ] +function get_icon(tipo){ + icons = { + xml: 'fa-file-code-o', + pdf: 'fa-file-pdf-o', + zip: 'fa-file-zip-o', + email: 'fa-envelope-o', + print: 'fa-print', + } + return "" +} + + function focus(name){ webix.UIManager.setFocus(name) } @@ -43,6 +55,11 @@ function showvar(values){ } +function showtype(values){ + webix.message(typeof(values)) +} + + function show(nombre, value){ if(value == '0'){ value = false @@ -84,7 +101,7 @@ String.prototype.to_float = function(){ function get_float(value){ - return parseFloat(value.replace('$', '').replace(',', '').trim()).round(2) + return parseFloat(value.replace('$', '').replace(',', '').trim()).round(DECIMALES) } @@ -112,12 +129,17 @@ webix.protoUI({ }, webix.ui.text) -webix.ui.datafilter.rowCount = webix.extend({ - refresh:function(master, node, value){ - node.firstChild.innerHTML = master.count(); - } -}, webix.ui.datafilter.summColumn) +//~ webix.ui.datafilter.rowCount = webix.extend({ + //~ refresh:function(master, node, value){ + //~ node.firstChild.innerHTML = master.count(); + //~ } +//~ }, webix.ui.datafilter.summColumn) +webix.ui.datafilter.countRows = webix.extend({ + refresh:function(master, node, value){ + node.firstChild.innerHTML = master.count(); + } +}, webix.ui.datafilter.summColumn); function validate_rfc(value){ rfc = value.trim().toUpperCase(); @@ -279,3 +301,11 @@ webix.DataDriver.plainjs = webix.extend({ return this.hash2tree(hash, 0); } }, webix.DataDriver.json) + + +function get_forma_pago(control){ + webix.ajax().get('/values/formapago', {key: true}, function(text, data){ + var values = data.json() + $$(control).getList().parse(values) + }) +} \ No newline at end of file diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index 3cd09a1..d60e456 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -426,7 +426,7 @@ var controls_correo = [ var form_folios = { type: 'space', - responsive: true, + //~ responsive: true, cols: [{ view: 'form', id: 'form_folios', @@ -451,12 +451,13 @@ var form_correo = { view: 'form', id: 'form_correo', complexData: true, + scroll: true, elements: controls_correo, elementsConfig: { labelWidth: 150, labelAlign: 'right' }, - autoheight: true + //~ autoheight: true }], } @@ -486,6 +487,14 @@ var options_templates = [ var options_admin_otros = [ {maxHeight: 15}, + {template: 'Facturación', type: 'section'}, + {cols: [{maxWidth: 15}, + {view: 'checkbox', id: 'chk_config_ocultar_metodo_pago', labelWidth: 0, + labelRight: 'Ocultar método de pago'}, + {view: 'checkbox', id: 'chk_config_ocultar_condiciones_pago', labelWidth: 0, + labelRight: 'Ocultar condiciones de pago'}, + {}]}, + {maxHeight: 20}, {template: 'Ayudas varias', type: 'section'}, {cols: [{maxWidth: 15}, {view: 'checkbox', id: 'chk_config_anticipo', labelWidth: 0, @@ -496,7 +505,11 @@ var options_admin_otros = [ {cols: [{maxWidth: 15}, {view: 'checkbox', id: 'chk_config_cuenta_predial', labelWidth: 0, labelRight: 'Mostrar cuenta predial'}, - {}]}, + {view: 'checkbox', id: 'chk_config_codigo_barras', labelWidth: 0, + labelRight: 'Mostrar código de barras'}, + {view: 'checkbox', id: 'chk_config_precio_con_impuestos', labelWidth: 0, + labelRight: 'Mostrar precio con impuestos'}, + ]}, {maxHeight: 20}, {template: 'Complementos', type: 'section'}, {cols: [{maxWidth: 15}, @@ -504,13 +517,24 @@ var options_admin_otros = [ labelRight: 'Mostrar el complemento INE al facturar'}, {}]}, {maxHeight: 20}, + {template: 'Punto de venta', type: 'section'}, + {cols: [{maxWidth: 15}, + {view: 'checkbox', id: 'chk_usar_punto_de_venta', labelWidth: 0, + labelRight: 'Usar punto de venta'}, + {}]}, + + {maxHeight: 20}, {}] + +var body_admin_otros = { + view: 'scrollview', body: {rows: options_admin_otros}, +} + var tab_options = { view: 'tabview', id: 'tab_options', - multiview: true, animate: true, cells: [ {id: 'Plantillas', rows: options_templates}, @@ -696,8 +720,8 @@ var suggest_sat_moneda = { name: 'grid_moneda_found', body: { autoConfig: false, - scroll:true, - autoheight:false, + scroll: true, + autoheight: false, header: true, yCount: 10, columns: [ @@ -935,7 +959,6 @@ var app_correo = { {view: 'template', id: 'th_correo', type: 'header', template: 'Configuración de correo'}, form_correo, - {}, ], } diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index 532fe7f..7d1327a 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -2,7 +2,7 @@ var grid_cfdi_cliente_cols = [ {id: 'index', header: '#', adjust: 'data', css: 'right', - footer: {content: 'rowCount', colspan: 3, css: 'right'}}, + footer: {content: 'countRows', colspan: 3, css: 'right'}}, {id: "id", header:"ID", hidden:true}, {id: "serie", header: ["Serie", {content: "selectFilter"}], adjust: "header", sort:"string"}, @@ -180,20 +180,9 @@ var toolbar_invoices_filter = [ ] -function get_icon(tipo){ - icons = { - xml: 'fa-file-code-o', - pdf: 'fa-file-pdf-o', - zip: 'fa-file-zip-o', - email: 'fa-envelope-o', - } - return "" -} - - var grid_invoices_cols = [ {id: 'index', header: '#', adjust: 'data', css: 'right', - footer: {content: 'rowCount', colspan: 3, css: 'right'}}, + footer: {content: 'countRows', colspan: 3, css: 'right'}}, {id: "id", header:"ID", hidden:true}, {id: "serie", header: ["Serie", {content: "selectFilter"}], adjust: "data", sort:"string"}, @@ -227,6 +216,13 @@ var grid_invoices = { resizeColumn: true, headermenu: true, columns: grid_invoices_cols, + scheme:{ + $change:function(item){ + if (item.estatus == 'Cancelada'){ + item.$css = 'cancel' + } + } + }, on:{ 'data->onStoreUpdated':function(){ this.data.each(function(obj, i){ @@ -317,6 +313,7 @@ var suggest_partners = { } } + var suggest_products = { view: 'gridsuggest', id: 'grid_products_found', diff --git a/source/static/js/ui/main.js b/source/static/js/ui/main.js index d004c18..cd6bdaf 100644 --- a/source/static/js/ui/main.js +++ b/source/static/js/ui/main.js @@ -5,12 +5,13 @@ var menu_data = [ {id: 'app_partners', icon: 'users', value: 'Clientes y Proveedores'}, {id: 'app_products', icon: 'server', value: 'Productos y Servicios'}, {id: 'app_bancos', icon: 'university', value: 'Bancos'}, - {id: 'app_invoices', icon: 'cart-plus', value: 'Facturas'}, + {id: 'app_invoices', icon: 'file-code-o', value: 'Facturas'}, ] var sidebar = { view: 'sidebar', + id: 'main_sidebar', data: menu_data, ready: function(){ this.select('app_home'); @@ -36,6 +37,7 @@ var multi_main = { app_partners, app_products, app_bancos, + app_tickets, app_invoices, ], } @@ -60,7 +62,7 @@ var ui_main = { {view: 'toolbar', padding: 3, elements: [ {view: 'button', type: 'icon', icon: 'bars', width: 37, align: 'left', css: 'app_button', click: function(){ - $$('$sidebar1').toggle() + $$('main_sidebar').toggle() } }, {view: 'label', id: 'lbl_title_main', label: 'Empresa Libre'}, diff --git a/source/static/js/ui/partners.js b/source/static/js/ui/partners.js index 4b763fe..bc082c7 100644 --- a/source/static/js/ui/partners.js +++ b/source/static/js/ui/partners.js @@ -12,7 +12,7 @@ var toolbar_partners = [ var grid_partners_cols = [ {id: 'index', header:'#', css: 'right', - footer: {content: 'rowCount', colspan: 2, css: 'right'}}, + footer: {content: 'countRows', colspan: 2, css: 'right'}}, {id: 'id', header: 'Clave', sort: 'int', css: 'right'}, {id: 'rfc', header: ['RFC', {content: 'textFilter'}], adjust: 'data', sort: 'string', footer: {text: 'Clientes y Proveedores', colspan: 2}}, @@ -143,7 +143,7 @@ var toolbar_contacts = [ var grid_contacts_cols = [ {id: 'index', header: '#', adjust:'data', css:'right', - footer: {content: 'rowCount'}}, + footer: {content: 'countRows'}}, {id: 'id', header: '', hidden: true}, {id: 'title', header: 'Título', adjust:'data', sort: 'string', footer: 'Contactos'}, diff --git a/source/static/js/ui/products.js b/source/static/js/ui/products.js index d5d87d4..2296f7c 100644 --- a/source/static/js/ui/products.js +++ b/source/static/js/ui/products.js @@ -13,7 +13,7 @@ var toolbar_products = [ var grid_products_cols = [ { id: "id", header: "ID", width: 75, hidden: true}, { id: "clave", header: ["Clave", {content: "textFilter"}], width: 100, - sort: 'string', footer: {content: 'rowCount', css: 'right'}}, + sort: 'string', footer: {content: 'countRows', css: 'right'}}, { id: "descripcion", header: ["Descripción", {content: "textFilter"}], fillspace:true, sort: 'string', footer: 'Productos y Servicios'}, { id: "unidad", header: ["Unidad", {content: "selectFilter"}], width: 150, @@ -123,10 +123,16 @@ var controls_generals = [ labelAlign: 'right', label: 'Etiquetas', placeholder: 'Separadas por comas'} ]}, - {cols: [{view: "currency", type: "text", id: "valor_unitario", - name: "valor_unitario", label: "Valor Unitario", width: 300, - labelWidth: 130, labelAlign: "right", required: true, - invalidMessage: "Captura un valor númerico", inputAlign: "right" },{}]}, + {cols: [ + {view: "currency", type: "text", id: "valor_unitario", + name: "valor_unitario", label: "Valor Unitario", width: 300, + labelWidth: 130, labelAlign: "right", required: true, + invalidMessage: "Captura un valor númerico", inputAlign: "right"}, + {view: 'currency', type: 'text', id: 'precio_con_impuestos', + name: 'precio_con_impuestos', label: 'Con Impuestos', width: 300, + labelWidth: 115, labelAlign: 'right', required: false, + invalidMessage: 'Captura un valor númerico', inputAlign: 'right'}, + {},]}, {cols: [ {view: 'checkbox', id: 'inventario', name: 'inventario', hidden: true, label: 'Inventario', labelAlign: 'right', labelWidth: 130}, diff --git a/source/static/js/ui/tickets.js b/source/static/js/ui/tickets.js new file mode 100644 index 0000000..609a847 --- /dev/null +++ b/source/static/js/ui/tickets.js @@ -0,0 +1,421 @@ + + +var toolbar_tickets = [ + {view: 'button', id: 'cmd_nuevo_ticket', label: 'Nuevo', type: 'iconButton', + autowidth: true, icon: 'plus'}, + {view: 'button', id: 'cmd_ticket_to_invoice', label: 'Facturar', + type: 'iconButton', autowidth: true, icon: 'file-code-o'}, + {}, + {view: 'button', id: 'cmd_cancelar_ticket', label: 'Cancelar', + type: 'iconButton', autowidth: true, icon: 'ban'}, +] + + +var toolbar_tickets_filter = [ + {view: 'richselect', id: 'filter_year_ticket', label: 'Año', + labelAlign: 'right', labelWidth: 50, width: 150, options: []}, + {view: 'richselect', id: 'filter_month_ticket', label: 'Mes', + labelAlign: 'right', labelWidth: 50, width: 200, options: months}, + {view: 'daterangepicker', id: 'filter_dates_ticket', label: 'Fechas', + labelAlign: 'right', width: 300}, +] + + +var grid_tickets_cols = [ + {id: 'index', header: '#', adjust: 'data', css: 'right', + footer: {content: 'countRows', colspan: 3, css: 'right'}}, + {id: "id", header:"ID", hidden:true}, + {id: "serie", header: ["Serie", {content: "selectFilter"}], adjust: "data", + sort:"string", hidden: true}, + {id: 'folio', header: ['Folio', {content: 'numberFilter'}], adjust: 'header', + sort: 'int', css: 'right', footer: {text: 'Tickets', colspan: 3}}, + {id: "fecha", header: ["Fecha y Hora"], + adjust: "data", sort: "date"}, + {id: "estatus", header: ["Estatus", {content: "selectFilter"}], + adjust: "data", sort:"string"}, + {id: 'total', header: ['Total', {content: 'numberFilter'}], width: 150, + sort: 'int', format: webix.i18n.priceFormat, css: 'right'}, + {id: "cliente", header: ["Razón Social", {content: "selectFilter"}], + fillspace:true, sort:"string", hidden: true}, + {id: 'pdf', header: 'PDF', adjust: 'data', template: get_icon('pdf')}, + {id: 'print', header: 'I', adjust: 'data', template: get_icon('print')}, +] + + +var grid_tickets = { + view: 'datatable', + id: 'grid_tickets', + select: 'row', + adjust: true, + footer: true, + resizeColumn: true, + headermenu: true, + columns: grid_tickets_cols, + scheme:{ + $change:function(item){ + if (item.estatus == 'Cancelado'){ + item.$css = 'cancel' + } + } + }, + on:{ + 'data->onStoreUpdated':function(){ + this.data.each(function(obj, i){ + obj.index = i + 1 + }) + } + }, +} + + +var rows_tickets_home = [ + {view: 'toolbar', elements: toolbar_tickets}, + {view: 'toolbar', elements: toolbar_tickets_filter}, + grid_tickets, +] + + +var ticket_suggest_products = { + view: 'gridsuggest', + id: 'gt_productos_found', + name: 'gt_productos_found', + body: { + autoConfig: false, + header: true, + columns: [ + {id: 'id', hidden: true}, + {id: 'clave', header: 'Clave', adjust: 'data'}, + {id: 'descripcion', header: 'Descripción', adjust: 'data'}, + {id: 'unidad', header: 'Unidad', adjust: 'data'}, + {id: 'valor_unitario', header: 'Valor Unitario', adjust: 'data', + format: webix.i18n.priceFormat} + ], + dataFeed:function(text){ + if (text.length > 2){ + this.load('/values/product?name=' + text) + }else{ + this.hide() + } + } + }, +} + + +var tbody_buscar_producto = {rows: [ + {cols: [ + {view: 'search', id: 'tsearch_product_key', name: 'tsearch_product_key', + label: 'por Clave', labelPosition:'top', maxWidth: 250, + placeholder: 'Presiona ENTER para buscar'}, + {view: 'search', id: 'tsearch_product_name', name: 'tsearch_product_name', + label: 'por Descripción', labelPosition:'top', + suggest: ticket_suggest_products, placeholder: 'Captura al menos tres letras'}, + ]}, +]} + + +var grid_tdetails_cols = [ + {id: "id", header:"ID", hidden: true}, + {id: 'delete', header: '', width: 30, css: 'delete'}, + {id: "clave", header:{text: 'Clave', css: 'center'}, width: 100, + footer: {text: 'Artículos', css: 'right_footer3'}}, + {id: "clave_sat", hidden: true}, + {id: "descripcion", header:{text: 'Descripción', css: 'center'}, + fillspace: true, footer: {content: 'countRows', css: 'footer3'}}, + {id: "unidad", header:{text: 'Unidad', css: 'center'}, width: 100}, + {id: 'cantidad', header: {text: 'Cantidad', css: 'center'}, width: 100, + format: webix.i18n.numberFormat, css: 'right', editor: 'text', + footer: {content: 'summColumn', css: 'right_footer3'}}, + {id: "valor_unitario", header:{text: 'Valor Unitario', css: 'center'}, + width: 100, format: webix.i18n.priceFormat, css: 'right', + footer: {text: 'Total ', css: 'right_footer2'}}, + {id: 'descuento', header: {text: 'Descuento', css: 'center'}, hidden: true, + width: 80, format: webix.i18n.priceFormat, css: 'right', editor: 'text'}, + {id: 'precio_final', hidden: true, header: 'precio_final', width: 80, + format: webix.i18n.priceFormat, css: 'right'}, + {id: "importe", header:{text: 'Importe', css: 'center'}, width: 150, + format: webix.i18n.priceFormat, css: 'right', + footer: {content: 'summColumn', css: 'right_footer2'}}, +] + + +var grid_tdetails = { + view: 'datatable', + id: 'grid_tdetails', + select: 'row', + adjust: true, + autoheight: true, + editable: true, + footer: true, + columns: grid_tdetails_cols, + on:{ + 'data->onStoreUpdated':function(){ + this.data.each(function(obj, i){ + obj.delete = '-' + }) + } + }, + data: [], +} + + +var body_ticket_informacion = {rows: [ + {view: 'richselect', id: 'lst_ticket_forma_pago', name: 'forma_pago', + label: 'Forma de Pago', labelPosition: 'top', required: true, + options: []}, +],} + + +var controls_generate_ticket = [ + {minHeight: 10, maxHeight: 10}, + {cols: [{rows: [ + {view: 'fieldset', label: 'Buscar Producto', body: tbody_buscar_producto}, + ]}, + {maxWidth: 10}, + {maxWidth: 300, rows: [ + {view: 'fieldset', label: 'Información', body: body_ticket_informacion}, + ]}, + ]}, + {view: 'label', label: 'Detalle', height: 30, align: 'left'}, + grid_tdetails, + {minHeight: 20, maxHeight: 20}, + {margin: 20, cols: [{}, + {view: 'button', id: 'cmd_generar_ticket', label: 'Generar', + icon: 'ticket', type: 'iconButton', autowidth: true, align: 'center'}, + {}] + }, + {rows: [ + {template: '', type: 'section'}, + {margin: 10, cols: [{}, + {view: 'button', id: 'cmd_cerrar_ticket', label: 'Cerrar', + type: 'danger', autowidth: true, align: 'center'} + ] + }, + ]} +] + + +var toolbar_ticket_invoice = {view: 'toolbar', elements: [{}, + {view: 'checkbox', id: 'chk_is_invoice_day', labelWidth: 0, width: 150, + labelRight: 'Es factura del día'}, +{}]} + + +var ticket_suggest_partners = { + view: 'gridsuggest', + id: 'grid_ticket_clients_found', + name: 'grid_ticket_clients_found', + body: { + autoConfig: false, + header: false, + columns: [ + {id: 'id', hidden: true}, + {id: 'nombre', adjust: 'data'}, + {id: 'rfc', adjust: 'data'}, + {id: 'forma_pago', hidden: true}, + {id: 'uso_cfdi', hidden: true}, + ], + dataFeed:function(text){ + if (text.length > 2){ + this.load('/values/client?name=' + text) + }else{ + this.hide() + } + } + } +} + + +var ticket_search_client = {cols: [{rows: [ + {view: 'fieldset', id: 'fs_ticket_search_client', label: 'Buscar Cliente', body: {rows: [ + {cols: [ + {view: 'search', id: 'tsearch_client_key', name: 'tsearch_client_key', + label: 'por Clave', labelPosition: 'top', maxWidth: 250, + placeholder:'Presiona ENTER para buscar'}, + {view: 'search', id: 'tsearch_client_name', + name: 'tsearch_client_name', label: 'por Nombre o RFC', + labelPosition: 'top', suggest: ticket_suggest_partners, + placeholder: 'Captura al menos tres letras'}, + ]}, + {cols: [ + {view: 'label', id: 'lbl_tclient_title', autowidth:true, + name: "lbl_tclient_title", label: 'Seleccionado: ' }, + {view: 'label', id: 'lbl_tclient', name: 'lbl_tclient', + label: 'Ninguno'}, + ]} + ]}}, +]},]} + + +var grid_tickets_active_cols = [ + {id: 'index', header: '#', adjust: 'data', css: 'right'}, + {id: "id", header:"ID", hidden:true}, + {id: "serie", header: ["Serie", {content: "selectFilter"}], adjust: "data", + sort: "string", hidden: true}, + {id: 'folio', header: ['Folio', {content: 'numberFilter'}], adjust: 'header', + sort: 'int', css: 'right', footer: {content: 'countRows', css: 'right'}}, + {id: "fecha", header: ["Fecha y Hora"], adjust: "data", sort: "string", + footer: 'Tickets'}, + {id: 'total', header: 'Total', width: 150,sort: 'int', css: 'right', + format: webix.i18n.priceFormat, footer: {content: 'summColumn', + css: 'right'}}, +] + + +var grid_tickets_active = { + view: 'datatable', + id: 'grid_tickets_active', + select: 'row', + adjust: true, + footer: true, + drag: true, + resizeColumn: true, + headermenu: true, + columns: grid_tickets_active_cols, + on:{ + 'data->onStoreUpdated':function(){ + this.data.each(function(obj, i){ + obj.index = i + 1 + }) + } + }, +} + + +var grid_tickets_invoice_cols = [ + {id: 'index', header: '#', adjust: 'data', css: 'right'}, + {id: "id", header:"ID", hidden:true}, + {id: "serie", header: ["Serie", {content: "selectFilter"}], adjust: "data", + sort: "string", hidden: true}, + {id: 'folio', header: ['Folio', {content: 'numberFilter'}], adjust: 'header', + sort: 'int', css: 'right', footer: {content: 'countRows', css: 'right'}}, + {id: "fecha", header: ["Fecha y Hora"], adjust: "data", sort: "string", + footer: 'Tickets'}, + {id: 'total', header: 'Total', width: 150,sort: 'int', css: 'right', + format: webix.i18n.priceFormat, footer: {content: 'summColumn', + css: 'right'}}, +] + + +var grid_tickets_invoice = { + view: 'datatable', + id: 'grid_tickets_invoice', + select: 'row', + adjust: true, + footer: true, + drag: true, + resizeColumn: true, + headermenu: true, + columns: grid_tickets_invoice_cols, + on:{ + 'data->onStoreUpdated':function(){ + this.data.each(function(obj, i){ + obj.index = i + 1 + }) + } + }, +} + + +var controls_ticket_to_invoice = [ + {minHeight: 10, maxHeight: 10}, + toolbar_ticket_invoice, + {minHeight: 10, maxHeight: 10}, + ticket_search_client, + {minHeight: 5, maxHeight: 5}, + {cols:[ + {rows: [{view: 'label', label: 'Tickets sin facturar', height: 30, + align: 'left'}, + grid_tickets_active]}, + {rows:[{}, + {view: 'button', id: 'cmd_move_tickets_right', label: '->', autowidth: true}, + {view: 'button', id: 'cmd_move_tickets_left', label: '<-', autowidth: true}, + {}]}, + {rows: [{view: 'label', label: 'Tickets a facturar', height: 30, align: 'left'}, grid_tickets_invoice]}, + ]}, + {minHeight: 20, maxHeight: 20}, + {margin: 20, cols: [{}, + {view: 'button', id: 'cmd_new_invoice_from_ticket', label: 'Facturar', + icon: 'ticket', type: 'iconButton', autowidth: true, align: 'center'}, + {}] + }, + {rows: [ + {template: '', type: 'section'}, + {margin: 10, cols: [{}, + {view: 'button', id: 'cmd_close_ticket_invoice', label: 'Cerrar', + type: 'danger', autowidth: true, align: 'center'} + ] + }, + ]} +] + + +var controls_new_ticket = [ + { + view: 'tabview', + id: 'tv_new_ticket', + animate: true, + cells: [ + {id: 'Generar', rows: controls_generate_ticket}, + ] + }, +] + + +var controls_ticket_invoice = [ + { + view: 'tabview', + id: 'tv_ticket_invoice', + animate: true, + cells: [ + {id: 'Facturar Tickets', rows: controls_ticket_to_invoice}, + ] + }, +] + + +var form_new_ticket = { + type: 'space', + responsive: true, + cols: [{ + view: 'form', + id: 'form_new_ticket', + complexData: true, + scroll: true, + elements: controls_new_ticket, + }], +} + + +var form_ticket_invoice = { + type: 'space', + responsive: true, + cols: [{ + view: 'form', + id: 'form_ticket_invoice', + complexData: true, + scroll: true, + elements: controls_ticket_invoice, + }], +} + + +var multi_tickets = { + id: 'multi_tickets', + view: 'multiview', + animate: true, + cells:[ + {id: 'tickets_home', rows: rows_tickets_home}, + {id: 'tickets_new', rows:[form_new_ticket]}, + {id: 'tickets_invoice', rows:[form_ticket_invoice]} + ], +} + + +var app_tickets = { + id: 'app_tickets', + rows:[ + {view: 'template', id: 'th_ticckets', type: 'header', + template: 'Punto de venta - Tickets'}, + multi_tickets + ], +} diff --git a/source/templates/main.html b/source/templates/main.html index 8c95768..d5395ca 100644 --- a/source/templates/main.html +++ b/source/templates/main.html @@ -8,12 +8,14 @@ + +