Mostrando entradas con la etiqueta callback. Mostrar todas las entradas
Mostrando entradas con la etiqueta callback. Mostrar todas las entradas

miércoles, 1 de mayo de 2013

Primeras pruebas - 2ª parte: node + mongodb + express


En el anterior post (Primeras pruebas: node + mnogodb + express) dejé preparados los datos necesarios para crear un nuevo documento en la colección 'historial', para luego introducir líneas con los pedidos que se van realizando en dicho historial.

La función 'nuevo_historial' recogía los datos enviados por un formulario y mostraba en el navegador los datos necesarios para crear el nuevo historial. Habrá que modificar para que inserte dichos datos:


exports.nuevo_historial = function(req, res){
var x = 0;
var y = new Date();
var usuario = req.body.usuario;
var _mesa = req.body.mesa;

db.historial.find({},{_id:1}).sort({_id:-1}).limit((1), function(err, id){
if(err || !id) res.send("Error o no hay ningun documento");
else {
x = id[0]._id + 1;
db.historial.insert({_id: x, fecha: y, mesa: _mesa}, function(err, result){
if (err) res.send("Algo ha fallado");
else res.send("Mesa: " + _mesa + " abierta con la id: " + x + " por el usuario " + usuario);
});

}
});
};

He cambiado el nombre de la variable 'mesa' por '_mesa', para que no haya problemas con el nombre de la clave. La renovada función debería insertar un nuevo documento en el historial al recibir los para metros del formulario:


Si envío el formulario:


Parece que todo ha ido bien. De todas formas lo comprobaré desde shell:

{ "_id" : 3, "fecha" : ISODate("2013-04-24T17:58:53.989Z"), "mesa" : "2" }

Todo está bien. Se ha insertado el historial con la _id 3 y la mesa 2. El usuario es lo de menos ya que lo incluí de prueba, por enviar algo más a parte de la mesa. Pero en la aplicación real también debe ir en el documento, para saber quién abre la mesa.

El caso es que, para comprobar los historiales en curso, necesito una función que me los devuelva. Así que voy a crearla. Esta vez mostrará los resultados con un formato más legible, en vez de json:

exports.ver_historiales = function(req, res){
var h = "";
db.historial.find({}, function(err, id){
if(err || !id) res.send("Error o no hay ningun documento");
else { id.forEach(function(historiales){
fecha = historiales.fecha.getDate() +"/"+ historiales.fecha.getMonth()+"/"+ historiales.fecha.getYear();
hora = historiales.fecha.getHours() +":"+ historiales.fecha.getMinutes();
 
h = h + "Historial: " + historiales._id + " Mesa: " + historiales.mesa + " Fecha: " + fecha + " " + hora + " <br>"
 
});
res.send(h)
}
});
};

Modifico app.js para que use la nueva función y pruebo:


Antes de hacer que se muestren los pedidos dentro de cada documento, voy  a crear la función que los inserte. El usuario pincha en el artículo y se agrega al historial.

Para hacerlo un poco más amigable lo que hay hecho hasta ahora, voy a modificar los archivos 'articulos.js' y 'mesas.js' para que me muestre botones en vez de json, así será más cómodo y podré ir probando nuevas opciones. Así practico un poco...

La función ver_zonas mostrará las zonas disponibles en nuestro bar para que seleccionemos una:

exports.ver_zonas = function(req, res) {
var html = "<form method=\"post\" action=\"http://localhost:3000/zona\">";
db.mesas.distinct('zona', function(err, zonas) {
 if( err || !zonas) res.send("No hay zonas");
 else{
 zonas.forEach( function(err, zon) {
html = html + "<input type=\"submit\" name=\"zona\" value=\""+ zonas[zon] + "\">";
 });
 html = html + "</form>";
 }
 res.send(html);
});
};



Ahora, al pulsar una zona, llamaremos a 'mesas_x_zonas' para que muestre las mesas bajo esa zona:. 


exports.mesas_x_zona = function(req, res) {

var html = "<form method=\"post\" action=\"http://localhost:3000/familias\">";
var _zona = req.body.zona;
db.mesas.find({zona: _zona}, function(err, mesas) {
 if( err || !mesas) res.send("No hay zonas");
 else{
 mesas.forEach( function(err, mesa) {
html = html + "<input type=\"submit\" name=\"mesa\" value=\""+ mesas[mesa].nombre + "\">";
 });
 html = html + "</form>";
 
 }
 res.send(html);
});
};

