Gestión Centros de Atención Primaria
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"
|
||||
xmlns:p="http://primefaces.org/ui" xmlns:o="http://omnifaces.org/ui">
|
||||
|
||||
<ui:composition template="../header.xhtml">
|
||||
<ui:define name="content">
|
||||
<h:form id="frmListCAPs">
|
||||
<p:growl id="mesgsList" showDetail="true" closable="true" autoupdate="true" />
|
||||
<p:panel id="PanelCAPList" style="margin-bottom: 10px;" header="Gestión de Centros de Atención Primaria">
|
||||
<div class="ui-g ui-fluid">
|
||||
<p:dataTable widgetVar="dtCAP" var="cap" value="#{ManageHealthCareCenters.lazyDataModelCAPsList}" editable="true" lazy="true" paginator="true" rows="10" paginatorPosition="bottom" paginatorAlwaysVisible="true"
|
||||
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}" rowsPerPageTemplate="10,20,30,40">
|
||||
|
||||
<p:ajax event="rowEdit" listener="#{ManageHealthCareCenters.onRowEdit}" update=":frmListCAPs:mesgsList" />
|
||||
<p:ajax event="rowEditCancel" listener="#{ManageHealthCareCenters.onRowCancel}" update=":frmListCAPs:mesgsList" />
|
||||
|
||||
<p:column headerText="Nombre">
|
||||
<p:cellEditor>
|
||||
<f:facet name="output"><h:outputText value="#{cap.name}" /></f:facet>
|
||||
<f:facet name="input"><p:inputText value="#{cap.name}" style="width:100%" label="Nombre"/></f:facet>
|
||||
</p:cellEditor>
|
||||
</p:column>
|
||||
|
||||
<p:column headerText="Localización">
|
||||
<p:cellEditor>
|
||||
<f:facet name="output"><h:outputText value="#{cap.location}" /></f:facet>
|
||||
<f:facet name="input"><p:inputText value="#{cap.location}" style="width:100%" label="Localización"/></f:facet>
|
||||
</p:cellEditor>
|
||||
</p:column>
|
||||
|
||||
<p:column style="width:80px; text-align: center;" headerText="Editar">
|
||||
<p:rowEditor />
|
||||
</p:column>
|
||||
|
||||
<p:column style="width:80px; text-align: center;" headerText="Eliminar">
|
||||
<p:commandLink onclick="if (! confirm('¿Seguro que quiere borrar el CAP?')) return false" ajax="true" action="#{ManageHealthCareCenters.deleteDataById(cap.id)}" update=":frmListCAPs:mesgsList frmListCAPs" process="frmListCAPs">
|
||||
<span title="Borrar" class="fa fa-remove"/>
|
||||
</p:commandLink>
|
||||
</p:column>
|
||||
|
||||
<f:facet name="paginatorBottomLeft">
|
||||
<p:commandButton type="button" icon="pi pi-refresh" onclick="PF('dtCAP').getPaginator().setPage(0);" />
|
||||
</f:facet>
|
||||
</p:dataTable>
|
||||
</div>
|
||||
</p:panel>
|
||||
</h:form>
|
||||
|
||||
<h:form id="frmAddCAPs">
|
||||
<p:growl id="mesgsAdd" showDetail="true" closable="true" autoupdate="true" />
|
||||
<p:panel id="PanelNewCAP" header="Alta de Centros de Atención Primaria">
|
||||
<div class="ui-g ui-fluid">
|
||||
<div class="ui-g-1 ui-md-1">
|
||||
<p:outputLabel value="Nombre: " for="nameNew" />
|
||||
</div>
|
||||
<div class="ui-g-2 ui-md-2">
|
||||
<p:inputText id="nameNew" value="#{ManageHealthCareCenters.name}" readonly="false" required="ManageHealthCareCenters.medicalSpecialty.name eq null"
|
||||
requiredMessage="Es necesario un nombre" />
|
||||
</div>
|
||||
<div class="ui-g-1 ui-md-1">
|
||||
<p:outputLabel value="Localización: " for="locationNew" />
|
||||
</div>
|
||||
<div class="ui-g-6 ui-md-6">
|
||||
<p:inputText id="locationNew" value="#{ManageHealthCareCenters.location}" readonly="false" required="ManageHealthCareCenters.medicalSpecialty.location eq null"
|
||||
requiredMessage="Es necesaria una localización" />
|
||||
</div>
|
||||
<div class="ui-g-2 ui-md-2 ">
|
||||
<p:commandButton validateClient="true" value="Crear" update="mesgsAdd frmListCAPs frmAddCAPs" action="#{ManageHealthCareCenters.insertData}" icon="pi pi-check" />
|
||||
</div>
|
||||
</div>
|
||||
</p:panel>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
</ui:composition>
|
||||
</html>
|
||||
@@ -43,7 +43,7 @@
|
||||
</div>
|
||||
<div class="ui-g-1 ui-md-1 ">
|
||||
<p:commandButton validateClient="true" value="Borrar" update="frmEditSpecialties" action="#{ManageSpecialities.deleteData}" icon="fa fa-remove">
|
||||
<p:confirm header="Confirme la eliminación" message="¿Esta seguro de que desea eliminar la especialidad?" icon="pi pi-exclamation-triangle" />
|
||||
<p:confirm header="Confirme la eliminación" message="¿Está seguro de que desea eliminar la especialidad?" icon="pi pi-exclamation-triangle" />
|
||||
</p:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package ejb.systemAdmin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ejb.EJB;
|
||||
@@ -12,6 +13,7 @@ import TO.FamilyDoctorTO;
|
||||
import TO.LoggedUserTO;
|
||||
import TO.MedicalSpecialtyTO;
|
||||
import TO.PatientTO;
|
||||
import TO.PrimaryHealthCareCenterTO;
|
||||
import TO.SpecialistDoctorTO;
|
||||
import common.Constants;
|
||||
import common.HashUtils;
|
||||
@@ -19,6 +21,7 @@ import common.UserType;
|
||||
import ejb.common.CommonFacadeLocal;
|
||||
import jpa.AdministratorJPA;
|
||||
import jpa.MedicalSpecialtyJPA;
|
||||
import jpa.PrimaryHealthCareCenterJPA;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -148,10 +151,81 @@ public class SystemAdminFacadeBean implements SystemAdminFacadeRemote {
|
||||
|
||||
@Override
|
||||
public MedicalSpecialtyTO insertSpecialty(String name, String description) throws Exception {
|
||||
|
||||
MedicalSpecialtyJPA ms = new MedicalSpecialtyJPA(name, description);
|
||||
entman.persist(ms);
|
||||
|
||||
return this.commonServices.getPOJOforMedicalSpecialtyJPA(ms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrimaryHealthCareCenterTO updateHealthCareCenter(int id, String name, String location) throws Exception {
|
||||
PrimaryHealthCareCenterJPA ms = entman.find(PrimaryHealthCareCenterJPA.class, id);
|
||||
|
||||
if (ms == null) {
|
||||
throw new Exception("No se pueden actualizar los datos del CAP porque no se encuentra en la base de datos ningún registro con id: " + String.valueOf(id));
|
||||
}
|
||||
|
||||
ms.setName(name);
|
||||
ms.setLocation(location);
|
||||
|
||||
entman.persist(ms);
|
||||
|
||||
return this.commonServices.getPOJOforPrimaryHealthCareCenterJPA(ms);
|
||||
}
|
||||
|
||||
public PrimaryHealthCareCenterTO findHealthCareCenterByName(String searchedName) {
|
||||
TypedQuery<PrimaryHealthCareCenterJPA> query = entman.createQuery("from PrimaryHealthCareCenterJPA cap where cap.name=:name", PrimaryHealthCareCenterJPA.class);
|
||||
query.setMaxResults(1);
|
||||
query.setParameter("name", searchedName);
|
||||
|
||||
List<PrimaryHealthCareCenterJPA> results = query.getResultList();
|
||||
if (results.size() > 0)
|
||||
return this.commonServices.getPOJOforPrimaryHealthCareCenterJPA(results.get(0));
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteHealthCareCenter(int id) throws Exception {
|
||||
PrimaryHealthCareCenterJPA cap = entman.find(PrimaryHealthCareCenterJPA.class, id);
|
||||
|
||||
if (cap == null) {
|
||||
throw new Exception("No se puede borrar el CAP porque no se encuentra en la base de datos ningún registro con id: " + String.valueOf(id));
|
||||
}
|
||||
|
||||
entman.remove(cap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrimaryHealthCareCenterTO insertHealthCareCenter(String name, String location) throws Exception {
|
||||
PrimaryHealthCareCenterJPA cap = new PrimaryHealthCareCenterJPA(name, location);
|
||||
entman.persist(cap);
|
||||
|
||||
return this.commonServices.getPOJOforPrimaryHealthCareCenterJPA(cap);
|
||||
}
|
||||
|
||||
public Long getCAPCount() {
|
||||
TypedQuery<Long> query = entman.createQuery("SELECT count(1) from PrimaryHealthCareCenterJPA", Long.class);
|
||||
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
public List<PrimaryHealthCareCenterTO> listCAPsPaged(int pageNumber, int pageSize) {
|
||||
TypedQuery<PrimaryHealthCareCenterJPA> query = entman.createQuery("SELECT c from PrimaryHealthCareCenterJPA c order by c.name", PrimaryHealthCareCenterJPA.class);
|
||||
|
||||
if (pageSize > 0) {
|
||||
query.setFirstResult(pageNumber * pageSize);
|
||||
query.setMaxResults(pageSize);
|
||||
}
|
||||
|
||||
List<PrimaryHealthCareCenterJPA> allJPA = query.getResultList();
|
||||
List<PrimaryHealthCareCenterTO> caps = new ArrayList<PrimaryHealthCareCenterTO>();
|
||||
|
||||
for (PrimaryHealthCareCenterJPA item : allJPA) {
|
||||
caps.add(commonServices.getPOJOforPrimaryHealthCareCenterJPA(item));
|
||||
}
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package ejb.systemAdmin;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.ejb.Remote;
|
||||
|
||||
import TO.LoggedUserTO;
|
||||
import TO.MedicalSpecialtyTO;
|
||||
import TO.PrimaryHealthCareCenterTO;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -25,4 +28,16 @@ public interface SystemAdminFacadeRemote {
|
||||
public void deleteSpecialty(int id) throws Exception;
|
||||
|
||||
public MedicalSpecialtyTO insertSpecialty(String name, String description) throws Exception;
|
||||
|
||||
public PrimaryHealthCareCenterTO updateHealthCareCenter(int id, String name, String location) throws Exception;
|
||||
|
||||
public PrimaryHealthCareCenterTO findHealthCareCenterByName(String name);
|
||||
|
||||
public void deleteHealthCareCenter(int id) throws Exception;
|
||||
|
||||
public PrimaryHealthCareCenterTO insertHealthCareCenter(String name, String location) throws Exception;
|
||||
|
||||
public Long getCAPCount();
|
||||
|
||||
public List<PrimaryHealthCareCenterTO> listCAPsPaged(int pageNumber, int pageSize);
|
||||
}
|
||||
@@ -42,7 +42,7 @@ public class MenuMBean implements Serializable {
|
||||
subMenu = new DefaultSubMenu("Administración del sistema", "fa fa-wrench");
|
||||
|
||||
subMenu.addElement(createMenuItem("Gestionar especialidades", "fa fa-file-text-o", "/systemAdmin/ManageSpecialties", null));
|
||||
subMenu.addElement(createMenuItem("Centros At. Primaria", "fa fa-hospital-o", "/systemAdmin/ManageSpecialties", null));
|
||||
subMenu.addElement(createMenuItem("Centros At. Primaria", "fa fa-hospital-o", "/systemAdmin/ManageHealthCareCenters", null));
|
||||
subMenu.addElement(new DefaultSeparator());
|
||||
subMenu.addElement(createMenuItem("Ver médicos de un CAP", "fa fa-medkit", "/systemAdmin/ManageSpecialties", null));
|
||||
subMenu.addElement(new DefaultSeparator());
|
||||
|
||||
@@ -83,7 +83,7 @@ public class LoginMBean extends ManagedBeanBase {
|
||||
|
||||
// logout event, invalidate session
|
||||
public String logout() {
|
||||
this.addFacesMessageKeep(FacesMessage.SEVERITY_INFO, "Sessión cerrada", "Ha cerrado correctament su ssesión. Hasta la vista");
|
||||
this.addFacesMessageKeep(FacesMessage.SEVERITY_INFO, "Sessión cerrada", "Ha cerrado correctament su sesión. Hasta la vista");
|
||||
|
||||
SessionUtils.DestroySession();
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package managedbean.systemAdmin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.faces.view.ViewScoped;
|
||||
import javax.faces.application.FacesMessage;
|
||||
import javax.faces.context.FacesContext;
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.primefaces.event.RowEditEvent;
|
||||
import org.primefaces.model.LazyDataModel;
|
||||
import org.primefaces.model.SortOrder;
|
||||
|
||||
import TO.PrimaryHealthCareCenterTO;
|
||||
import managedbean.common.ManagedBeanBase;
|
||||
|
||||
@Named("ManageHealthCareCenters")
|
||||
@ViewScoped
|
||||
public class ManageHealthCareCentersMBean extends ManagedBeanBase implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Integer id;
|
||||
private String name;
|
||||
private String location;
|
||||
private PrimaryHealthCareCenterTO primaryHealthCareCenter;
|
||||
private LazyDataModel<PrimaryHealthCareCenterTO> lazyDataModelCAPsList;
|
||||
|
||||
public ManageHealthCareCentersMBean() {
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
this.lazyDataModelCAPsList = new LazyDataModel<PrimaryHealthCareCenterTO>() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public List<PrimaryHealthCareCenterTO> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
|
||||
Long totalRowCount = getRemoteManagerSystemAdmin().getCAPCount();
|
||||
this.setRowCount(totalRowCount.intValue());
|
||||
|
||||
return getRemoteManagerSystemAdmin().listCAPsPaged((first / pageSize), pageSize);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public LazyDataModel<PrimaryHealthCareCenterTO> getlazyDataModelCAPsList() {
|
||||
return lazyDataModelCAPsList;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
private void showPrimaryHealthCareCenterData(PrimaryHealthCareCenterTO cap) {
|
||||
this.primaryHealthCareCenter = cap;
|
||||
|
||||
if (cap == null) {
|
||||
this.id = null;
|
||||
this.name = "";
|
||||
this.location = "";
|
||||
} else {
|
||||
this.id = cap.getId();
|
||||
this.name = cap.getName();
|
||||
this.location = cap.getLocation();
|
||||
}
|
||||
}
|
||||
|
||||
public PrimaryHealthCareCenterTO getPrimaryHealthCareCenter() {
|
||||
return primaryHealthCareCenter;
|
||||
}
|
||||
|
||||
public void setPrimaryHealthCareCenter(PrimaryHealthCareCenterTO value) {
|
||||
this.primaryHealthCareCenter = value;
|
||||
}
|
||||
|
||||
public void onRowEdit(RowEditEvent event) {
|
||||
int error = 0;
|
||||
|
||||
if (((PrimaryHealthCareCenterTO) event.getObject()).getName() == null || ((PrimaryHealthCareCenterTO) event.getObject()).getName().trim().length() == 0) {
|
||||
this.addFacesMessage(FacesMessage.SEVERITY_WARN, "Nombre no relleno", "Por favor, escriba un nombre para el centro.");
|
||||
error++;
|
||||
}
|
||||
if (((PrimaryHealthCareCenterTO) event.getObject()).getLocation() == null || ((PrimaryHealthCareCenterTO) event.getObject()).getLocation().trim().length() == 0) {
|
||||
this.addFacesMessage(FacesMessage.SEVERITY_WARN, "Localización no rellena", "Por favor, escriba una localización.");
|
||||
error++;
|
||||
}
|
||||
|
||||
if (error == 0) {
|
||||
try {
|
||||
this.getRemoteManagerSystemAdmin().updateHealthCareCenter(((PrimaryHealthCareCenterTO) event.getObject()).getId(), ((PrimaryHealthCareCenterTO) event.getObject()).getName(),
|
||||
((PrimaryHealthCareCenterTO) event.getObject()).getLocation());
|
||||
|
||||
this.showPrimaryHealthCareCenterData(null);
|
||||
|
||||
FacesMessage msg = new FacesMessage("CAP editado", ((PrimaryHealthCareCenterTO) event.getObject()).getName());
|
||||
FacesContext.getCurrentInstance().addMessage(null, msg);
|
||||
} catch (Exception e) {
|
||||
this.manageException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onRowCancel(RowEditEvent event) {
|
||||
FacesMessage msg = new FacesMessage("Edición cancelada", ((PrimaryHealthCareCenterTO) event.getObject()).getName());
|
||||
FacesContext.getCurrentInstance().addMessage(null, msg);
|
||||
}
|
||||
|
||||
public void deleteDataById(Integer id) throws IOException {
|
||||
try {
|
||||
this.getRemoteManagerSystemAdmin().deleteHealthCareCenter(id);
|
||||
this.showPrimaryHealthCareCenterData(null);
|
||||
|
||||
this.addFacesMessage(FacesMessage.SEVERITY_INFO, "El CAP se ha borrado", "Los datos del Centro de Atención Primaria se han borrado correctamente.");
|
||||
} catch (Exception e) {
|
||||
this.manageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void insertData() {
|
||||
int error = 0;
|
||||
|
||||
if (name == null || name.trim().length() == 0) {
|
||||
this.addFacesMessage(FacesMessage.SEVERITY_WARN, "Nombre no relleno", "Por favor, escriba un nombre para el centro.");
|
||||
error++;
|
||||
}
|
||||
if (location == null || location.trim().length() == 0) {
|
||||
this.addFacesMessage(FacesMessage.SEVERITY_WARN, "Localización no rellena", "Por favor, escriba una localización.");
|
||||
error++;
|
||||
}
|
||||
|
||||
if (this.getRemoteManagerSystemAdmin().findHealthCareCenterByName(name) != null) {
|
||||
this.addFacesMessage(FacesMessage.SEVERITY_WARN, "CAP ya existente", "El CAP ya se encuentra en la base de datos");
|
||||
error++;
|
||||
}
|
||||
|
||||
if (error == 0) {
|
||||
try {
|
||||
this.getRemoteManagerSystemAdmin().insertHealthCareCenter(name, location);
|
||||
this.showPrimaryHealthCareCenterData(null);
|
||||
|
||||
this.addFacesMessage(FacesMessage.SEVERITY_INFO, "Los datos se han guardado", "Los datos del Centro de Atención Primaria se han guardado correctamente.");
|
||||
} catch (Exception e) {
|
||||
this.manageException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import javax.inject.Named;
|
||||
|
||||
import TO.LoggedUserTO;
|
||||
import TO.MedicalSpecialtyTO;
|
||||
import common.Constants;
|
||||
import managedbean.common.ManagedBeanBase;
|
||||
import managedbean.common.SessionUtils;
|
||||
|
||||
@@ -51,7 +50,7 @@ public class ManageSpecialitiesMBean extends ManagedBeanBase implements Serializ
|
||||
}
|
||||
|
||||
private void refreshFormData() {
|
||||
this.medicalSpecialitiesList = this.getRemoteManagerCommon().listMedicalSpecialitiesPaged(0, Constants.MAX_ITEMS_AUTOCOMPLETE_SEARCH);
|
||||
this.medicalSpecialitiesList = this.getRemoteManagerCommon().listAllMedicalSpecialities();
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
@@ -118,9 +117,7 @@ public class ManageSpecialitiesMBean extends ManagedBeanBase implements Serializ
|
||||
|
||||
if (error == 0) {
|
||||
try {
|
||||
MedicalSpecialtyTO ms = this.getRemoteManagerSystemAdmin().updateSpecialty(this.medicalSpecialty.getId(), this.medicalSpecialty.getName(),
|
||||
this.medicalSpecialty.getDescription());
|
||||
|
||||
this.getRemoteManagerSystemAdmin().updateSpecialty(this.medicalSpecialty.getId(), this.medicalSpecialty.getName(),this.medicalSpecialty.getDescription());
|
||||
this.showSpecialtyData(null);
|
||||
this.refreshFormData();
|
||||
|
||||
@@ -149,7 +146,7 @@ public class ManageSpecialitiesMBean extends ManagedBeanBase implements Serializ
|
||||
this.showSpecialtyData(null);
|
||||
this.refreshFormData();
|
||||
|
||||
this.addFacesMessage(FacesMessage.SEVERITY_INFO, "Los especialidad se ha borrado", "Los datos de la especialidad se han borrado correctamente.");
|
||||
this.addFacesMessage(FacesMessage.SEVERITY_INFO, "La especialidad se ha borrado", "Los datos de la especialidad se han borrado correctamente.");
|
||||
} catch (Exception e) {
|
||||
this.manageException(e);
|
||||
}
|
||||
@@ -175,8 +172,7 @@ public class ManageSpecialitiesMBean extends ManagedBeanBase implements Serializ
|
||||
|
||||
if (error == 0) {
|
||||
try {
|
||||
MedicalSpecialtyTO ms = this.getRemoteManagerSystemAdmin().insertSpecialty(name, description);
|
||||
|
||||
this.getRemoteManagerSystemAdmin().insertSpecialty(name, description);
|
||||
this.showSpecialtyData(null);
|
||||
this.refreshFormData();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user