mirror of
https://github.com/rmontanana/inventario2.git
synced 2025-08-16 07:56:00 +00:00
Quitado el mensaje de usuario demo
Añadida la clase csv que se encargará de gestionar este tipo de ficheros para exportar e importar información Añadidas las opciones de exportar a fichero el inventario de un Artículo y el inventario de una Ubicación. Añadida la opción de importar un fichero csv (a medio)
This commit is contained in:
@@ -134,7 +134,7 @@ class AportaContenido {
|
|||||||
if ($this->usuario_inc) {
|
if ($this->usuario_inc) {
|
||||||
$salida.=USUARIO_INCORRECTO;
|
$salida.=USUARIO_INCORRECTO;
|
||||||
}
|
}
|
||||||
$salida.=MENSAJE_DEMO;
|
//$salida.=MENSAJE_DEMO;
|
||||||
return $salida;
|
return $salida;
|
||||||
}
|
}
|
||||||
case 'opcion':
|
case 'opcion':
|
||||||
@@ -154,6 +154,7 @@ class AportaContenido {
|
|||||||
return 'Configuración y Preferencias.';
|
return 'Configuración y Preferencias.';
|
||||||
case 'informeInventario':return "Informe de Inventario";
|
case 'informeInventario':return "Informe de Inventario";
|
||||||
case 'descuadres':return 'Informe de descuadres';
|
case 'descuadres':return 'Informe de descuadres';
|
||||||
|
case 'importacion': return 'Importación de datos';
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
case 'control':
|
case 'control':
|
||||||
@@ -201,7 +202,7 @@ class AportaContenido {
|
|||||||
$conf = new Configuracion();
|
$conf = new Configuracion();
|
||||||
return $conf->ejecuta();
|
return $conf->ejecuta();
|
||||||
} else {
|
} else {
|
||||||
return $this->mensajePermisos('Configuracion');
|
return $this->mensajePermisos('Configuración');
|
||||||
}
|
}
|
||||||
case 'informeInventario':
|
case 'informeInventario':
|
||||||
if ($this->perfil['Informe']) {
|
if ($this->perfil['Informe']) {
|
||||||
@@ -221,6 +222,13 @@ class AportaContenido {
|
|||||||
} else {
|
} else {
|
||||||
return $this->mensajePermisos('Informes');
|
return $this->mensajePermisos('Informes');
|
||||||
}
|
}
|
||||||
|
case 'importacion':
|
||||||
|
if ($this->perfil['Modificacion'] && $this->perfil['Borrado']) {
|
||||||
|
$import = new Importacion($this->bdd, $this->registrado);
|
||||||
|
return $import->ejecuta();
|
||||||
|
} else {
|
||||||
|
return $this->mensajePermisos("Actualización, creación y borrado de elementos");
|
||||||
|
}
|
||||||
} // Fin del contenido
|
} // Fin del contenido
|
||||||
case 'usuario_incorrecto':
|
case 'usuario_incorrecto':
|
||||||
$this->usuario_inc = true;
|
$this->usuario_inc = true;
|
||||||
|
60
Importacion.php
Normal file
60
Importacion.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package Inventario
|
||||||
|
* @copyright Copyright (c) 2008, Ricardo Montañana Gómez
|
||||||
|
* @license http://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
* This file is part of Inventario.
|
||||||
|
* Inventario is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Inventario is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Inventario. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Importacion {
|
||||||
|
|
||||||
|
private $bdd;
|
||||||
|
|
||||||
|
public function __construct($baseDatos, $registrado) {
|
||||||
|
if (!$registrado) {
|
||||||
|
return 'Debe registrarse para acceder a este apartado';
|
||||||
|
}
|
||||||
|
$this->bdd = $baseDatos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ejecuta() {
|
||||||
|
$opc = $_GET['opc'];
|
||||||
|
switch ($opc) {
|
||||||
|
case 'form':return $this->formulario();
|
||||||
|
case 'importar':return $this->importarFichero();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function importarFichero() {
|
||||||
|
$uploadfile = "tmp/" . basename($_FILES['fichero']['name']);
|
||||||
|
if (!move_uploaded_file($_FILES['fichero']['tmp_name'], $uploadfile)) {
|
||||||
|
die('No se pudo subir el fichero ' . $_FILES['userfile']['tmp_name']);
|
||||||
|
}
|
||||||
|
$csv = new Csv($this->bdd);
|
||||||
|
return $csv->cargaCSV($uploadfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formulario() {
|
||||||
|
$accion = "index.php?importacion&opc=importar";
|
||||||
|
$salida = '<form enctype="multipart/form-data" name="importacion.form" method="post" action="' . $accion . '">' . "\n";
|
||||||
|
$salida.="<fieldset style=\"width: 96%;\"><p><legend style=\"color: red;\"><b>Elige Archivo</b></legend>\n";
|
||||||
|
$salida.= '<input type="file" name="fichero" id="fichero">';
|
||||||
|
$salida.='<p align="center"><button type=submit>Aceptar</button></p><br>' . "\n";
|
||||||
|
return $salida;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
@@ -39,8 +39,14 @@ class InformeInventario {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function listarUbicacion() {
|
private function listarUbicacion() {
|
||||||
$fichero = "xml/inventarioUbicacion.xml";
|
$salidaInforme = isset($_POST['salida']) ? $_POST['salida'] : 'pantalla';
|
||||||
$salida = "tmp/inventarioUbicacion.xml";
|
if ($salidaInforme == "pantalla") {
|
||||||
|
$fichero = "xml/inventarioUbicacion.xml";
|
||||||
|
$salida = "tmp/inventarioUbicacion.xml";
|
||||||
|
} else {
|
||||||
|
$fichero = "xml/inventarioUbicacionCSV.xml";
|
||||||
|
$salida = "tmp/inventarioUbicacionCSV.xml";
|
||||||
|
}
|
||||||
$plantilla = file_get_contents($fichero) or die('Fallo en la apertura de la plantilla ' . $fichero);
|
$plantilla = file_get_contents($fichero) or die('Fallo en la apertura de la plantilla ' . $fichero);
|
||||||
$id = $_POST['id'] == NULL ? $_GET['id'] : $_POST['id'];
|
$id = $_POST['id'] == NULL ? $_GET['id'] : $_POST['id'];
|
||||||
$comando = "select * from Ubicaciones where id='" . $id . "';";
|
$comando = "select * from Ubicaciones where id='" . $id . "';";
|
||||||
@@ -52,16 +58,32 @@ class InformeInventario {
|
|||||||
$plantilla = str_replace("{id}", $id, $plantilla);
|
$plantilla = str_replace("{id}", $id, $plantilla);
|
||||||
$plantilla = str_replace("{Descripcion}", utf8_encode($fila['Descripcion']), $plantilla);
|
$plantilla = str_replace("{Descripcion}", utf8_encode($fila['Descripcion']), $plantilla);
|
||||||
file_put_contents($salida, $plantilla) or die('Fallo en la escritura de la plantilla ' . $salida);
|
file_put_contents($salida, $plantilla) or die('Fallo en la escritura de la plantilla ' . $salida);
|
||||||
$informe = new InformePDF($this->bdd, $salida, true);
|
if ($salidaInforme == "pantalla") {
|
||||||
$informe->crea($salida);
|
$informe = new InformePDF($this->bdd, $salida, true);
|
||||||
$informe->cierraPDF();
|
$informe->crea($salida);
|
||||||
$informe->guardaArchivo("tmp/Informe.pdf");
|
$informe->cierraPDF();
|
||||||
echo '<script type="text/javascript"> window.open( "tmp/Informe.pdf" ) </script>';
|
$informe->guardaArchivo("tmp/Informe.pdf");
|
||||||
|
echo '<script type="text/javascript"> window.open( "tmp/Informe.pdf" ) </script>';
|
||||||
|
} else {
|
||||||
|
//Genera una hoja de cálculo en formato csv
|
||||||
|
$nombre = "tmp/Ubicacion" . strftime("%Y%m%d") . rand(100, 999) . ".csv";
|
||||||
|
$hoja = new Csv($this->bdd);
|
||||||
|
$hoja->crea($nombre);
|
||||||
|
$hoja->ejecutaConsulta($salida);
|
||||||
|
$hoja->cierra();
|
||||||
|
echo '<script type="text/javascript"> window.open( "' . $nombre . '" ) </script>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function listarArticulo() {
|
private function listarArticulo() {
|
||||||
$fichero = "xml/inventarioArticulo.xml";
|
$salidaInforme = isset($_POST['salida']) ? $_POST['salida'] : 'pantalla';
|
||||||
$salida = "tmp/inventarioArticulo.xml";
|
if ($salidaInforme == "pantalla") {
|
||||||
|
$fichero = "xml/inventarioArticulo.xml";
|
||||||
|
$salida = "tmp/inventarioArticulo.xml";
|
||||||
|
} else {
|
||||||
|
$fichero = "xml/inventarioArticuloCSV.xml";
|
||||||
|
$salida = "tmp/inventarioArticuloCSV.xml";
|
||||||
|
}
|
||||||
$plantilla = file_get_contents($fichero) or die('Fallo en la apertura de la plantilla ' . $fichero);
|
$plantilla = file_get_contents($fichero) or die('Fallo en la apertura de la plantilla ' . $fichero);
|
||||||
$id = $_POST['id'] == NULL ? $_GET['id'] : $_POST['id'];
|
$id = $_POST['id'] == NULL ? $_GET['id'] : $_POST['id'];
|
||||||
$comando = "select * from Articulos where id='" . $id . "';";
|
$comando = "select * from Articulos where id='" . $id . "';";
|
||||||
@@ -75,11 +97,21 @@ class InformeInventario {
|
|||||||
$plantilla = str_replace("{Marca}", utf8_encode($fila['marca']), $plantilla);
|
$plantilla = str_replace("{Marca}", utf8_encode($fila['marca']), $plantilla);
|
||||||
$plantilla = str_replace("{Modelo}", utf8_encode($fila['modelo']), $plantilla);
|
$plantilla = str_replace("{Modelo}", utf8_encode($fila['modelo']), $plantilla);
|
||||||
file_put_contents($salida, $plantilla) or die('Fallo en la escritura de la plantilla ' . $salida);
|
file_put_contents($salida, $plantilla) or die('Fallo en la escritura de la plantilla ' . $salida);
|
||||||
$informe = new InformePDF($this->bdd, $salida, true);
|
if ($salidaInforme == "pantalla") {
|
||||||
$informe->crea($salida);
|
$informe = new InformePDF($this->bdd, $salida, true);
|
||||||
$informe->cierraPDF();
|
$informe->crea($salida);
|
||||||
$informe->guardaArchivo("tmp/Informe.pdf");
|
$informe->cierraPDF();
|
||||||
echo '<script type="text/javascript"> window.open( "tmp/Informe.pdf" ) </script>';
|
$informe->guardaArchivo("tmp/Informe.pdf");
|
||||||
|
echo '<script type="text/javascript"> window.open( "tmp/Informe.pdf" ) </script>';
|
||||||
|
} else {
|
||||||
|
//Genera una hoja de cálculo en formato csv
|
||||||
|
$nombre = "tmp/Articulo" . strftime("%Y%m%d") . rand(100, 999) . ".csv";
|
||||||
|
$hoja = new Csv($this->bdd);
|
||||||
|
$hoja->crea($nombre);
|
||||||
|
$hoja->ejecutaConsulta($salida);
|
||||||
|
$hoja->cierra();
|
||||||
|
echo '<script type="text/javascript"> window.open( "' . $nombre . '" ) </script>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function listaUbicaciones() {
|
private function listaUbicaciones() {
|
||||||
@@ -111,10 +143,13 @@ class InformeInventario {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function formulario($accion, $etiqueta, $lista) {
|
private function formulario($accion, $etiqueta, $lista) {
|
||||||
$salida = '<form name="mantenimiento.form" method="post" action="' . $accion . '">' . "\n";
|
$salida = '<form name="informeInventario.form" method="post" action="' . $accion . '">' . "\n";
|
||||||
$salida.="<fieldset style=\"width: 96%;\"><p><legend style=\"color: red;\"><b>Elige $etiqueta</b></legend>\n";
|
$salida.="<fieldset style=\"width: 96%;\"><p><legend style=\"color: red;\"><b>Elige $etiqueta</b></legend>\n";
|
||||||
$salida.="<br><br><label>$etiqueta</label>";
|
$salida.="<br><br><label>$etiqueta</label>";
|
||||||
$salida.=$lista;
|
$salida.=$lista;
|
||||||
|
$salida.="<br><br>Salida del informe por: Pantalla ";
|
||||||
|
$salida.='<input type="radio" name="salida" value="pantalla" checked>';
|
||||||
|
$salida.=' Hoja de cálculo <input type="radio" name="salida" value="Hoja de cálculo">';
|
||||||
$salida.="<br><br></fieldset><p>";
|
$salida.="<br><br></fieldset><p>";
|
||||||
$salida.='<p align="center"><button type=submit>Aceptar</button></p><br>' . "\n";
|
$salida.='<p align="center"><button type=submit>Aceptar</button></p><br>' . "\n";
|
||||||
return $salida;
|
return $salida;
|
||||||
|
@@ -150,7 +150,7 @@ class Mantenimiento {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->campoBusca = $dato[1];
|
$this->campoBusca = $dato[1];
|
||||||
$valor = '<a target="_blank" href="index.php?informeInventario&opc=listar' . $datoEnlace . '&id=' . $id . '">' . $valor;
|
$valor = '<a title="Inventario de '.$valor.'" $target="_blank" href="index.php?informeInventario&opc=listar' . $datoEnlace . '&id=' . $id . '">' . $valor;
|
||||||
}
|
}
|
||||||
$salida.="<td>$valor</td>\n";
|
$salida.="<td>$valor</td>\n";
|
||||||
}
|
}
|
||||||
|
8
Menu.php
8
Menu.php
@@ -29,10 +29,10 @@ class Menu {
|
|||||||
// Obtenemos la lista de pares Opción|Enlace
|
// Obtenemos la lista de pares Opción|Enlace
|
||||||
$elementos=explode("\n", $contenido);
|
$elementos=explode("\n", $contenido);
|
||||||
foreach($elementos as $elemento) {
|
foreach($elementos as $elemento) {
|
||||||
list($tipo, $opcion, $enlace, $destino)=explode('|', $elemento);
|
list($tipo, $opcion, $enlace, $destino, $titulo)=explode('|', $elemento);
|
||||||
// Los guardamos en la matriz de opciones
|
// Los guardamos en la matriz de opciones
|
||||||
if ($tipo)
|
if ($tipo)
|
||||||
$this->opciones[]=$tipo.",".$opcion.",".$enlace.",".$destino;
|
$this->opciones[]=$tipo.",".$opcion.",".$enlace.",".$destino.",".$titulo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function insertaMenu()
|
public function insertaMenu()
|
||||||
@@ -40,9 +40,9 @@ class Menu {
|
|||||||
$salida="";
|
$salida="";
|
||||||
reset($this->opciones);
|
reset($this->opciones);
|
||||||
foreach($this->opciones as $opcion) {
|
foreach($this->opciones as $opcion) {
|
||||||
list($tipo,$opcion,$enlace,$destino)=explode(",",$opcion);
|
list($tipo,$opcion,$enlace,$destino,$titulo)=explode(",",$opcion);
|
||||||
if ($tipo==2)
|
if ($tipo==2)
|
||||||
$salida.='<a href="'.$enlace.'" target="'.$destino.'">'.$opcion.'</a><br />';
|
$salida.='<a href="'.$enlace.'" target="'.$destino.'" title="'.$titulo.'">'.$opcion.'</a><br />';
|
||||||
else
|
else
|
||||||
$salida.='<label class="key">'.$opcion.'</label><br/>';
|
$salida.='<label class="key">'.$opcion.'</label><br/>';
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
1|Maestros|
|
1|Maestros|
|
||||||
2|Ubicaciones|index.php?ubicaciones&opc=inicial|_self
|
2|Ubicaciones|index.php?ubicaciones&opc=inicial|_self|Ubicaciones del centro
|
||||||
2|Artículos|index.php?articulos&opc=inicial|_self
|
2|Artículos|index.php?articulos&opc=inicial|_self|Artículos inventariados
|
||||||
2|Elementos|index.php?elementos&opc=inicial&orden=ubicacion&sentido=asc|_self
|
2|Elementos|index.php?elementos&opc=inicial&orden=ubicacion&sentido=asc|_self|Artículos distribuidos en las ubicaciones
|
||||||
2|Usuarios|index.php?usuarios&opc=inicial|_self
|
2|Usuarios|index.php?usuarios&opc=inicial|_self|Usuarios autorizados a utilizar la aplicación
|
||||||
1|Inventario|
|
1|Inventario|
|
||||||
2|Ubicación|index.php?informeInventario&opc=Ubicacion|_self
|
2|Ubicación|index.php?informeInventario&opc=Ubicacion|_self|Inventario de una ubicación
|
||||||
2|Artículo|index.php?informeInventario&opc=Articulo|_self
|
2|Artículo|index.php?informeInventario&opc=Articulo|_self|Inventario de un Artículo
|
||||||
2|Total|index.php?informeInventario&opc=Total|_blank
|
2|Total|index.php?informeInventario&opc=Total|_blank|Inventario de todas las ubicaciones
|
||||||
2|Descuadres|index.php?descuadres|_blank
|
2|Descuadres|index.php?descuadres|_blank|Diferencias entre artículos y elementos
|
||||||
1|Varios|
|
1|Varios|
|
||||||
2|Configuración|index.php?configuracion|_self
|
2|Configuración|index.php?configuracion|_self|Opciones configurables de la aplicación
|
||||||
|
2|Importación|index.php?importacion&opc=form|_self|Importa datos de una hoja de cálculo
|
@@ -3,8 +3,8 @@
|
|||||||
<Titulo Texto="{Descripcion}" />
|
<Titulo Texto="{Descripcion}" />
|
||||||
<Datos>
|
<Datos>
|
||||||
<Consulta>
|
<Consulta>
|
||||||
select A.id as id,U.Descripcion as ubicacion,E.fechaCompra as fechaCompra,
|
select A.id as id,U.Descripcion as ubicacion,E.numserie as numserie,
|
||||||
E.numSerie as numserie,E.Cantidad as cantidad
|
E.fechaCompra as fechaCompra,E.Cantidad as cantidad
|
||||||
from Elementos E, Articulos A, Ubicaciones U where A.id=E.id_Articulo and U.id=E.id_Ubicacion
|
from Elementos E, Articulos A, Ubicaciones U where A.id=E.id_Articulo and U.id=E.id_Ubicacion
|
||||||
and A.id='{id}' order by U.Descripcion,numserie;
|
and A.id='{id}' order by U.Descripcion,numserie;
|
||||||
</Consulta>
|
</Consulta>
|
||||||
|
23
xml/inventarioArticuloCSV.xml
Normal file
23
xml/inventarioArticuloCSV.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<Informe>
|
||||||
|
<Titulo Texto="{Descripcion}" id="{id}"/>
|
||||||
|
<Datos>
|
||||||
|
<Consulta>
|
||||||
|
select A.id as id,E.id as idEl, U.id as idUb,U.Descripcion as ubicacion,E.numserie as numserie,
|
||||||
|
E.fechaCompra as fechaCompra,E.Cantidad as cantidad
|
||||||
|
from Elementos E, Articulos A, Ubicaciones U where A.id=E.id_Articulo and U.id=E.id_Ubicacion
|
||||||
|
and A.id='{id}' order by U.Descripcion,numserie;
|
||||||
|
</Consulta>
|
||||||
|
</Datos>
|
||||||
|
<Pagina Orientacion="P" Formato="A4">
|
||||||
|
<Cabecera>Inventario de Articulo</Cabecera>
|
||||||
|
<Cuerpo>
|
||||||
|
<Col Nombre="idEl" Ancho="10" Ajuste="R" Titulo="idElem"/>
|
||||||
|
<Col Nombre="idUb" Ancho="10" Ajuste="R" Titulo="idUbic"/>
|
||||||
|
<Col Nombre="ubicacion" Ancho="80" Ajuste="L" Titulo="Ubicación"/>
|
||||||
|
<Col Nombre="numserie" Ancho="40" Ajuste="L" Titulo="N Serie"/>
|
||||||
|
<Col Nombre="fechaCompra" Ancho="40" Ajuste="L" Titulo="Fecha C." />
|
||||||
|
<Col Nombre="cantidad" Ancho="20" Ajuste="D" Titulo="Cantidad" Total="S"/>
|
||||||
|
</Cuerpo>
|
||||||
|
</Pagina>
|
||||||
|
</Informe>
|
25
xml/inventarioUbicacionCSV.xml
Normal file
25
xml/inventarioUbicacionCSV.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<Informe>
|
||||||
|
<Titulo Texto="{Descripcion}" id="{id}"/>
|
||||||
|
<Datos>
|
||||||
|
<Consulta>
|
||||||
|
select A.id as id,E.id as idEl,A.Marca as marca,A.Modelo as modelo,E.numSerie as numserie,
|
||||||
|
E.fechaCompra as fechaCompra,A.Descripcion as descripcion,E.Cantidad as cantidad
|
||||||
|
from Elementos E, Articulos A, Ubicaciones U where A.id=E.id_Articulo and U.id=E.id_Ubicacion
|
||||||
|
and U.id='{id}' order by A.descripcion;
|
||||||
|
</Consulta>
|
||||||
|
</Datos>
|
||||||
|
<Pagina Orientacion="L" Formato="A4">
|
||||||
|
<Cabecera>Inventario de Ubicacion</Cabecera>
|
||||||
|
<Cuerpo>
|
||||||
|
<Col Nombre="idEl" Ancho="10" Ajuste="R" Titulo="idElem"/>
|
||||||
|
<Col Nombre="id" Ancho="10" Ajuste="R" Titulo="idArt"/>
|
||||||
|
<Col Nombre="descripcion" Ancho="70" Ajuste="L" Titulo="Artículo"/>
|
||||||
|
<Col Nombre="marca" Ancho="50" Ajuste="L" Titulo="Marca"/>
|
||||||
|
<Col Nombre="modelo" Ancho="50" Ajuste="L" Titulo="Modelo"/>
|
||||||
|
<Col Nombre="numserie" Ancho="40" Ajuste="L" Titulo="N Serie"/>
|
||||||
|
<Col Nombre="fechaCompra" Ancho="35" Ajuste="L" Titulo="Fecha C." />
|
||||||
|
<Col Nombre="cantidad" Ancho="20" Ajuste="D" Titulo="Cantidad" Total="S"/>
|
||||||
|
</Cuerpo>
|
||||||
|
</Pagina>
|
||||||
|
</Informe>
|
Reference in New Issue
Block a user