No hay que olvidar cambiar get por post en la app y quitar el parámetro ':zona':

app.post('/zona', mesas.mesas_x_zona);

 Si por ejemplo selecciono 'Salon':

Siguiendo el ejemplo, al pulsar una mesa deben aparecer las familias disponibles para escoger un artículo. Para ello, modifico 'ver_familias' en 'articulos.js':

exports.ver_familias = function(req, res) {
var html = "<form method=\"post\" action=\"http://localhost:3000/familia\">";
var _mesa = req.body.mesa;
db.articulos.distinct('familia', function(err, familias){
if(err || !familias) res.send("No hay familias");
else {
html = html + "Mesa: <input type=\"text\" name=\"mesa\" value=\""+ _mesa + "\"><br>";
 familias.forEach( function(err, familia) {
html = html + "<input type=\"submit\" name=\"familia\" value=\""+ familias[familia] + "\">";
 });
 html = html + "</form>";
    }
res.send(html);
});
};:

Si selecciono la mesa 2:



Aparecen las distintas familias. Pero aquí haré un alto en el camino antes de llegar a la función que inserte el pedido.

Necesito comprobar si la mesa ya tiene algún pedido y mostrarlo debajo de las familias. Modificaré la función 'ver_familias' para que lo muestre:


exports.ver_familias = function(req, res) {
var html = "<form method=\"post\" action=\"http://localhost:3000/familia\">";
var _mesa = req.body.mesa;
db.articulos.distinct('familia', function(err, familias){
if(err || !familias) res.send("No hay familias");
else {
html = html + "Mesa: <input type=\"text\" name=\"mesa\" value=\""+ _mesa + "\"><br>";
 familias.forEach( function(err, familia) {
html = html + "<input type=\"submit\" name=\"familia\" value=\""+ familias[familia] + "\">";
 });
 html = html + "</form>";
    }
db.historial.find({mesa: _mesa}, {descrip: 1}, function(err, historial){
if(err || !historial) html = html + "Error o mesa vacía";
else { historial.forEach( function (err, hist){
historial[hist].descrip.forEach( function (err, desc){
html = html + historial[hist].descrip[desc].uds;
html = html + "  " + historial[hist].descrip[desc].articulo;
html = html + "  " + historial[hist].descrip[desc].precio + "<br>";
});
res.send(html);
});
}
});
});
};


He añadido una consulta para que muestre en el resultado los pedidos en esa mesa. El resultado al seleccionar la mesa 5 ( la que tiene más pedidos) sería como sigue:

Aunque el resultado es el correcto y muestra lo que necesito, me gustaría aislar en una función la consulta que muestra los pedidos en la mesa. De este modo 'ver_familias' quedaría algo más limpia y podría reutilizar la nueva función en otras operaciones.

Esta nueva función se llamará 'check_mesa':

function check_mesa(_mesa, callback){
var html = "Pedido: <br>";
db.historial.find({mesa: _mesa}, {descrip: 1}, function(err, historial){
if(err || !historial) html = html + "Error o mesa vacía";
else { 
historial.forEach( function (err, hist){
historial[hist].descrip.forEach( function (err, desc){
html = html + historial[hist].descrip[desc].uds;
html = html + "  " + historial[hist].descrip[desc].articulo;
html = html + "  " + historial[hist].descrip[desc].precio + "<br>";
});
 
});
}
 
callback(html);
});
}

Modifico 'ver_familias' para que llame a la nueva función:

check_mesa(_mesa, function (datos){
res.send(html + datos);}
);

Comprobaré el pedido de la mesa 5:

Todo correcto. Comprobaré la mesa 6, la cual no tiene nada... Todo OK.

Ahora comprobaré la 2, la cual está en 'historial' pero no tiene pedidos:

La app deja de funcionar, arrojando el error:

TypeError: Cannot call method 'forEach' of undefined

Es lógico, ya que existe el historial pero no hay datos con la  clave 'descrip'. Para corregir esto haré algo que debería haber hecho desde un principio. Añadir excepciones (o como quieran llamarlas). De este modo si hay algún error o algún documento o clave no existen, redirijo a donde deba con el mensaje que corresponda.

Modificando la función 'check_mesa', quedaría así:

 function check_mesa(_mesa, callback){
var html = "Pedido: <br>";
db.historial.find({mesa: _mesa}, {descrip: 1}, function(err, historial){
if(err){
html = "Error obteniedo historial.";
}else if(!historial[0]) {
html = "mesa vacía";
}else { 
  if (!historial[0].descrip){
html = "Mesa abierta pero no hay pedidos";
  }else{
historial.forEach( function (err, hist){
historial[hist].descrip.forEach( function (err, desc){
html = html + historial[hist].descrip[desc].uds;
html = html + "  " + historial[hist].descrip[desc].articulo;
html = html + "  " + historial[hist].descrip[desc].precio + "<br>";
});
});
}
}
 
callback(html);
});
}

Voy a comprobarlo con las  distintas mesas.

Mesa 5 tiene pedidos:

La mesa 2 tiene un historial abierto, pero no tiene pedidos:

La mesa 6 no tiene un historial abierto:



Una vez corregido el fallo ya puedo seguir con la tarea de insertar un pedido en la mesa. Lo siguiente entonces será mostrar los artículos al pulsar sobre una familia para poder agregarlo al pedido.

Modifico 'articulos_x_familia' para que muestre los artículos

exports.articulos_x_familia = function(req, res) {
var html = "<form method=\"post\" action=\"http://localhost:3000/inserta_pedido\">";
var _mesa = req.body.mesa;
var _familia = req.body.familia;
db.articulos.find({familia: _familia}, function(err, articulos){
if(err || !articulos) res.send("No hay artículos en la familia " + _familia);
else{
html = html + "Mesa: <input type=\"text\" name=\"mesa\" value=\""+ _mesa + "\"><br>";
html = html + "Familia: <input type=\"text\" name=\"familia\" value=\""+ _familia + "\"><br>";
articulos.forEach( function(err, articulo){
html = html + "<input type=\"submit\" name=\"articulo\" value=\""+ articulos[articulo].nombre + "\">";
});
html = html + "</form>";
check_mesa(_mesa, function (datos){
res.send(html + datos);}
);
}
});
};

Además de modificar la función para que muestre los artículos, también hago uso de 'check_mesa' para seguir mostrando la lista de pedidos. Si selecciono la familia 'Carnes' el resultado sería:


Ahora crearé la función 'inserta_pedido' encargada de añadir el artículo seleccionado al pedido. La crearé dentro del archivo 'historial.js. Como voy a hacer uso de la tabla 'articulos' para obtener el precio y los visores, tengo que añadir la colección a la variable 'collections'. También necesito añadir la colección mesas, ya que tengo que usar el campo 'visores' para insertarlo en el pedido':

var collections = ["historial", "articulos", "mesas"];

Como voy a crear una función en 'articulos.js' que devuelva el articulo pedido, requiero dicho archivo desde historial.js:

var articulos = require('./articulos');

Ahora, en articulos.js, creo la función que me devolverá los datos del artículo:


exports.ver_articulo = function(_articulo, callback){

db.articulos.find({nombre: _articulo}, function(err, articulo){
if (err){
console.log("Error obteniendo articulo.");
}else if(!articulo){
console.log("No existe el artículo.");
}else{
articulo.forEach( function(art){
callback(art);
});

}
});

};

Y la llamaré desde 'inserta_pedido' para ver si obtengo los datos que necesito:


exports.inserta_pedido = function(req, res){
var _mesa = req.body.mesa;
var _articulo = req.body.articulo;

articulos.ver_articulo(_articulo, function(datos){
res.send(datos.nombre + " " + datos.precio + " " + datos.tipo);
});
};

Compruebo:









El siguiente dato que necesito está en la clave 'visores' dentro de la colección 'mesas'. Crearé otra función en 'mesas' para que lo devuelva. Antes hay que requerir el archivo 'mesas.js' desde 'historial':

var mesas = require('./mesas');

Ahí va la función:


exports.ver_mesa = function (_mesa, art_tipo, callback){
var visor;

db.mesas.find({nombre: _mesa}, function(err, datos){
datos.forEach( function(mes){
  if (art_tipo == "comida"){
  visor = mes.visores.comida;
  }else{
visor = mes.visores.bebida;
  }
});
callback(visor);
});
};


De nuevo para comprobar, modifico 'inserta_pedido' para que muestre los datos por pantalla y estar seguro de que todo es correcto:


articulos.ver_articulo(_articulo, function(datos){
mesas.ver_mesa(_mesa, datos.tipo, function(mes){
res.send(datos.nombre + " " + datos.precio + " " + datos.tipo + "<br>" + mes);
});

});

Compruebo en el navegador:









Si selecciono 'Churrasco' me aparece el nombre, su precio (8.60) y el tipo 'comida', por lo que muestra 'cocina y parrilla' que son los visores por los cuales debe verse el artículo.

Comprobaré con una bebida... Marcaré una buena Jarra de Cerveza:








Aparecen los datos correctos. El último dato que falta para finalmente añadir el pedido, es el número de línea. Necesito otra función que lo compruebe. Si no hay pedidos, lo inserta con la línea número 1. Si los hay, obtiene el dato y le suma uno. Si la mesa no tiene un historial abierto, que avise y redirija al sitio para abrir un nuevo historial.


num_linea = function(_mesa, callback){
var num = 0;
db.historial.find({mesa: _mesa}, {descrip:1}, function(err, historial){
if(err){
callback("Error");
}else if (!historial[0]){
callback("La mesa no tiene historial abierto.");
}else{
historial.forEach( function(desc){
if (!desc.descrip){
num = 1;
callback(num);
}else{
desc.descrip.forEach( function(d){
num = d.linea;
});
callback(num+1);
}
});
}
});
};


La función 'num_linea' será la encargada de realizar esta tarea. Para comprobar los datos, 'inserta_pedido' queda más o menos así:

articulos.ver_articulo(_articulo, function(datos){
mesas.ver_mesa(_mesa, datos.tipo, function(mes){
num_linea(_mesa, function(num){
res.send(datos.nombre + " " + datos.precio + " " + datos.tipo + "<br>" + mes + "<br>Num. linea: " + num);
});
});
});


Pruebo con las distintas mesas:
Mesa 5, como ya tiene 4 lineas, me devuelve 5:

Mesa 2, como no tiene ningún pedido, me devuelve 1:


Mesa 3, no esta en historiales y me avisa:

En este último caso, redireccionaría al formulario para su apertura. Pero ahora lo importante es que ya tengo los datos para poder insertar el pedido en una mesa.

Lo primero será modificar 'num_linea' para que devuelva 0 en caso de no estar en ningún historial:

}else if (!historial[0]){
num = 0;
callback(num);



Ahora creo los $set o $push, dependiendo del número de línea. 'inserta_pedido' queda como sigue:

exports.inserta_pedido = function(req, res){
var _mesa = req.body.mesa;
var _articulo = req.body.articulo;
articulos.ver_articulo(_articulo, function(datos){
mesas.ver_mesa(_mesa, datos.tipo, function(mes){
num_linea(_mesa, function(num){
if (num == 0){
res.send("No se puede insertar el pedido en una mesa sin historial.");
}else if(num == 1){
db.historial.update({mesa:_mesa}, {$set:{descrip:[{linea: num, articulo: datos.nombre, precio : datos.precio, uds: 1, visores:mes }]}}, function(err){
 res.send("Insertado");
});
}else{
db.historial.update({mesa:_mesa}, {$push:{descrip:{linea: num, articulo: datos.nombre, precio : datos.precio, uds: 1, visores:mes }}}, function(err){
 res.send("Insertado");
});
}
});
});
});
};




Para probar, borraré todos los historiales desde shell y comenzare desde 0.

Al borrar los documentos de la colección 'historial', si trato de abrir una mesa desde form_mesa.html, me arrojará un error, ya que la función 'nuevo_historial' suma 1 al último. Al no haber ninguno, fallará. Así que lo primero es modificarla:


db.historial.find({},{_id:1}).sort({_id:-1}).limit((1), function(err, id){
if(err){
res.send("Error");
}else if(!id[0]){
x = 1;
}else {
x = id[0]._id + 1;
}
db.historial.insert({_id: x, fecha: y, mesa: _mesa}, function(err, result){
if (err) res.send("Algo ha fallado");
else res.send("Mesa: " + _mesa + " abierta con la id: " + x + " por el usuario " + usuario);
});
});

Ya está. En caso de que no haya ningún historial, devuelve 1.

A probar. Abro mi formulario y abro la mesa 2:









Pulso Enviar para abrir la mesa:







Accedo al principio de la app (localhost:3000/zonas) y selecciono 'Salon' (donde está la mesa 2):








Selecciono la mesa 2... :







... y me aparecen sus datos:










Selecciono la familia Carnes para ver los artículos carnívoros:











Finalmente pulso 'Churrasco' y...







Parece que se ha insertado correctamente... para comprobarlo, vuelvo hacia atrás en el historial del navegador y actualizo la página:












Añadiré varios artículos más al pedido para ver que funciona:

















Pues parece que todo funciona. Pruebo con otra mesa. Abriré la 5:
























Realmente, lo ideal sería que estuviera viendo una línea nueva en pantalla en vez de ver el mensaje 'insertado' cada vez que se añade un artículo (volver hacia atrás y actualizar la página...). Pero para el ejemplo y lo que quiero comprobar, va sobrado.

Si compruebo las colecciones desde shell:


> db.historial.find().pretty();
{
        "_id" : 1,
        "descrip" : [
                {
                        "linea" : 1,
                        "articulo" : "Churrasco",
                        "precio" : 8.6,
                        "uds" : 1,
                        "visores" : [
                                "cocina",
                                "parrilla"
                        ]
                },
                {
                        "linea" : 2,
                        "articulo" : "Kobe",
                        "precio" : 8.6,
                        "uds" : 1,
                        "visores" : [
                                "cocina",
                                "parrilla"
                        ]
                },
                {
                        "linea" : 3,
                        "articulo" : "Solomillo de Ternera",
                        "precio" : 8.6,
                        "uds" : 1,
                        "visores" : [
                                "cocina",
                                "parrilla"
                        ]
                },
                {
                        "linea" : 4,
                        "articulo" : "Jarra Cerveza",
                        "precio" : 3,
                        "uds" : 1,
                        "visores" : [
                                "barra"
                        ]
                },
                {
                        "linea" : 5,
                        "articulo" : "Tanque",
                        "precio" : 1,
                        "uds" : 1,
                        "visores" : [
                                "barra"
                        ]
                }
        ],
        "fecha" : ISODate("2013-05-01T17:30:13.673Z"),
        "mesa" : "2"
}
{
        "_id" : 2,
        "descrip" : [
                {
                        "linea" : 1,
                        "articulo" : "Solomillo de Ternera",
                        "precio" : 8.6,
                        "uds" : 1,
                        "visores" : [
                                "cocina",
                                "parrilla"
                        ]
                },
                {
                        "linea" : 2,
                        "articulo" : "Kobe",
                        "precio" : 8.6,
                        "uds" : 1,
                        "visores" : [
                                "cocina",
                                "parrilla"
                        ]
                },
                {
                        "linea" : 3,
                        "articulo" : "Urta",
                        "precio" : 8.6,
                        "uds" : 1,
                        "visores" : [
                                "cocina",
                                "parrilla"
                        ]
                },
                {
                        "linea" : 4,
                        "articulo" : "Fanta Naranja",
                        "precio" : 1,
                        "uds" : 1,
                        "visores" : [
                                "barra"
                        ]
                },
                {
                        "linea" : 5,
                        "articulo" : "Fanta Limon",
                        "precio" : 1,
                        "uds" : 1,
                        "visores" : [
                                "barra"
                        ]
                }
        ],
        "fecha" : ISODate("2013-05-01T17:46:40.237Z"),
        "mesa" : "5"
}




Todo correcto (al menos hasta donde llego a alcanzar).

Ya tengo un mini-sistema de pedidos funcionando. No puedo decir que sea KomanderTPV pero puedo insertar pedidos en las mesas.Es lo que me interesa para poder ver los pedidos a través de los visores en tiempo real.

Y hablando de tiempo real... Hasta ahora, lo que se ha hecho, no ha sido una comunicación en tiempo real.  Es más, este sistema no me sirve realmente para mis propósitos (al menos por ahora).

Si quisiera ver la información de los pedidos, tendría que estar llamando cada X tiempo para comprobar si hay pedidos nuevos y mostrarlos. Eso no es lo que quiero.

Para lo que yo quiero necesito Socket.io, el cual desconozco, pero permitirá a mi app mantener una comunicación en tiempo real con los clientes.

Así que el siguiente paso será trastear con socket.io desde node (y mongo por su puesto) y comprobar como recogería estos pedidos en los visores.

Cualquier comentario es de agradecer ya que no sé si voy por el buen camino. Puede que me esté liando y no esté haciendo los esquemas o las funciones como debiera.

Hasta entonces, Saludos.

Luismips