Skip to content

Manual técnico (GM Almacenes)

Manual técnico
GM - Almacenes

Fecha: 29 de agosto de 2024
Versión: Ver. 1.0
Proyecto: GM-Almacenes

Elaborado por:

Certuit Software S. de R.L de C.V.
Cerro de las Campanas No. 384, Insurgentes Oeste, CP. 21280

Mexicali, BC. México

Historial de Cambios

FechaSeccionesRazón de CambiosAutor(es)Revisor(es)
13 de agosto de 2022TODASCreación del documentoSilvia Tolosa
2 de septiembre del 2024TODASActualización de contenido y formatoSilvia Tolosa

Introducción

Descripción del Proyecto

GM Última Milla Sistema Web es una aplicación web diseñada para ser utilizada por el personal de las empresas de logística.

Tecnologías y Librerías Utilizadas

Se presenta el listado de tecnologías y sus versiones.

  • Frontend

    • NodeJS: v14.17.3
    • ReactJS: v17.0.1
    • React Router: v5.2.0
    • npm: v6.14.13
    • JavaScript
  • Backend

    • Springboot
    • Maven

    Librerías destacadas

Here API

Es usado para mostrar el mapa en Última Milla y calcular las rutas sugeridas a los operadores.

Material UI v4

Para componentes de vista.

Bootstrap

El proyecto se inició usando Bootstrap para los componentes de vista pero se fue migrando a Material UI, sin embargo, aún hay código de bootstrap.

Arquitectura del proyecto

La navegación dentro del proyecto se hace usando React Router. Se puede observar la configuración en el archivo App.js ubicado en la carpeta src en la raíz del proyecto.

Visualmente se accede a los procesos y catálogos por medio de una barra lateral.

Configuración Inicial del Proyecto

Frontend

Seguir los pasos descritos para configurar el proyecto en un ambiente de desarrollo (local):

  1. Clonar el Repositorio:
git clone http://190.9.53.4:3000/GM-Transport/almacenes-front.git
  1. Ejecutar el Proyecto:
npm start

Nota: No se requiere instalar librerias dado que ya se encuentran en el repositorio.

Esto abrirá la aplicación en http://localhost:3000.

Backend

Seguir los pasos descritos para configurar el proyecto en un ambiente de desarrollo (local):

  1. Clonar el Repositorio:
git clone http://190.9.53.4:3000/GM-Transport/almacenes-back.git
  1. Instalar IDE de preferencia:

Para la documentación utilizaremos el IDE de visual studio code, por lo cual seguiremos la documentación oficial para la instalación y ejecución de un proyecto en Spring Boot.

Los servicios estarán disponibles en la ruta http://localhost:8091.

Conexión con servicios

Rutas de servicios

El proyecto se divide en tres ramas principales: master, develop, y calidad. Las ramas calidad y develop están conectadas al ambiente de pruebas, mientras que la rama master a producción.

Las URLs de cada ambiente se encuentran en el archivo .env que se ubica en la raíz del proyecto.

Es importante mencionar que la forma de identificar qué base de datos se va consultar es a través del RFC del cliente. Para esto se manda el RFC como header en todas las peticiones de backend.

El backend de WEBDEV es en su mayoría solo utilizado para tema de reportes. Ahí se almacenan los formatos de impresión y se consultan. También se realizan las acciones relacionadas con el SAT como timbrado. El resto de flujos se hacen con el backend de Spring Boot.

Organización de Directorios

Frontend

La estructura de directorios del proyecto está organizada de la siguiente manera:

Proyecto

  • scr: contiene los archivos del proyecto tal cual, componentes, paginas, etc.
    • Constants/index: contiene encabezados para peticiones http, plantillas para la impresión de etiquetas y otras configuraciones.
    • Files: directorio con los archivos usados para tutoriales.
    • iconos: directorio con las imágenes usadas en el sistema, desde logos, hasta iconos.
    • Util/Context: directorio con los archivos context que contienen las urls de los servicios.
    • Views: directorio que contiene todas los los archivos que corresponden a las vistas.
    • index.js: aplicación principal
    • routes.js: archivo que contiene las rutas de la barra lateral de navegación.
    • routesCatalogos.js: archivo que contiene las rutas de la sección de catálogos.
    • routesConfiguraciones.js: archivo que contiene las rutas de la sección a configuraciones.
  • .env: archivo que contiene las url de los diferentes ambientes. Cambia dependiendo la rama en la que se encuentre.
  • package.json: archivo que contiene las librerías necesarias para el proyecto.

Backend

La estructura de directorios del proyecto está organizada de la siguiente manera:

Proyecto

  • src: Contiene los archivos del proyecto como:
    • main/java/com/certuit/base
      • config: directorio con los archivos de configuración para la ejecución del proyecto.
      • domain: directorio con las clases auxiliares empleados por los servicios rest para el mapeo de datos.
      • endpoint: directorio que contiene todos los servicios rest disponibles.
      • services/base: directorio con las clases y funciones que contienen la lógica y reglas de negocio.
      • util: directorio con la utilería y archivo de conexión a la base de datos.
      • CertuitBaseApplicaction.java: clase main la cual ejecuta el proyecto.
    • resources: Contiene los recursos del proyecto
      • application.yml: archivo de configuración principal del proyecto.

Catálogos

Catálogo de Productos

Descripción general

Esta vista se desarrolla inicialmente en el archivo EstatusRecoleccion.js. Este catálogo permite ver los diferentes estatus que puede tener una recolección. Los registros son por defecto del sistema y no son editables para el usuario.

Inicialización de la vista

Al iniciar la vista se verifica la sesión del usuario. Si no hay sesión iniciada se redirige al login. Si hay sesión activa se hace una llamada al backend para obtener el listado de estatus de recolección.

Acciones del usuario

Asignar Productos

En la el menú lateral izquierdo, el componente de catálogos, tenemos el catálogo de Asignar productos, el cual seleccionamos el campo para seleccionar cliente, seteando una state booleano a verdadera para abrir un componente tipo Dialog que utilizamos en varias partes del sistema. Una vez seleccionado se precarga la información de productos relacionados al cliente seleccionado (en caso que dicho cliente tenga ya asignados dichos productos), para así desplegarse un componente “MultiSelect”, el cual se instaló la librería con anterioridad.
import MultiSelect from “@kenshooui/react-multi-select”;

<MultiSelect
items={allProductos}
selectedItems={*state.*productos}
onChange={handleOnChangeProductos}
messages= {{ searchPlaceholder: “Buscar…”, noItemsMessage: “Sin datos…”, noneSelectedMessage: “Ninguno seleccionado”, selectedMessage: “Seleccionado”, selectAllMessage: “Seleccionar todos”, clearAllMessage: “Limpiar todos”, }}
/>

Terminado de seleccionar los productos que se asignan o des asignan se guarda la información con el servicio:

  • Asignar producto a cliente
    • URL:
      • /almacen/api/Clientes/AsignarProductos
    • Body:
      • idCliente
      • Productos

El cual hace que corran 2 querys eliminando primero los registros anteriores para agregar los nuevos, terminando así el flujo.

Si la operación es exitosa se redirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Catálogo de Andenes

Descripción general

Esta vista se desarrolla inicialmente en el archivo Andenes.js. Este catálogo permite ver los diferentes andenes que puede tener una Sucursal. Los registros pueden crearse, modificarse, consultarse y eliminarse.

Inicialización de la vista

Al iniciar la vista se verifica la sesión del usuario. Si no hay sesión iniciada se redirige al login. Si hay sesión activa se hace una llamada al backend para obtener el listado de Andenes.

Acciones del usuario

Crear Andén

En el menú lateral izquierdo, el componente de catálogos, tenemos el catálogo de Andenes el cual nos permite crear, modificar y eliminar un andén, además de asignar un almacén en específico. Al llenar la información general del andén, se selecciona de un componente MultiSelect (anteriormente importado e instalada la librería), para asignarle el andén a los almacenes necesarios (los almacenes mostrados dependen de la sucursal seleccionada).

Terminado de seleccionar los almacenes que estará relacionado el ande que se asignan o des asignan se guarda la información (dependiendo si agrega o modifica), con los servicios:

  • Agregar andén
    • URL:
      • /almacen/api/Andenes/Agregar
    • Body:
      • nombreAnden
      • inicioAtencion
      • finAtencion
      • idSucursal
      • servicioLunes
      • servicioMartes
      • servicioMiercoles
      • servicioJueves
      • servicioViernes
      • servicioSabado
      • servicioDomingo
      • almacenes
      • activo
      • creadoEl
      • modificadoEl
      • creadoPor
      • modificadoPor
  • Modificar andén
    • URL:
      • /almacen/api/Andenes/Modificar/{idAnden}
    • Body:
      • nombreAnden
      • inicioAtencion
      • finAtencion
      • idSucursal
      • servicioLunes
      • servicioMartes
      • servicioMiercoles
      • servicioJueves
      • servicioViernes
      • servicioSabado
      • servicioDomingo
      • almacenes
      • activo
      • creadoEl
      • modificadoEl
      • creadoPor
      • modificadoPor

Si la operación es exitosa se redirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Agenda

Descripción general

En el menú lateral izquierdo, el componente de Agenda nos permite visualizar el listado de citas de Embarque o recibo, dependiendo del día.

Inicialización de la vista

Al iniciar la vista se verifica la sesión del usuario. Si no hay sesión iniciada se redirige al login. Si hay sesión activa se hace una llamada al backend para obtener el listado de citas de Recibo y/o Embarque. El servicio que se utiliza es el siguiente:

  • Obtener Agenda
    • URL:
      • /almacen/api/Agenda/GetListadoFiltro/{fechaInicial}/{fechaFinal}

La primera vez que entra carga el filtro con la fecha del día que se entra (solo busca por fecha inicial y final), además de filtrar dependiendo del RFC del Cliente.
En el servicio mencionado se ejecuta el procedimiento almacenado con el nombre de “usp_ProAgendaAndenAM”, el cual crea una tabla temporal para agregar las citas de embarque y recibo, para al final regresar el listado.

En la misma pantalla de listado también está la opción de exportar los registros.

Dentro del componente también se localiza la pestaña de Calendario, el cual es un componente donde se filtra por sucursal y andén. En este componente se utilizó una librería especial para el componente de calendario, el cual se instaló e importó en dicho componente.

import React, {useEffect, useRef, useState} from “react”;
const calendarRef = useRef();

Se setean por default los datos para la configuración de los datos del calendario:

const [calendarConfig, setCalendarConfig] = React.useState({
viewType: “Week”,
allowEventOverlap: false,
durationBarVisible: false,
timeRangeSelectedHandling: “Enabled”,
current: {
control: {
events: []
}
},
contextMenu: new DayPilot.Menu({
items: [
{
text: ”-”
}
]
}),
onBeforeEventRender: args => {
args.data.areas = [
{
top: 3,
right: 3,
width: 20,
height: 20,
symbol: “icons/daypilot.svg#minichevron-down-2”,
fontColor: “#fff”,
toolTip: “Show context menu”,
action: “ContextMenu”,
},
{
top: 3,
right: 25,
width: 20,
height: 20,
symbol: “icons/daypilot.svg#x-circle”,
fontColor: “#fff”,
action: “None”,
toolTip: “Borra evento”,
onClick: async args => {
const dp = calendarRef.current.control;
dp.events.remove(args.source);
}
}
];
},
onBeforeCellRender: args => {
if (args.cell.start.getDatePart() < DayPilot.Date.today()) {
args.cell.disabled = true;
args.cell.backColor = “#bcbcbc”;
args.cell.allowEventOverlap = false;
}
}
});

Y se obtienen las citas de la sucursal y/o anden con el servicio siguiente:

  • Obtener agenda por andén

    • URL:
      • /almacen/api/Agenda/GetListadoByAnden/{idAnden}

    Una vez obtenido los datos del servicio se setean en la constante antes declarada:

const getCitasPorAnden = (idAnden) => {
let arrayCitas = [];
let selectionDate = fechaActual;
obtenerListadoAgendaByAnden(idAnden).then(respuesta => {
//console.log(respuesta.data);
if (respuesta.data.length == 0) {
showSuccess(“No hay citas registradas para el andén”);
}else {
selectionDate = respuesta.data[0].FechaCita + “T” + respuesta.data[0].HoraCita;
respuesta.data.forEach((cita) => {
cita.id = cita.IdCita;
cita.text = cita.Folio + ”/” + cita.NombreCliente;
cita.start = cita.FechaCita + “T” + cita.HoraCita;
cita.end = cita.FechaCita + “T” + cita.HoraFin;
cita.participants = 1;
cita.backColor = cita.Tipo ? “#99FFA8” : “#9bd6fc”;
cita.moveDisabled = true
cita.allowEventOverlap = false
arrayCitas.push(cita);
})
}
setDataCitas(arrayCitas);
if(calendarRef.current !== undefined){
calendarRef.current.control.update({
events: arrayCitas,
startDate: selectionDate
});
}
});
}

Todo este componente es de consulta.

Acciones del usuario

Ninguna específica.

Citas

Descripción general

En el menú lateral izquierdo, el componente de Citas, se muestra el componente para visualizar el listado de citas. Las Citas sirven para proponer horarios para los Embarques o Recibo de productos.
El componente de Citas, se muestra el componente para visualizar el listado de citas teniendo las opciones de Agregar, Editar, consultar o cancelar la cita.

Inicialización de la vista

Al iniciar la vista se verifica la sesión del usuario. Si no hay sesión iniciada se redirige al login. Si hay sesión activa se hace una llamada al backend para obtener el listado de citas. El servicio que se utiliza es el siguiente:

  • Obtener agenda por andén
    • URL:
      • /almacen/api/Citas/GetListadoByFiltros/{folio}/{fechaCita}/{fechaInicial}/{fechaFinal}/{idEstatus}/{idSucursal}

Para la búsqueda existen filtros por fechas, sucursal, estatus, búsqueda por campo libre, específicamente nombre fiscal del cliente, clientes y código recibo.
Todos los registros del listado de citas tiene la opción para generar reporte y consultar (no importa su estatus), sin embargo dependiendo de ciertos parámetros permite realizar diferentes funciones. Por ejemplo:

  • En el caso de que el estatus de la cita no esté cancelada, confirmada, pendiente recibo, completada y completada con incidencia, se puede modificar la cita.

  • En el caso de que el estatus de la cita esté en estatus pendiente o pendiente confirmación, se podrá acceder a la función para reagendar.

  • En el caso de que el estatus de la cita esté en estatus pendiente o pendiente confirmación y este confirmador por almacén, se podrá entrar a confirmar la cita o reagendar.

  • En el caso de que la cita de recibo se desbloquee la función para Enviar Solicitud a Recibo.

    Acciones del usuario

Enviar Solicitud a Recibo

En la función para Enviar la solicitud a Recibo, que sería la siguiente:
const handleEnviarClick = (event) => {
event.stopPropagation();

*setState*({
...state,
*showAgregar*: *false*,
*showCancelar*: *false*
})
*confirmAlert*({
*title*: 'Confirmación envío de solicitud',
*message*: '¿Seguro que desea enviar la solicitud?',
*buttons*: \[
{
*label*: 'Sí',
*onClick*: () \=\> *handleEnviarSolicitud*(form)
},
{
*label*: 'No',
*onClick*: () \=\> *handleShowListado*()
}
\]
})
};

Se utiliza el componente confirmAlerta, anteriormente importado, el cual muestra un mensaje para proceder a correr la función “handleEnviarSolicitud” o la función para regresar al listado principal.
En la funcion “handleEnviarSolicitud” se realiza lo siguiente:

const *handleEnviarSolicitud* \= (*cita*) \=\> {
*enviarSolicitud*(*cita.idCita*, *getCurrentDateTime*())*.then*(({*data*}) \=\> {
*showSuccess*(data)
*handleShowListado*()
})*.catch*((*err*) \=\> {
*showSuccess*(err);
});
};

Llamando a llamar el servicio para enviar la solicitud:

  • Enviar solicitud a recibo
    • URL:
      • /almacen/api/CitaRecibo/EnviarSolicitud/{id}/{fecha}

Manda a llamar el procedimiento almacenado “usp_ProReciboAgregarPorCitaAM” el cual agrega el nuevo Registro de recibo en la tabla ProReciboAM. Después de correr dicho procedimiento lo que realiza es un update del estatus de la cita y envía la notificación al cliente.

Para finalizar en el front muestra mensaje de éxito o error, dependiendo de la respuesta del servicio (se utiliza la librería noty anteriormente importada e instalada, para mostrar los mensajes).

Si la operación es exitosa se redirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Reagendar Cita

Dentro del listado está la función para Reagendar cita, como antes se había mencionado la cual dependiendo del estatus podremos cambiar un state booleano a verdadero para abrir un componente Dialog que restablece fecha y la hora nueva de la cita (en caso sea requerido).

{dialogReagendar &&
<ReagendarCitaForm
open={dialogReagendar}
onSubmit={reagendarCitaSubmit}
seleccionada={*state.*seleccionado}
close={() => setDialogReagendar(false)}
/>

Al cual se le envían los props anteriores, siendo la función “reagendarCitaSubmit” la que manda a llamar el servicio para modificar dicha cita:

function reagendarCitaSubmit(params) {
let fechaActual= getCurrentDateTime().replace(“T”, ” ”);
if(*params.*fechaCitaNueva<fechaActual){
showError(“No se puede agregar una cita con fecha anterior al día de hoy.”)
return;
} else {
reagendarCitaAlmacen(*state.seleccionado.*IdCita, params).then((result) => {
showSuccess(*result.*data)
setDialogReagendar(false)
handleShowListado()
})
}
}

  • Reagendar cita
    • URL:
      • /almacen/api/Citas/ReagendarCitaAlmacen/{idCita}
    • Body:
      • folio
      • horaCita
      • fechaCita
      • horaCitaNueva
      • fechaCitaNueva

Si la operación es exitosa se redirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Consulta

Al ingresar al flujo de consulta de una cita se carga el servicio de consulta de la información de la cita:

  • Consultar cita
    • URL:
      • /almacen/api/Citas/GetById/{idCita}

Y se setean los datos de la cita en un state (en este caso llamado form), para así mostrar la pestaña de agregar, consultar y modificar:

function handleShowConsultar(row) {
obtenerCitaId(*row.*IdCita).then(({data}) => {
//console.log(data);
obtenerProveedorId(*data.*IdProveedor).then(respuestaProveedor => {
setForm({
…form,
idCita: *data.*IdCita,
folioCita: *data.*FolioCita,
idEstatusCita: *data.*IdEstatusCita,
idProveedor: *data.*IdProveedor,
proveedor:*respuestaProveedor.data.*NombreFiscal,
idCliente: *data.*IdCliente,
cliente: *data.*NombreFiscal,
fechaRegistro: *data.*FechaRegistro,
idTipoUnidad: *data.*IdTipoUnidad,
fechaCita: *data.*FechaCita,
horaCita: *data.*HoraInicioFormato,
ordenCompra: *data.*OrdenCompra,
idSucursal: *data.*IdSucursal,
comentariosGenerales: *data.*ComentariosGenerales,
estatusCita: *data.*EstatusCita,
sucursal: *data.*Sucursal,
confirmadoCitaCliente: *data.*ConfirmadoCitaCliente,
Remision: *data.*Remision,
codigoRecibo: *data.*CodigoRecibo,
horaFin: *data.*HoraFinFormato,
recibeMercancia: *data.*TipoCita ? “true” : “false”,
idAnden: *data.*IdAnden
});
setDataMercancia(*data.*ArrayProductosCitas);
getAndenesPorSucursal(*data.*IdSucursal);
setState({
…state,
disableMercancia: true,
agregar: “Consultar”,
consult:true,
showAgregar: true,
showCancelar: false
});
setDialogAgendar(false);
})

});
*$*(".nav-tabs li ")*.removeClass*("active");
*$*(".nav-tabs li")*.eq*(1)*.addClass*("active");
*$*(".tab-content div ")*.removeClass*("in show");
*$*("\#Agregar")*.addClass*("in show");
}

También se obtienen los datos del servicio dependiendo del proveedor del cliente, para setear el proveedor específico. El servicio sería el siguiente:

  • Obtener proveedor
    • URL:
      • /almacen/api/Proveedor/GetById/{idProveedor}

Al setear los datos se pueden observar la información general de la cita en la sección de arriba del componente:

Dentro del componente de Citas está el importado el componente Mercancías, donde está la información de las tablas y los productos (en el caso que sea cita de recibo).

Los props que se envian serian los siguientes:
<Mercancia
dataMercancia={dataMercancia}
onChangeList={handleListMercanciaChange}
disabled={*state.*consult}
consult={*state.*agregar}
idCliente={*form.*idCliente}
changeCliente={changeCliente}
disabledAgregarMercancia={*state.*disableMercancia}
/>

Descripción de los props:

  1. dataMercancia : los productos relacionados a la cita
  2. onChangeList: la función que se utiliza al querer cambiar la mercancía (en caso de agregar o editar).
  3. disabled: booleano que identifica si es una consulta (sirve para identificar entre consultar, agregar o editar)
  4. consult: sirve para identificar si es una consulta
  5. idCliente: sirve para identificar el id del cliente
  6. changeCliente: sirve para cambiar el cliente
  7. disabledAgregarMercancia: identifica si se puede agregar mercancía o no (esto en el caso que la cita sea de Embarque)

Finalmente los componentes están inaccesibles por ser una consulta, bloqueando o poniendo en disables la mayoría de los componentes.

Modificar

Al igual que el proceso de consulta se setean todos los datos mediante el uso del servicio que trae los datos de la cita dependiendo el id de dicha cita, la diferencia es que tenemos la posibilidad de modificar los campos, agregar o quitar información y guardar los cambios.

La diferencia es que en vez de utilizar el servicio de agregar cita, se utiliza el de modificar cita:

const handleAceptar = (e) => {

*e.preventDefault*();
let seRecibe \= *form.recibeMercancia* \=== "true";
let arrayMercancia \= \[\];
if(seRecibe) {
*dataMercancia.forEach*((*p*) \=\> {
*p.idCitaProducto* \= *p.IdCitaProducto*
*p.idProducto* \= *p.IdProducto*
*p.cantidad* \= *p.Cantidad*
*p.totalPallets* \= *p.TotalPallets*
*p.periodoCaducidad* \= *p.PeriodoCaducidad*
*p.observaciones* \= *p.Observaciones*
*arrayMercancia.push*(p)
})
}
let horaCita \= *form.horaCita.replace*(".0000000", "")
let horaFin \= *form.horaFin.replace*(".0000000", "")
let params \= {
*folioCita*: *form.folioCita*,
*idProveedor*: *form.idProveedor*,
*idCliente*: *form.idCliente*,
*fechaRegistro*: *form.fechaRegistro.replace*("T", " "),
*fechaCita*: *form.fechaCita*,
*horaCita*: horaCita,
*ordenCompra*: *form.ordenCompra*,
*idSucursal*: *form.idSucursal*,
*comentariosGenerales*: *form.comentariosGenerales*,
*proveedor*: *form.idProveedor*,
*arrayProductosCitas*: arrayMercancia,
*idEstatusCita*: *form.idEstatusCita*,
*idTipoUnidad*: *form.idTipoUnidad*,
*idUsuario*: *localStorage.getItem*("UsuarioId"),
*confirmadoCitaCliente*: *form.confirmadoCitaCliente*,
*remision*: *form.Remision*,
*codigoRecibo*: *form.codigoRecibo*,
*idAnden*: *form.idAnden*,
*recibeMercancia*: seRecibe,
*horaFin*: horaFin
};
let fechaActual \= *getCurrentDate*()*.replace*("T", " ");
if (*form.idCita* \!== 0 && \!*form.disponibleRecibo* && *form.idEstatusCita* \!== *configuracion.idCitaPendienteRecibo*) {
*modificarCita*(*form.idCita*, params)
*.then*(({*data*}) \=\> {
*setState*({
...state,
*aceptar*: *true*
})
if (*data.Estatus* \=== *true*) {
*showSuccess*("Modificado con éxito")
*handleShowListado*()
}
})*.catch*((*err*) \=\> {
*showSuccess*("No se pudo realizar la modificación.");
});
} else if (*arrayMercancia.length* \=== 0 && seRecibe) {
*showError*("Es necesario agregar mercancía")
*return*;
} else {
if (*form.fechaCita*\<fechaActual){
*showError*("No se pueden crear cita con fechas pasadas.")
*return*
}else{
*agregarCita*(params)
*.then*(({*data*}) \=\> {
*setState*({
...state,
*aceptar*: *true*
})
if (*data.Estatus* \=== *true*) {
*showSuccess*("Agregado con éxito")
*handleShowListado*();
*limpiarCamposAgregar*();
}
})*.catch*((*err*) \=\> {
*showSuccess*(err);
});
}
}
*getAllCitas*();
*$*(".nav-tabs li ")*.removeClass*("active");
*$*(".nav-tabs li")*.eq*(0)*.addClass*("active");
*$*(".tab-content div ")*.removeClass*("in show");
*$*("\#Listado")*.addClass*("in show");
};

y el servicio sería el siguiente:

  • Modificar cita
    • URL:
      • /almacen/api/Citas/Modificar/{idCita}
    • Body:
      • folioCita
      • idProveedor
      • idCliente
      • fechaRegistro
      • fechaCita
      • horaCita
      • ordenCompra
      • idSucursal
      • comentariosGenerales
      • proveedor
      • arrayProductosCitas
      • idEstatusCita
      • idTipoUnidad
      • idUsuario
      • confirmadoCitaCliente
      • remision
      • codigoRecibo
      • idAnden
      • recibeMercancia
      • horaFin

En el servicio primero ejecuta el procedimiento almacenado “usp_ProCitasProductosModificarAndenAM”, modificando los datos del registro de la tabla ProCitasAM, una vez ejecutado ese procedimiento se ejecuta el procedimiento “usp_ProCitasProductoAgregarAM”, el cual agrega los productos de las citas a la tabla ProCitasProductosAM, agregando el id de la cita y el id del producto para relacionarlos.

Para finalizar con un query se buscan los booleanos ConfirmadoCitaAlmacen y el ConfirmadoCitaCliente, para utilizarlos en un query para hacer un update a la cita modificada, y también modificando el estatus de la cita a Cita confirmada.

Si la operación es exitosa se redirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Agregar

Similar a los procesos anteriores, en este proceso se muestra la pestaña de Agregar, consultar y modificar, solo que esta vez no se carga ningún valor en especifico, solo los states de donde se selecciona la información, ya que en esta ocasión será el usuario quien llene la información de la cita.

Al llenar los datos se tendrán en cuenta varias validaciones y/o filtros, por ejemplo:

  • Al querer elegir el proveedor, está validado que dependiendo el cliente que se elija primero, precargara la información del servicio que trae los proveedores en el onChange del componente del cliente, si no se elige primero, el componente de proveedor con la información del select, estará vacía (el proveedor seria el creado por el cliente en los catálogos).
  • Al querer seleccionar un andén, de igual forma que con el proveedor, estará vacía la lista, ya que solo se setea la información mediante el onChange de la información del servicio, dependiendo de la sucursal

Una vez seleccionado el andén, se podrá elegir la fecha de la cita, el cual abrirá un componente tipo dialog, donde se verá lo siguiente:

Tiene la opción para volver a seleccionar el andén y dependiendo del horario configurado, mostrará los días disponibles y sus horarios de atención (esta información se llena en el módulo de catálogos en el catálogo de Andén). Dependiendo el color son los días que se pueden seleccionar, por ejemplo, el gris oscuro indica que no está disponible en ese día, el gris claro son días pasados y el blanco indica disponibilidad, además que se puede mostrar un campo coloreado de azul en caso de haber una cita programada.

En este componente se importó una librería para el calendario la cual se instaló con anterioridad:
import React, {useEffect, useRef} from “react”;

{
dialogAgendar &&
<AgendaCalendario
handleAddEvent={handleAddEvent}
dialogAgendar={dialogAgendar} closeDialogAgendar={closeDialogAgendar}
idAnden={*form.*idAnden}
idSucursal={*form.*idSucursal}
nombreCliente={*form.*cliente}
idCita={*form.*idCita}
diasAtencion={atencion}
close={() => setDialogAgendar(false)}
/>
}

Dependiendo si se hace click en el componente de la Fecha de la cita, se pondrá el boolean dialogAgendar y se abrirá el componente de AgendaCalendario.

Se utiliza una constante donde tendremos la información de nuestro calendario y con el ref iremos cambiando los valores correspondientes.

const calendarConfig = {
viewType: “Week”,
dynamicLoading: true,
allowEventOverlap: false,
durationBarVisible: false,
timeRangeSelectedHandling: “Enabled”,
autoRefreshInterval: 5,
autoRefreshMaxCount: 30,
autoRefreshEnabled: true,
heightSpec: “BusinessHoursNoScroll”,
businessBeginsHour: atencion.inicio.split(’:’)[0],
businessEndsHour: atencion.fin.split(’:’)[0],
onTimeRangeSelected: async (args) => {
const dp = args.control;
dp.clearSelection();
console.log(dp);
if(dp.events.list.length > 0) {
if(dp.events.list.slice(-1)[0].id == 1){
dp.events.update({
start: args.start,
end: args.end,
id: 1,
text: “Cita de ” + nombreCliente,
allowEventOverlap: false
});
}else{
dp.events.add({
start: args.start,
end: args.end,
id: 1,
text: “Cita de ” + nombreCliente,
allowEventOverlap: false
});
}
}else{
dp.events.add({
start: args.start,
end: args.end,
id: 1,
text: “Cita de ” + nombreCliente,
allowEventOverlap: false
});
}
dp.allowEventOverlap = false;
setDataEvento(args);
args.day = args.start;
handleTimeRangeSelected(args);
},
contextMenu: new DayPilot.Menu({
items: [
{
text: ”-”
},
{
text: “Editar…“,
onClick: async args => {
await editEvent(args);
}
}
]
}),
onBeforeEventRender: args => {
args.data.areas = [
{
top: 3,
right: 3,
width: 20,
height: 20,
symbol: “icons/daypilot.svg#minichevron-down-2”,
fontColor: “#fff”,
toolTip: “Show context menu”,
action: “ContextMenu”,
},
{
top: 3,
right: 25,
width: 20,
height: 20,
symbol: “icons/daypilot.svg#x-circle”,
fontColor: “#fff”,
action: “None”,
toolTip: “Borra evento”,
onClick: async args => {
const dp = args.control;
dp.events.remove(args.source);
}
}
];
},
onBeforeCellRender: args => {
let fecha = new Date(args.cell.start.getDatePart());
let diaInvalido = false;
switch (fecha.getDay()) {
case 0:
if(atencion.domingo == false){ diaInvalido = true;}
break;
case 1:
if(atencion.lunes == false){ diaInvalido = true;}
break;
case 2:
if(atencion.martes == false){ diaInvalido = true;}
break;
case 3:
if(atencion.miercoles == false){ diaInvalido = true;}
break;
case 4:
if(atencion.jueves == false){ diaInvalido = true;}
break;
case 5:
if(atencion.viernes == false){ diaInvalido = true;}
break;
case 6:
if(atencion.sabado == false){ diaInvalido = true;}
break;
}
if (diaInvalido || args.cell.start.getDatePart() < DayPilot.Date.today()) {
args.cell.properties.disabled = true;
args.cell.properties.backColor = diaInvalido ? “#5b5b5b” : “#999999”;
args.cell.properties.allowEventOverlap = false;
args.cell.properties.business = false;
}else{
args.cell.properties.business = true;
}
},
};

<div style={*styles.*wrap}>
<div style={*styles.*left}>
<DayPilotNavigator
selectMode={“Week”}
showMonths={2}
skipMonths={2}
startDate={fechaActual}
selectionDay={fechaActual}
onTimeRangeSelected={handleTimeRangeSelected}
locale={“es-mx”}
/>
</div>
<div style={*styles.*main}>
<DayPilotCalendar {…calendarConfig} ref={calendarRef}/>
</div>
</div>

Una vez seleccionado el día y hora de cita, se cierra el dialog y se setean los datos del día y las horas de inicio y fin de la cita, teniendo aún la opción de re elegir la fecha en los campos de las horas.

Una vez terminada de llenar la información general, tendremos un componente check y un radio button, los cuales nos servirán para pasos importantes dentro de la creación de la cita.

  1. Si confirmamos cita por parte del proveedor, no es necesaria la confirmación de la cita por parte del proveedor y al momento de crear la cita, la bandera de ConfirmadoCitaCliente sera verdadera.
  2. En la sección para las opciones de los radio, tenemos las opciones de Cita de Recibo de Mercancías y la Cita de Embarque de Mercancías. Si elegimos la cita de recibo, podremos seleccionar en la parte inferior la mercancía que queremos recibir y en el caso de elegir de embarque no podremos seleccionar ninguna mercancía y procedería el flujo en el componente de Embarque (del cual se verá más adelante).

Continuando con el caso de ser una Cita de Recibo de Mercancía, tendremos habilitado nuestro componente de Mercancías, el cual ya se importó en el componente de Citas. Aquí tendremos tres diferentes opciones para agregar nuestra mercancía.

  1. La primera opción seria de elegir un producto (que esté asignado al cliente, en el catálogo de Asignar Producto), el cual se podrá elegir con el botón AGREGAR MERCANCÍA, el cual pondra en verdadero un booleano para que se muestre el componente de “DialogoNuevoMercancia”, donde seleccionamos el producto a recibir, una vez elegido se setea la información necesaria del producto (mediante el servicio para consultar productos asignados al cliente), sin contar la cantidad, los Total Pallets, Emp x Tam y las observaciones. Estos campos se llenan manualmente, menos el Emp x Tam, ya que este es una funcion que se realiza al cambiar la cantidad del producto, utilizando un hook useEffect:
    useEffect(() => {
    if (*props.mercancia.*IdCitaProducto !== 0) {
    setMercancia(*props.*mercancia)
    setOpen(true);
    }
    if (*props.changeCliente.*change === true) {
    getAllProductos()
    } else {
    if (*props.*idCliente) {
    getAllProductos()
    }
    }
    }, [*props.*mercancia]) También están las opciones de editar o borrar el producto ya anteriormente agregado.
  2. La segunda opción sería con una importación de productos (el proceso de importar se verá más adelante) mediante una plantilla excel, la cual se podrá descargar con el icono de la flecha azul. Al momento de importar los productos, estos se verían en el listado de mercancía y pueden ser editados o modificados.
  3. La tercera y última opción, es la de crear un nuevo producto desde cero, el cual se visualiza al dar click AGREGAR PRODUCTO NUEVO, el cual hará que se puede visualizar el componente DialogoNuevoProducto, el cual nos pedirá toda la información del producto nuevo, además de la Cantidad, Observación y Empaques por Tarima (que es la información extra para agregar el producto a la cita). De igual manera estarán las opciones de editar y eliminar (tomar en cuenta que el producto que se agrega aquí, ya se estaría agregando al catálogo de productos, es decir CatProductos, ya que el servicio para agregar producto, también se usará una vez agregado el nuevo producto).

Para finalizar al darle al botón de Aceptar se enviará la información de la nueva cita al servicio:

const handleAceptar = (e) => {

*e.preventDefault*();
let seRecibe \= *form.recibeMercancia* \=== "true";
let arrayMercancia \= \[\];
if(seRecibe) {
*dataMercancia.forEach*((*p*) \=\> {
*p.idCitaProducto* \= *p.IdCitaProducto*
*p.idProducto* \= *p.IdProducto*
*p.cantidad* \= *p.Cantidad*
*p.totalPallets* \= *p.TotalPallets*
*p.periodoCaducidad* \= *p.PeriodoCaducidad*
*p.observaciones* \= *p.Observaciones*
*arrayMercancia.push*(p)
})
}
let horaCita \= *form.horaCita.replace*(".0000000", "")
let horaFin \= *form.horaFin.replace*(".0000000", "")
let params \= {
*folioCita*: *form.folioCita*,
*idProveedor*: *form.idProveedor*,
*idCliente*: *form.idCliente*,
*fechaRegistro*: *form.fechaRegistro.replace*("T", " "),
*fechaCita*: *form.fechaCita*,
*horaCita*: horaCita,
*ordenCompra*: *form.ordenCompra*,
*idSucursal*: *form.idSucursal*,
*comentariosGenerales*: *form.comentariosGenerales*,
*proveedor*: *form.idProveedor*,
*arrayProductosCitas*: arrayMercancia,
*idEstatusCita*: *form.idEstatusCita*,
*idTipoUnidad*: *form.idTipoUnidad*,
*idUsuario*: *localStorage.getItem*("UsuarioId"),
*confirmadoCitaCliente*: *form.confirmadoCitaCliente*,
*remision*: *form.Remision*,
*codigoRecibo*: *form.codigoRecibo*,
*idAnden*: *form.idAnden*,
*recibeMercancia*: seRecibe,
*horaFin*: horaFin
};
let fechaActual \= *getCurrentDate*()*.replace*("T", " ");
if (*form.idCita* \!== 0 && \!*form.disponibleRecibo* && *form.idEstatusCita* \!== *configuracion.idCitaPendienteRecibo*) {
*modificarCita*(*form.idCita*, params)
*.then*(({*data*}) \=\> {
*setState*({
...state,
*aceptar*: *true*
})
if (*data.Estatus* \=== *true*) {
*showSuccess*("Modificado con éxito")
*handleShowListado*()
}
})*.catch*((*err*) \=\> {
*showSuccess*("No se pudo realizar la modificación.");
});
} else if (*arrayMercancia.length* \=== 0 && seRecibe) {
*showError*("Es necesario agregar mercancía")
*return*;
} else {
if (*form.fechaCita*\<fechaActual){
*showError*("No se pueden crear cita con fechas pasadas.")
*return*
}else{
*agregarCita*(params)
*.then*(({*data*}) \=\> {
*setState*({
...state,
*aceptar*: *true*
})
if (*data.Estatus* \=== *true*) {
*showSuccess*("Agregado con éxito")
*handleShowListado*();
*limpiarCamposAgregar*();
}
})*.catch*((*err*) \=\> {
*showSuccess*(err);
});
}
}
*getAllCitas*();
*$*(".nav-tabs li ")*.removeClass*("active");
*$*(".nav-tabs li")*.eq*(0)*.addClass*("active");
*$*(".tab-content div ")*.removeClass*("in show");
*$*("\#Listado")*.addClass*("in show");
};
const *handleEnviarSolicitud* \= (*cita*) \=\> {
*enviarSolicitud*(*cita.idCita*, *getCurrentDateTime*())*.then*(({*data*}) \=\> {
*showSuccess*(data)
*handleShowListado*()
})*.catch*((*err*) \=\> {
*showSuccess*(err);
});
};

El servicio sería el siguiente:

  • Agregar cita
    • URL:
      • /almacen/api/Citas/Agregar
    • Body:
      • folioCita
      • idProveedor
      • idCliente
      • fechaRegistro
      • fechaCita
      • horaCita
      • ordenCompra
      • idSucursal
      • comentariosGenerales
      • proveedor
      • arrayProductosCitas
      • idEstatusCita
      • idTipoUnidad
      • idUsuario
      • confirmadoCitaCliente
      • remision
      • codigoRecibo
      • idAnden
      • recibeMercancia
      • horaFin

Que prácticamente es igual al de modificar pero esta vez sería agregar un nuevo registro a ProCitasAM y ProCitasProductosAM(en caso que sea cita de recibo).

Si la operación es exitosa se redirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Cancelación

Dependiendo del estatus de la Cita, esta podrá ser cancelada, mientras no esté ya cancelada, completada o en recibo, puede cancelarse. En el caso en que se elija cancelar en estatus de Cancelada se irá a la pestaña de Cancelar y estará todo disabled, para consultar el motivo de la cancelación.

Una vez elegimos cancelarla podremos visualizar el cliente que cancela, la fecha de cancelación, el Folio de la Cita el Usuario y el Estatus en que se encuentra la cita, solo tendremos el campo abierto de motivo para llenar manualmente. Una vez agregado el motivo se manda a llamar el servicio de cancelación, el cual sería el siguiente:

  • Cancelar cita
    • URL:
      • /almacen/api/Cita/Cancelar/{idCita}
    • Body:
      • motivoCancelacion
      • idUsusarioCancelacion
      • fechaCancelacion

Que lo que hace es cambiar el estatus de la cita y modificar los campos de MotivoCancelacion, FechaCancelacion y Usuario cancelación, al registro de la tabla ProCitasAM, finalizando así con el flujo de Cancelación en el módulo de Citas.

Si la operación es exitosa se redirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Recibo

Descripción general

En el menú lateral izquierdo, el componente de Recibo, se muestra el componente para visualizar el listado de recibo donde se tiene un filtro para buscar los registros dependiendo del estatus, fechas, entre otros.Este filtro se utiliza mediante un componente general (se utiliza para la mayoría de listados).
Otros flujos que tiene el componente de Recibo, son la consulta, general reporte, Documentar Inspección y Capturar Conteo (recibo y almacén).

Inicialización de la vista

Al iniciar la vista se verifica la sesión del usuario. Si no hay sesión iniciada se redirige al login. Si hay sesión activa se hace una llamada al backend para obtener el listado de Recibos.
Una vez seteada la información del servicio del listado de Recibos, podremos hacer los diferentes flujos, dependiendo del estatus del recibo, por ejemplo:

  • En el caso de consultar y generar reporte, siempre estarán disponibles sin importar su estatus.
  • Para entrar a Documentar Inspección, se debe seleccionar un registro del listado de recibo y si el estatus está en Pendiente Confirmar, se podrá documentar la inspección, pero en el caso de ya haber realizado la inspección, cuando se seleccione se podrá consultar la inspección que se realizó anteriormente.
  • Para acceder a la opción de Capturar conteo, el estatus del recibo debe de estar Aprobado por calidad, una vez seleccionado podrá realizar el conteo, cabe mencionar que hay 2 tipos de conteo, de recibo y almacén, al elegir la opción, primero solo podrá realizar el conteo de recibo y una vez terminado, se podrá volver acceder al conteo pero esta vez se podrá elegir conteo de almacén (El conteo de recibo se puede realizar cuantas veces sea necesario).

Acciones del usuario

Consulta

En el flujo de Consulta de Recibo primeros se selecciona el registro de la cita que se quiere consultar y en el icono del ojo activa la pestaña de Consultar, seteando un state con la información que trae el servicio siguiente:

  • Consultar recibo
    • URL:
      • /almacen/api/Recibo/GetById/{idRecibo}

Que trae la información de la tabla ProReciboAM, ProReciboProductosAM y las ubicaciones en el Almacén. Una vez seteados los datos se podrá visualizar la información del recibo (que sería casi la misma que se agregó, cuando se creó la cita ), así como también el listado de productos a recibir.


Para finalizar todos los componentes están desactivados, no se podrá realizar ningún flujo dentro de la pestaña.

Documentar Inspección

En el mismo listado de Recibo, al seleccionar un registro de recibo con el estatus Pendiente Confirmar, se desbloqueara la opción Documentar Inspección y en el onClick se mostrará la pestaña con el componente “InpeccionCalidadForm”, entrando al componentDidMount(), el cual carga el servicio de “http://190.9.53.4:8083/almacen/api/Recibo/GetById/” para setear la informacion en el state, una vez cargada esa información tomando en cuenta los Parámetros de Configuración (se veria más adelante), se obtienen también los cuestionarios de calidad de productos e unidades correspondientes a lo configurado en Parámetros. El servicio que se utilizan para obtener los Parametros seria “http://190.9.53.4:8083/almacen/api/ParametrosConfiguracion/GetParametros”, el cual trae todos los parametros que configura el cliente. Y con el “idCuestionarioReciboProducto“, mandamos a llamar el servicio de los cuestionarios:

  • Consultar recibo respuestas de inspección
    • URL:
      • /api/Recibo/ConsultarRespuestasInspeccion/{idRespuestaFormularioProducto}

Para así también setear los cuestionarios y que se muestran en la sección intermedia de la pestaña.

Más abajo podremos ya con el state seteado, se mostrará la información en un Datagrid, para mostrar la mercancía que se va a inspeccionar.
<Grid item sm={12}>
<div className=“widget-header”>
<h2>Mercancía</h2>
</div>
<div className=“row” style={{ height: `${((this*.state.data.ArrayProductosRecibo ? this.state.data.ArrayProductosRecibo.*length : 0) * 60) + 100}px`, width: “100%” }}>
<DataGrid
disableColumnMenu
disableColumnFilter
localeText={*esES.components.MuiDataGrid.defaultProps.localeText}
pageSize={10}
columns={columnsMercancia}
rowHeight={30}
rows={this
.state.data.*ArrayProductosRecibo ?? []}
disableSelectionOnClick={true}
getRowId={(row) => *row.*IdReciboProducto}
/>
</div>
</Grid>

Llenando la información que pide se podrá terminar la inspección, con el servicio para guardar las respuestas:

  • Guardar respuestas inspeccion recibo
    • URL:
      • /Recibo/GuardarRespuestasInspeccion
    • Body:
      • respuestaFormularios

Lo que hace el servicio es insertar en la tabla ProRespuestsFormularioAM y ProRespuestaContestada, para guardar las respuestas del cuestionario de la inspección.
Una vez terminado ese flujo pasamos a agregar la inspección de Recibo con el servicio:

  • Guardar inspección de recibo
    • URL:
      • /Recibo/AgregarInspeccion
    • Body:
      • data
      • resultado
      • comentarios

El cual agrega la inspección en la tabla “ProInspeccionReciboAM”, con el IdRecibo, Resultado y Comentarios. Dentro del mismo servicio buscamos el estatus dependiendo si fue aprobado o rechazado por calidad configurado en la tabla CatParametrosConfiguracionAM (que seria lo configurado en el módulo de parámetros configuración), para así cambiar el registro de la tabla ProReciboAM, cambiando el IdEstatus y el booleano Incidencia, en caso que haya sido aprobado con alguna incidencia. Para finalizar si elegimos un registro de recibo que el estatus sea diferente Aprobado de calidad, la inspección se podrá consultar solamente, bloqueando todo componente.

Conteo Recibo y Almacén

En el mismo listado de Recibo, al seleccionar un registro de recibo con el estatus Aprobado Calidad, se desbloqueara la opción Capturar Conteo y en el onClick se mostrará la pestaña con el componente “ConteoFormulario”, entrando al componentDidMount(), el cual carga el servicio de “http://190.9.53.4:8083/almacen/api/Recibo/GetById/” para setear la informacion en el state, seteando tambien el tipo de conteo que en el caso de ser recibo sería valor de “1” y en caso que sea almacén seria “2” (siempre que no se haya realizado ningún conteo de recibo, solo permite elegir conteo de recibo). Ya seleccionado el conteo de recibo, se podrá visualizar la información general del Recibo, además del listado de productos que se solicitaron al crear la cita y el recibo.

En el Datagrid que muestra el listado de productos, estará una columna con un campo para poder ingresar la Cantidad Recibo, que sería la cantidad de productos que el usuario de recibo haya contado, pudiendo ingresar una cantidad menor o mayor a la Cantidad original. En el onChange del campo se valida si es valor es mayor o menor, en el caso que no concuerde con la Cantidad exacta, el recuadro tengra un borde de color rojo y si concuerda se veria en verde. Cabe mencionar que el listado de productos está dentro del componente “ConteoMercancias”. Para finalizar el proceso de Conteo de Recibo, se cargar el servicio:

  • Agregar conteo recibo
    • URL:
      • /api/Recibo/AgregarConteo

Que en este caso cambia el estatus de registro del recibo en la tabla ProReciboAM, a “Conteo Terminado Recibo” además de cambiar el estatus de la Cita relacionada a CitaCompletada.

Ya terminado nuestro Conteo de Recibo, podremos volver acceder a conteo, pero esta vez tendremos desbloqueada la opción de Conteo Área de almacén, que sería la misma ventana pero esta vez se cambiaría la información o los campos disponibles para ingresar la cantidad en el listado de productos. En este caso a parte de ingresar la Cantidad Real que seguirá mostrando en rojo el recuadro si el valor introducido no es el mismo que la Cantidad original o en verde en caso de que si, pero esta vez tendremos un icono de palomita verde que se mostrará sólo cuando hayamos llenado todos los Lotes o Series de las cantidades de los productos.
Para agregar el Lote o serie (dependiendo la forma que se agrego el producto en catálogo de productos), deberemos primero ingresar la Cantidad Real que en el onChange mostrará el icono para asignar el lote/serie y fecha de caducidad (en caso sea necesaria la fecha), el cual una vez haciendo clic mostrará el Dialog con los campos de Cantidad, Lote/Serie, Fecha Caducidad y un boton disabled de Agregar Lote. El funcionamiento del botón estará desactivado en el caso de que el lote/serie sea el mismo en la Cantidad de productos, si no es así y se elige un número menor a la cantidad asignada anteriormente (Cantidad Real), se podrá agregar dicha cantidad seleccionada con esa información en específico, hasta que la cantidad sea igual a la real (ejemplo visual):

En el código el cambio de los cambios se vería de esta forma:

onChangeProducto(index, name, value) {
console.log(‘changeProducto’)
if(this*.state.productos.map*(d => parseInt(d.cantidad)).reduce((a, b) => a + b, 0) >= parseInt(this.state.seleccionado.cantidadReal)){
// console.log(“Entro if”)
// console.log(“Change Producto: “)
const productos = JSON.parse(JSON.stringify(this.state.productos));
productos[index][name] = value
productos[index]
.Listo = true
// console.log(productos)
this
.setState
({productos: productos})
}else{
// console.log(“Change Producto: “)
const productos = JSON.parse(JSON.stringify(this.state.productos));
productos[index][name] = value
productos[index].Listo = false
// console.log(productos)
this
.setState
({productos: productos})
}
console.log(this*.props.*productosLote)
}

Para finalizar el flujo hacemos clic en el botón de Aceptar y se correría el mismo servicio del Conteo de recibo, solo que esta vez cambiaría el estatus a Conteo terminado de Almacén.

Cancelar

Para cancelar se debe presionar la pestaña Cancelar (solo disponible cuando el recibo está en el estatus de pendiente confirmar configurado en parámetros), ubicada en la parte superior. Al entrar a la vista se carga la información del recibo y los parámetros de configuración (que se configuran en el módulo de Configuración), los cuales vienen de unas consultas del backend. Los servicios son los siguientes:

  • Obtener recibo

    • URL:
      • /api/CitasRecibo/CancelarGetListado/
  • Obtener parámetros

    • URL:
      • /api/ParametrosConfiguracion/GetParametros

    En la vista se llenan automáticamente los campos requeridos para cancelar el embarque. Se necesitan los siguientes datos para cancelar el embarque:

  • Cliente: se selecciona de CatClientesAM en la base de datos

  • Folio Recibo: se obtiene de la consulta del embarque

  • Fecha de Registro: se obtiene de la fecha de hoy desde el frontend

  • Usuario: se obtiene del ERP

  • Estatus: se obtiene de la consulta del embarque

  • Motivo: lo ingresa manualmente el usuario

    Finalmente al darle Aceptar se hace una llamada al backend para guardar la información.

  • Cancelar recibo

    • URL:
      • /api/Recibo/Cancelar/{idEmbarque}
    • Body:
      • fechaCancelacion
      • motivoCancelacion
      • idUsuarioCancelacion

Si la operación es exitosa se redirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Logística Almacén

Descripción general

En el menú lateral izquierdo, el módulo de Logística Almacén, se muestra el componente para visualizar los listados de recibo y embarque, además del Estatus de Almacén.

Inicialización de la vista

Al iniciar la vista se verifica la sesión del usuario. Si no hay sesión iniciada se redirige al login. Si hay sesión activa se hace una llamada al backend para obtener el listado de Recibos y Embarques.

El Tab de listado de recibo está activado al entrar al componente, en el cual se tienen las opciones para entrar a las opciones de Consulta de Recibo y Asignar Productos. De igual forma está la pestaña (Tab), para visualizar el listado de Embarques, dónde similar a el listado de Recibo, se puede consultar el Embarque, Asignar ubicación y Generar Reporte una vez completado.
En la última pestaña está la opción para ver el Estatus del Almacén, la cual sirve para ver todos los productos en los diferentes almacenes. En seguida veremos el funcionamiento de las opciones que se han mencionado.

Acciones del usuario

Asignar productos Recibo

Al seleccionar un registro del listado de Recibo en estatus Completo (que quiere decir que está completado por el módulo de Recibo), podremos seleccionar el icono de Asignar productos, el cual no llevará al Tab correspondiente y podremos visualizar el componente “AsignarProductosRecibo“. Al ingresar al componente, lo primero que realiza en el DiMount es obtener los datos del servicio “/api/Recibo/GetById/“ y setear los datos en el state de la Clase. Una vez seteados los datos se podrá visualizar la información general del Recibo así como el listado de Productos a ubicar en los espacios disponibles de los diferentes Almacenes de la sucursal asignada al Recibo. Los productos se visualizan mediante un componente DataGrid, el cual podremos elegir el icono correspondiente en la columna Ubicación, una vez realicemos clic en el icono la funcion “openDialogoProducto”, correrá y se declarara en verdadero el booleano “mostrarProducto”, así como setear los valores de ese “row” al array “productoSeleccionado” del state, para utilizarlo en el componente tipo Dialog llamado “AsignarUbicacionProducto”.


Una vez dentro del componente anteriormente mencionado, en un “componentDidMount” vamos a setear los datos que trae el servicio:

  • Obtener pasillos
    • URL:
      • /api/Almacenes/GetListadoActivosPasillos/Sucursal/${idSucursal}/Producto/${idProducto}/Cliente/${idCliente}

Enviando los datos correspondientes mediante los props del componente padre, que en este caso serian los props idSucursal, idProducto y idCliente.
Si el servicio trae información se van a setear los datos al array almacenes y también la variable pesoTotal la cual se consigue se multiplicando los props “this.props.data.Peso * this.props.data.Cantidad”, que se necesita para visualizar en en componente TextField Peso Total.
Debajo de la sección de los campos para visualizar los datos correspondientes, podremos ver un par de botones para buscar ubicaciones en los almacenes de manera automática o manual y debajo un componente DataGrid para ir agregando las ubicaciones que tendrán los productos dentro del almacén.
Vamos a dividir la explicación de las funciones de los botones Buscar Espacio y Buscar Espacio Manual.

Buscar Espacio (buscar espacio mediante servicio de cubicaje)

El botón de Buscar Espacio, solo estará disponible si el valor del prop cantidad es mayor a la suma de las cantidades del prop ubicaciones:

parseInt(this.props.cantidad) > this.props.ubicaciones.filter(p => p.IdReciboProductoDetalle === this.props.data.IdReciboProductoDetalle).map(d => parseInt(d.cantidad)).reduce((a, b) => a + b, 0)

En el onClick del componente correrá la función buscarUbicacion, que lo que realiza es correr el servicio:

  • Cubicaje
    • URL:
      • /api/CubicarMultiple

Enviando los parámetros correspondientes que en este caso serían this.props.idProducto, this.props.idSucursal, this.props.cantidad, this.props.idCliente, ubicacionesAsignadas o 0. Si el servicio trae datos significa que hay espacios disponibles en los almacenes y podremos setear dichas ubicaciones con el prop cambiarUbicaciones, que lo que realiza es setear el state del componente padre (AsignarProductosRecibo) agregando valores al array ubicaciones, mostrando así las ubicaciones en el DataGrid del componente AsignarUbicacionProducto. Sin embargo, si el servicio no trae ningún valor mostramos un mensaje de advertencia “No se encontraron espacios disponibles”, no pudiendo proceder con el flujo de buscar espacio de manera automática o mediante el servicio cubicaje.
Para finalizar si efectivamente trae datos el servicio de cubicaje tendremos las opciones de eliminar la ubicación del listado o visualizar el producto mediante una librería (la cual se verá más adelante en la documentación), además que los botones ya no estarían disponibles ya que a todos los productos se les encontraron ubicaciones.

Buscar Espacio Manual (buscar espacio de manera manual)

El botón de Buscar Espacio Manual, solo estará disponible si el valor del prop cantidad es mayor a la suma de las cantidades del prop ubicaciones:

parseInt(this.props.cantidad) > this.props.ubicaciones.filter(p => p.IdReciboProductoDetalle === this.props.data.IdReciboProductoDetalle).map(d => parseInt(d.cantidad)).reduce((a, b) => a + b, 0)

En el onClick del componente se estarían seteando diferentes variables que vamos a necesitar para abrir un nuevo Dialog para Ubicar el producto, el cual está en el mismo componente. Cambiamos el booleano openManual a verdadero para poder ver el Dialog para elegir primero la Cantidad del producto que vamos asignar, tomando en cuenta que la cantidad máxima que se puede elegir es la cantidad del producto que viene por defecto menos las cantidades que anteriormente se hayan asignado ya la ubicación:

cantidad: parseInt(this*.props.data.Cantidad) - (this.props.ubicaciones.filter*(p => *p.IdReciboProductoDetalle === this.props.data.*IdReciboProductoDetalle).map(d => parseInt(*d.*cantidad)).reduce((a, b) => a + b, 0)),

Hay que tener en cuenta que cada que se vaya a elegir una ubicación manual se limpien los campos de almacén, pasillo y espacio. Ya seleccionada la cantidad vamos a seleccionar un almacén disponible del listado en el OnChange del componente vamos a correr la función handleChange el cual traerá los datos del servicio:
“/api/Almacenes/Pasillos/GetListadoActivosEspacios/${id}/${idCliente}/${idProducto}”, para setearlos en el listado de pasillos que estén disponibles de ese almacén e igual se realiza lo mismo cuando se elige un pasillo pero ahora con el servicio:

  • Obtener espacios
    • URL:
      • /api/Almacenes/Espacios/GetListadoActivosClienteProducto/${id}/${idCliente}/${idProducto}

Seteando esta vez el listado de espacios a elegir. Al elegir un espacio, en el onChange del componente se correrá otro servicio para buscar la información del espacio seleccionado y sumar el peso total de los productos dentro del espacio.
Para finalizar haciendo clic en el botón de aceptar se va a correr la función onSubmitManual, en el cual tendremos varias validaciones dependiendo la disponibilidad de del espacio, el volumen, el peso, el peso de estibado, el volumen entre otros. Dándole siempre la opción al usuario si aún con las advertencias procederá a ubicar el producto en el lugar seleccionado, terminando así con el proceso para elegir ubicación de manera manual. Ya en el listado tendremos las opciones de eliminar la ubicación del listado o visualizar el producto mediante una librería (la cual se verá más adelante en la documentación), además que los botones ya no estarían disponibles ya que a todos los productos se les encontraron ubicaciones.

Ya que hemos cubierto las diferentes formas de asignar las ubicaciones de los productos el siguiente paso sería hacer clic al botón de Aceptar para cerrar el componente AsignarUbicacionProducto.

Finalmente en el componente AsignarProductoRecibo al dar clic en el botón Aceptar se hace submit al form corriendo la función onSubmit, en el cual hacemos nuestro params para enviar al servicio que vamos a correr, dependiendo si terminamos de asignar ubicaciones a todos los productos o no. En el caso de que no se hayan guardado las ubicaciones de todos los productos se llamaría el servicio:

  • Obtener espacios
    • URL:
      • /api/Recibo/${id}/AsignarUbicacionTemporal

El cual de la tabla ProReciboAM, busca el recibo correspondiente y setea el campo tipo bit, AsignacionTemporal a 1, después también elimina los registros de la tabla ProReciboUbicacionesTemporalesAM que ya se habian agregado anteriormente (en el caso que haya), para así terminar volviendo agregar las ubicaciones a la tabla anterior.

Consultar Recibo

En el mismo listado de Logística Almacén, al seleccionar un registro de Recibo, podremos consultar el Recibo cuando el estatus de dicho registro sea Completado Logística/Recibo
el cual una vez seleccionado el icono de Consultar nos abrirá al Tab de Consultar, mostrando el componente AsignarProductoRecibo con sus respectivos componentes hijos, pero esta vez solo podremos consultar la información en vez de modificarla o agregarla. Tomando en cuenta todos los servicios que se vieron en el flujo de Asignar Productos Recibo.


Asignar ubicación producto

Al seleccionar un registro del listado de Embarques en el estatus de Embarcado en la columna de Acciones del DataGrid, se podrá seleccionar el icono de Asignar Ubicación, el correrá la función handleShowUbicarEmbarque, donde cambiara al Tab de Asignar Ubicación mostrando el componente SalidaEmbarque, al entrar al componente lo primero que se realiza es correr un componentDidMount el cual llama el servicio:

  • Obtener embarque
    • URL:
      • /api/Embarque/GetById/${id}

Setea datos en el state del componente:
componentDidMount() {
obtenerEmbarqueId(this*.props.id).then(({data}) => {
let hora = data.m_sHoraEmbarque.replace(“.0000000”, "");
console.log(data);
this
.setState*({
data: data,
horaEmbarque: hora,
ubicaciones:data.ubicaciones.map(p => ({…p, id:getRandomId()})),
completadoUltimaMilla: *data.*CompletadaUltimaMilla == 0 ? false : true
})
})
}

Con estos datos se llena la sección de Información del Embarque, que solo son de consulta, además de enlistar los Productos del Embarque en un DataGrid. De los productos del listado podremos seleccionar el icono de la columna Ubicación para abrir el componente AsignarProductosSalida, el cual nos servirá para buscar el producto a embarcar (disponible), de los almacenes correspondientes.

Ya dentro del componente de AsignarProductosSalida, tendremos un par de componentes de botones y debajo un DataGrid, en el DataGrid se van a ir agregando los productos seleccionados a embarcar, mientras que en los botones vamos a explicar cada una de las diferentes funciones que realiza.

Botón Buscar Producto (mediante servicio)

En el componente UbicacionSalidaProducto, se encuentra el botón Buscar Producto, el cual estará disponible en el caso siguiente:
this*.props.consult && parseInt(this.props.cantidad) > this.props.ubicaciones.filter*(p => *p.idEmbarqueProducto === this.props.seleccionado.*idEmbarqueProducto).map(u => parseInt(*u.*Cantidad)).reduce((previousValue, currentValue) => previousValue + currentValue, 0)

Es decir mientras la suma de las cantidades del listado de los productos ubicados no exceda la cantidad total del producto a ubicar.
En el onClick corre la función handleOnSubmitProductos, la cual llama al servicio:

  • Obtener productos espacios
    • URL:
      • /api/EspaciosProducto/GetProductoSalida

Que en el caso que traiga datos se setean dichos datos al prop del componente padre para así llenar el listado de ubicaciones de los productos:

handleOnSubmitProductos() {
this.setState({seEncontro:true})
this.props.changeUbicaciones(this.props.ubicaciones.filter(u => u.idEmbarqueProducto !== this.props.seleccionado.idEmbarqueProducto))

getProductosAlmacenDisponible(this.props.idCliente, this.props.idProducto, this.props.idSucursal, this.props.cantidad).then(({data}) \=\> {
if(data.length\>0){
console.log(data)
/\* data.forEach(e \=\> {
this.state.productosAsignados.push(Object.assign(e, {...this.props.seleccionado}))
}) \*/
let newProductos \= \[...this.props.ubicaciones\]
data.forEach(e \=\> {
newProductos.push(Object.assign(e, {...this.props.seleccionado, id: getRandomId()}))
this.state.productosAsignados.push(Object.assign(e, {...this.props.seleccionado}))
})
this.props.changeUbicaciones(newProductos)
}
else{
showError("No se encontraron productos disponibles")
}
})

}

En caso contrario envía el mensaje de advertencia “No se encontraron productos disponibles”. Finalmente una vez encontrados todos los productos que saldrán en el Embarque, ya no se verá disponible el botón para Buscar Producto.

Botón Buscar Producto Manual (elegir producto de almacén, directamente) -directamente)}

En el componente UbicacionSalidaProducto, se encuentra el botón Buscar Producto Manual, el cual tendrá la misma validación que con el botón de Buscar Producto. En el onClick del componente cambia una variable booleana del state en verdadero para abrir un Dialog donde se estaría seleccionando el producto de un listado, este listado de productos se obtiene mediante el servicio:

  • Obtener productos espacios cliente
    • URL:
      • /api/EspaciosProducto/Cliente/Producto/

Que es el listado de productos dependiendo el cliente y el producto, dicha información se obtiene de la tabla ProInventarioEspaciosProductosAM y sus relaciones:

select PIEP.Lote,PIEP.FechaCaducidad,PIEA.IdEspacio,
CEA.Espacio,
CPA.IdentificableUnidad,
PIEP.IdInventarioEspacio,
PIEP.IdInventarioEspacioProducto as IdInventarioProducto,
CZAA.IdZonaAlmacen,PIEP.IdProducto, CZAA.ZonaAlmacen, CAA.Almacen,FORMAT(PIEP.FechaCaducidad,‘dd/MM/yyyy’) as FechaCaducidad,FORMAT(PIEP.FechaEntrada,‘dd/MM/yyyy’) as FechaEntrada, CPA.Producto, PIEP.Cantidad as Cantidad,
(PIEP.Cantidad * CPA.PiezasEmpaque) as Piezas,
CEILING(PIEP.Cantidad / CPA.EmpaquesTarimas) as Empaques,
CPA.CodigoProducto as CodigoProducto,
ISNULL(PIEP.IdGrupo,0) as IdGrupo,
ISNULL(PGIA.Nombre, ‘sin grupo’) as NombreGrupo,
CAA.Almacen as Almacen,
CAA.IdAlmacenes as idAlmacen
from ProInventarioEspaciosProductosAM PIEP
INNER JOIN CatProductosAM CPA on PIEP.IdProducto = CPA.IdProducto
INNER JOIN ProInventarioEspaciosAM PIEA on PIEP.IdInventarioEspacio = PIEA.IdInventarioEspacio
INNER JOIN CatEspacioAM CEA on CEA.IdEspacio = PIEA.IdEspacio
INNER JOIN CatZonaAlmacenAM CZAA on CEA.IdZonaAlmacen = CZAA.IdZonaAlmacen
INNER JOIN CatAlmacenesAM CAA on CAA.IdAlmacenes = CZAA.IdAlmacen
left JOIN ProGrupoInventarioAM PGIA on PIEP.IdGrupo = PGIA.IdGrupoInventario
where PIEA.IdCliente = id
and CAA.IdSucursal = idSucursal
and PIEP.IdProducto= idProducto
AND (PIEP.IdGrupo = 0 OR PIEP.IdGrupo is null

Una vez seleccionado de donde vamos a elegir el producto tambien debemos ingresar en el campo Cantidad, la cantidad que elegiremos de ahi. Este campo tiene una validacion respecto a la cantidad de producto que haya en la ubicación, que no se puede seleccionar cantidad mayor al que hay. Finalmente cuando seleccionamos Aceptar en el componente se corre la función handleOnSubmitProductosManual, realizando la validación antes mencionada y seteando los datos en el listado de productos ubicados.

Para finalizar el flujo de Asignar ubicación damos aceptar a los productos elegidos de mediante la Búsqueda por servicio o manual (no importa si no se eligió la ubicación de todos los productos del embarque), para así darle Aceptar, dando inicio a la función onSubmit el cual lo primero que realiza es validar que efectivamente se hayan elegido la ubicación de la salida del producto para el embarque:

for (let i = 0; i < array.length; i++) {
const detalle = array[i]
const ubicaciones = this.state.ubicaciones.filter(u => u.idEmbarqueProducto===detalle.idEmbarqueProducto)
console.log(this.state.ubicaciones.filter(u => u.idEmbarqueProducto===detalle.idEmbarqueProducto))

const totalCantidad \= this.state.ubicaciones
.filter(p \=\> p.idEmbarqueProducto \=== detalle.idEmbarqueProducto)
.map(u \=\> parseInt(u.Cantidad, 10))
.reduce((sum, cantidad) \=\> sum \+ cantidad, 0);
const result \= totalCantidad \=== parseInt(detalle.cantidad, 10\) ? true : false;
console.log(result)
if (ubicaciones.length \=== 0 || result \=== false ){
valido \= false
break;
}
}

En el caso que la suma de las cantidades de todos los productos no sea igual, se manda a llamar la función guardarInformacion, la cual envía los parámetros necesarios para el servicio de:

  • Asignar ubicación temporal embarque
    • URL:
      • /api/Embarque/${id}/AsignarUbicacionTemporal

El funcionamiento del servicio es primero modificar el registro de la tabla ProEmbarqueAM para cambiar el parámetro tipo bit llamado AsignacionTemporal a 1, inmediatamente se corre otro query para eliminar de la tabla ProEmbarqueUbicacionesTemporalesAM los antiguos registros del embarque, para así correr el último query de Agregar los nuevos registros temporales a la misma tabla.
Pero si es caso es que si agregaron las ubicaciones de todos los productos del embarque entonces llama el servicio:

  • Asignar ubicación embarque
    • URL:
      • /api/Embarque/${id}/AsignarUbicacion”

Que corre el procedimiento almacenado “usp_ProMovimientosAlmacenAgregarAM” agregando el nuevo movimiento a la tabla ProMovimientosAlmacenAM, en seguida corre otro procedimiento almacenado para agregar el tipo de movimiento a la tabla ProInventarioEspaciosProductosAM, finalizando con el proceso de Asignar Ubicación Embarque.

Consultar Embarque

En el mismo listado de Logística Almacén, al seleccionar un registro de Embarque, podremos consultar el Embarque cuando el estatus de dicho registro sea Completado
el cual una vez seleccionado el icono de Consultar nos abrirá al Tab de Consultar, mostrando el componente SalidaEmbarque con sus respectivos componentes hijos, pero esta vez solo podremos consultar la información en vez de modificarla o agregarla. Tomando en cuenta todos los servicios que se vieron en el flujo de Asignar Ubicación Embarque.

Estatus Almacén

En el menú lateral izquierdo, el módulo de Logística Almacén, se muestra el componente para visualizar los listados de recibo y embarque, además del Estatus de Almacén. Si seleccionamos el Tab (pestaña), de Estatus Almacén, tendremos primero que elegir una sucursal del componente Select, el cual se setearon los datos de las sucursales que trajo un servicio. Al seleccionar una sucursal, en el onChange del componente se correrá una funciona para modificar una variable booleana en verdadero para así poder visualizar el componente TablaAlmacen, el cual muestra un DataGrid con el listado de Almacenes que tiene la sucursal al momento:

De manera similar al elegir sucursal, aqui al momento de seleccionar cualquier fila del DataGrid, se va a realizar una consulta al servicio:

  • Obtener zonas almacen
    • URL:
      • /api/ZonasAlmacen/GetByAlmacen/

Se mostrará el componente TablaPasillos, que sería otro DataGrid, pero ahora con la información de los pasillos del almacén seleccionado:

Una vez mas, si seleccionamos un pasillo se corre el servicio de:

  • Obtener espacios almacen
    • URL:
      • /api/Espacios/GetByIdZona/

El cual trae los espacios del pasillo seleccionado y se muestra el componente EstatusEspacios con el listado de espacios:

Para finalizar en el listado de Espacios podremos hacer clic a uno de los registros de la columna Contenido, el cual hará que habrá un componente Dialog donde se en listan los productos dentro del espacio mediante el servicio:

  • Obtener productos espacios
    • URL:
      • /api/Productos/GetByEspacio/

Terminando con el flujo.

Estatus Almacén

En el menú lateral izquierdo, el módulo de Almacén, nos dirige al componente Almacen, al entrar al componente valida que componente se estará mostrando, que por default es el de Inventario en el cual se muestra el componente de Filtros y un listado de clientes en un DataGrid, cargado con un servicio que trae los clientes. En el listado tendremos la columna de Acciones, la cual es la de consultar el inventario del cliente seleccionado, que nos dirige al componente ConsultarInventarioCliente que nos muestra el nombre del cliente y su RFC como Información General. También aquí tendremos el componente de Filtros para buscar la información de la forma que guste el usuario. Debajo de los Filtros tendremos enlistados en un componente ListItem los almacenes que tiene el cliente, además que tendremos la opción de exportar la información mediante un archivo Excel, la información exportada siempre dependerá de los filtros o los datos que tenga en ese momento seteados los listado de Almacenes.

Cada que expandamos un Almacén se muestra un DataGrid con el listado de Mercancía que tiene el almacén, además de información de dicha mercancía (cantidad etc), y su ubicación dentro del almacén (es decir pasillos, espacios etc) y en el caso que tengas Grupos también se van a mostrar en un listado con un DataGrid, siendo el uso principal de esta sección, la consulta del inventario de los diversos almacenes del cliente.

Toda esta información viene de los servicios:

  • Obtener almacenes
    • URL:
      • /api/Almacenes/GetListado
  • Obtener espacios
    • URL:
      • /api/EstatusEspacios/GetEstatusEspaciosActivos
  • Obtener productos
    • URL:
      • /api/Productos/GetListado/

Para terminar tenemos la opción de Descargar Kardex se manda a llamar un servicio en WebDev con la dirección:

  • Obtener almacenes
    • URL:
      • /api/Intentario/GetKardexCliente/

El cual descarga un archivo tipo Excel personalizado.

Movimiento Almacén

Descripción general

En el menú lateral izquierdo, el módulo de Almacén en la esquina superior derecha , se muestra la opción para visualizar los listados con todos los movimientos registrados de manera automática (es decir desde logístico) o manual desde Agregar Movimiento.
El movimiento de almacén sirve para agregar, consultar, etc, los diferentes tipos de movimientos dentro de un almacén.

Inicialización de la vista

En el mismo componente de Inventario está la opción para mostrar el componente de MovimientosAlmacen, el cual muestra un listado con todos los movimientos registrados de manera automática (es decir desde logístico) o manual desde Agregar Movimiento.
En el listado principal se muestra el componente de Filtros para buscar la información mediante diferentes filtros, los resultados del servicio:

  • Obtener movimientos almacen
    • URL:
      • /api/MovimientosAlmacen/GetListadoByFiltros/

Los setea en un DataGrid los cuales dependiendo del registro seleccionado se pueden consultar los movimientos.

Tenemos las opciones para Registrar Movimiento, Reportes, Exportar y finalmente la opción de regresar al componente anterior de Inventario, pero en este momento veremos la Consulta del Movimiento.
Para consultar el movimiento se debe hacer clic en el icono de Consultar en la columna de Acciones (icono de un ojo). Una vez haciendo clic correrá la función handleShowConsultar cambiando el Tab de Agregar y muestra el componente RegistroMovimiento. En un hook UseEffect del componente se valida si el prop idMovimientoAlmacen es mayor a 0 para buscar el movimiento mediante el servicio:

  • Obtener movimiento de almacén
    • URL:
      • /api/MovimientosAlmacen/GetById/{id}

Y setear los datos correspondientes del movimiento adermas de validar que tipo de movimiento es, para también setear los siguientes datos dependiendo de los casos:

  • Si el tipo de movimiento es Entrada por Recibo, se muestra en la parte inferior el componente EntradaRecibo.
  • Si es Entrada por Devolución, se muestra en la parte inferior el componente EntradaDevolucion.
  • Si es Salida Embarque, se muestra en la parte inferior el componente SalidaEmbarque.
  • Si es Salida Merma, se muestra en la parte inferior el componente SalidaMerma
  • Si es Reubicación o Traspaso, se muestra el componente MovimientoReubicacion.
  • Si es agrupación el componente es MovimientoAgrupacion.

Y se muestran los productos correspondientes del tipo de movimiento, tomando en cuenta el movimiento (dependiendo el componente activo es la forma que se consultaron los datos, estos componente se veran en la seccion de Agregar Movimiento).


Finalmente está la opción de Exportar, que solo exporta todo la data de listado de Movimientos en un archivo tipo Excel.

Acciones del usuario

Registrar Movimiento

Dentro del módulo de Movimientos de Almacén, está el Tab de Registrar movimiento, que al seleccionarlo dirige al Tab Agregar y muestra el componente de RegistroMovimiento. Ya en el componente pide algunos datos importantes para registrar el movimiento, pero el componente del Tipo de Movimiento es el más importante, ya que define qué componente hijo mostrará en la sección de abajo. Vamos a ir viendo la función de cada tipo de movimiento uno por uno.

Entrada Recibo

Una vez elegida la opción se debe seleccionar la sucursal y el almacén donde se realizará el movimiento para así desplegar la vista del componente EntradaRecibo en la sección debajo de la información general, en dicho componente primero se deberá elegir el cliente y después se muestra también para elegir el proveedor (sin esta información no se puede continuar con el flujo), y así poder seleccionar el botón Agregar Producto el cual en el onClick corre una función que cambia un booleano a verdadero para desplegar un componente Dialog llamado DialogAgregarProductoMovimientoAlmacen.

Una vez desplegada la vista del dialog el usuario debe seleccionar el o los productos que se requieren entrar al almacén, los cuales son los productos que anteriormente fueron asignados por el usuario el los catálogos y la cantidad del producto del movimiento.

Entrada Devolución

Al seleccionar esta opción el usuario debe seleccionar la sucursal y el almacén, lo cual hace que se despliegue la opción de seleccionar el cliente (en la parte de abajo) y poder también agregar el producto que se estaría haciendo devolución. Al igual que Entrada por recibo, se selecciona el producto y la cantidad.

Salida Embarque
Al seleccionar esta opción el usuario debe seleccionar la sucursal y el almacén, en esta ocasión se debe seleccionar el cliente y al agregar producto, el componente dialog que sale te da la opcion de elejir un productos de los que están disponibles pero en el almacén seleccionado, además de la cantidad y el destinatario. Además se tiene una opción extra que es la de subir una plantilla de productos que agregan al listado que se irá de una manera más fácil y rápida.
Salida Merma

En esta opción el usuario solo debe elegir el producto del almacén seleccionado y la cantidad, para agregar el producto al movimiento.

Reubicación y Traspaso
En la reubicación y traspaso es algo diferente de las opciones anteriores, ya que esta vez solo involucra la sucursal, el almacén y el cliente donde se va a reubicar el o los productos. Esta vez en un componente DataGrid, estarán los datos de los productos en el almacén elegidos, se hace clic al icono de flecha roja para abrir un Dialog donde se selecciona la cantidad y la nueva ubicación del producto. Finalmente al elegir se irá enlistando abajo todos los productos que se están reubicando.
Agrupación
Para la agrupación se selecciona el cliente y define en un radio si se va agregar o modificar una agrupación.
  • Agregar

    En el caso de agregar una agrupación al hacer clic al botón Agregar Producto se va a desplegar un Diálogo donde se ingresa el nombre de la nueva agrupación y se elije de un listado de productos del almacén anteriormente seleccionado así como la cantidad asignada que tendrá de ese producto y se van enlistando en la parte inferior del nombre del grupo, para asi hacer clic en Aceptar y enlistarse en el componente padre ya con la información del Grupo desplegada (es decir nombre y productos del grupo)

  • Modificar

    Para modificar se muestra un listado de grupos en el cual se puede buscar por nombre. Al seleccionar un grupo tendremos las opciones de editar y eliminar, al editar se despliega el mismo dialog de agregar pero sí es eliminar pone un aviso de advertencia antes de proceder.

Para finalizar el flujo al presionar el botón Aceptar se hace una llamada al backend para guardar la información, pero antes se toma en cuenta que los datos a enviar dependen del tipo de movimiento.

  • Agregar Movimiento
    • URL:
      • /api/MovimientosAlmacen/Agregar
    • Body:
      • folioMovimiento
      • idTipoMovimiento
      • fecha
      • motivo
      • descripcion
      • idUsuario
      • idSucursal
      • idAlmacen
      • idCliente
      • idProveedor
      • productos
      • tipo
      • grupos

Si la operación es exitosa se redirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Embarques

Descripción general

En el menú lateral izquierdo, el módulo de Embarques, se muestra el componente EmbarqueAreaEmbarque donde se puede visualizar un listado de embarques, agregar, exportar, documentación inspección, cancelar, marcar salida, marcar llegada e importar.

El Embarque es cuando se requiere la entrega de productos del Almacén a algún destinatario.

Inicialización de la vista

Al iniciar la vista se verifica la sesión del usuario: si no hay una sesión iniciada, se redirige al login; si hay una sesión activa se hace una llamada al backend para obtener el listado de Embarques.
  • Obtener Embarques
    • URL:
      • api/Embarque/GetListadoEmbarqueByFiltros/

Al obtener los datos del servicio se muestran en un DataGrid el cual permite hacer diferentes flujos dependiendo del estatus del Embarque, que serian los siguientes:

  • La consulta puede realizarse sin importar el estatus.

  • La modificación sólo ocurre cuando el estatus está configurado en Estatus embarque por embarcar.

  • Generar Reporte, también siempre está disponible sin importar el estatus.

    Acciones del usuario

Agregar

Para agregar se debe presionar la pestaña Agregar ubicada en la parte superior. Al entrar se cargan los listados de clientes, sucursales, citas y tipo de unidades, los cuales vienen de unas consultas del backend. Los servicios son los siguientes:

  • Obtener listado de sucursales

    • URL:
      • /api/Sucursales/GetListado
  • Obtener listado de tipo de unidades

    • URL:
      • /api/TiposUnidades/GetListado
  • Obtener paginado de listado de clientes

    • URL:
      • api/Client/GetListadoPaginado/

    Cuando se quiere agregar o elegir una cita de embarque con el botón superior derecho, se abre un componente de catálogo que anteriormente se utilizó en el módulo de Citas, en el cual una vez seleccionamos la sucursal se carga un listado de andenes mediante el siguiente servicio:

  • Obtener listado de andenes

    • URL:
      • api/Client/GetListadoPaginado/{idSucursal}

Se necesitan los siguientes datos para agregar el embarque:

  • Folio Embarque: se agrega automáticamente al agregar embarque.

  • Cliente: se selecciona de CatClientesAM en la base de datos

  • Fecha y Hora Registro: toma la fecha automáticamente del día desde el front.

  • Sucursal: viene del ERP, se selecciona al seleccionar una cita de embarque

  • Fecha Embarque: se selecciona al seleccionar una cita de embarque

  • Hora Embarque: se selecciona al seleccionar una cita de embarque

  • Anden Carga/Descarga: se selecciona al seleccionar una cita de embarque

  • Folio Cita: se selecciona al seleccionar una cita de embarque

  • Tipo Unidad: viene del ERP

  • Comentarios Generales: el usuario lo define

  • Entrega a Terceros: el usuario lo define

  • Destinatario: viene del ERP

  • Productos: se eligen por el usuario

    Dentro de la pestaña de agregar hay una sección de Productos (después de la de información del embarque), en la que podemos agregar productos mediante un botón o importando con un archivo excel.

Al presionar el botón Agregar Productos se abre un Dailog donde se necesitan nuevos datos, pero esta vez del producto:

  • Cantidad: se introduce por el usuario

  • Observaciones: en caso de ser necesario se introduce por el usuario (no es obligatorio)

    Al entrar se carga el listado de productos almacenados en el andén anteriormente seleccionado, mediante el servicio:

  • Obtener listado de productos del cliente en los espacios

    • URL:
      • /EspaciosProducto/ProductosCliente/{idCliente}/{idSucursal}

Al darle agregar en el botón inferior del Dialog, se agrega a una tabla donde se enlistan los productos a embarcar.

Finalmente al darle Aceptar se hace una llamada al backend para guardar la información.

  • Agregar Movimiento
    • URL:
      • /api/Embarque/Agregar
    • Body:
      • folioEmbarque
      • idCliente
      • fechaRegistro
      • horaEmbarque
      • fechaEmbarque
      • idTipoUnidad
      • idSucursal
      • idDestinatario
      • idZonaOperativa
      • comentariosGenerales
      • productos:confirmadoEmbarqueCliente
      • latitud
      • longitud
      • entregaTerceros
      • idCita

Si la operación es exitosa se redirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Modificar

Para modificar se debe presionar el icono similar a un lapiz ubicado en la columna Acciones del embarque que se requiera. Al entrar se cargan los listados de clientes, sucursales, citas, tipo de unidades, listado de productos del embarque, la información del embarque y el remitente, los cuales vienen de unas consultas del backend. Los servicios son los siguientes:

  • Obtener listado de sucursales
    • URL:
      • /api/Sucursales/GetListado
  • Obtener listado de tipo de unidades
    • URL:
      • /api/TiposUnidades/GetListado
  • Obtener paginado de listado de clientes
    • URL:
      • api/Client/GetListadoPaginado/
  • Obtener productos embarque
    • URL:
      • /api/EspaciosProducto/ProductosCliente/{idCliente}/{IdSucursal}
  • Obtener remitente
    • URL:
      • /api/Remitentes/GetById/{idRemitente}
  • Obtener embarque
    • URL:
      • /api/Embarque/GetById/{idEmbarque}

Cuando se quiere agregar o elegir una cita de embarque con el botón superior derecho, se abre un componente de catálogo que anteriormente se utilizó en el módulo de Citas, en el cual una vez seleccionamos la sucursal se carga un listado de andenes mediante el siguiente servicio:

  • Obtener listado de andenes
    • URL:
      • api/Client/GetListadoPaginado/{idSucursal}

Se necesitan los siguientes datos para modificar el embarque:

  • Folio Embarque: se agrega automáticamente al agregar embarque.

  • Cliente: se selecciona de CatClientesAM en la base de datos

  • Fecha y Hora Registro: toma la fecha automáticamente del día desde el front.

  • Sucursal: viene del ERP, se selecciona al seleccionar una cita de embarque

  • Fecha Embarque: se selecciona al seleccionar una cita de embarque

  • Hora Embarque: se selecciona al seleccionar una cita de embarque

  • Anden Carga/Descarga: se selecciona al seleccionar una cita de embarque

  • Folio Cita: se selecciona al seleccionar una cita de embarque

  • Tipo Unidad: viene del ERP

  • Comentarios Generales: el usuario lo define

  • Entrega a Terceros: el usuario lo define

  • Destinatario: viene del ERP

  • Productos: se eligen por el usuario

    Dentro de la pestaña de modificar hay una sección de Productos (después de la de información del embarque), en la que podemos modificar los productos ya anteriormente agregados mediante un botón o importando con un archivo excel.

Al presionar el botón Agregar o modificar Productos, se abre un Dailog donde se necesitan nuevos datos, pero esta vez del producto:

  • Cantidad: se introduce por el usuario

  • Observaciones: en caso de ser necesario se introduce por el usuario (no es obligatorio)

    Al entrar se carga el listado de productos almacenados en el andén anteriormente seleccionado, mediante el servicio:

  • Obtener listado de productos del cliente en los espacios

    • URL:
      • /EspaciosProducto/ProductosCliente/{idCliente}/{idSucursal}

Al darle agregar en el botón inferior del Dialog, se agrega a una tabla donde se enlistan los productos a embarcar.

Finalmente al darle Aceptar se hace una llamada al backend para guardar la información.

  • Agregar Movimiento
    • URL:
      • /api/Embarque/Agregar
    • Body:
      • folioEmbarque
      • idCliente
      • fechaRegistro
      • horaEmbarque
      • fechaEmbarque
      • idTipoUnidad
      • idSucursal
      • idDestinatario
      • idZonaOperativa
      • comentariosGenerales
      • productos:confirmadoEmbarqueCliente
      • latitud
      • longitud
      • entregaTerceros
      • idCita

Si la operación es exitosa se redirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Consultar

Para consultar se debe presionar el icono de ojo ubicado en la columna Acciones del embarque que se requiera. Al entrar se cargan los listados de clientes, sucursales, citas, tipo de unidades, listado de productos del embarque, la información del embarque, las evidencias y el remitente, los cuales vienen de unas consultas del backend. Los servicios son los siguientes:

  • Obtener listado de sucursales
    • URL:
      • /api/Sucursales/GetListado
  • Obtener listado de tipo de unidades
    • URL:
      • /api/TiposUnidades/GetListado
  • Obtener paginado de listado de clientes
    • URL:
      • api/Client/GetListadoPaginado/
  • Obtener productos embarque
    • URL:
      • /api/EspaciosProducto/ProductosCliente/{idCliente}/{IdSucursal}
  • Obtener remitente
    • URL:
      • /api/Remitentes/GetById/{idRemitente}
  • Obtener embarque
    • URL:
      • /api/Embarque/GetById/{idEmbarque}
  • Obtener evidencias de embarque
    • URL:
      • /api/Embarque/GetImagenEvidencia/{idEmbarque}/0

Estando todos los campos bloqueados ya que es consultar, solo puede ver la evidencia o regresar al listado de embarques con el botón cancelar.

Cancelar

Para cancelar se debe presionar la pestaña Cancelar (solo disponible cuando el embarque no ha sido embarcado), ubicada en la parte superior. Al entrar a la vista se carga la información del embarque y los parámetros de configuración (que se configuran en el módulo de Configuración), los cuales vienen de unas consultas del backend. Los servicios son los siguientes:

  • Obtener embarque

    • URL:
      • /api/Embarque/GetById/{idEmbarque}
  • Obtener parametros

    • URL:
      • /api/ParametrosConfiguracion/GetParametros

    En la vista se llenan automáticamente los campos requeridos para cancelar el embarque. Se necesitan los siguientes datos para cancelar el embarque:

  • Cliente: se selecciona de CatClientesAM en la base de datos

  • Folio Embarque: se obtiene de la consulta del embarque

  • Fecha de Registro: se obtiene de la fecha de hoy desde el frontend

  • Usuario: se obtiene del ERP

  • Estatus: se obtiene de la consulta del embarque

  • Motivo: lo ingresa manualmente el usuario

    Finalmente al darle Aceptar se hace una llamada al backend para guardar la información.

  • Cancelar embarque

    • URL:
      • /api/Embarque/Cancelar/{idEmbarque}
    • Body:
      • fechaCancelacion
      • motivoCancelacion
      • idUsuarioCancelacion

Si la operación es exitosa se redirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Documentación Inspección

Para documentar inspección se debe presionar la pestaña Documentación Inspección (sólo disponible cuando el embarque es seleccionado y el estatus configurado en parámetros de configuración de embarque del estatus inspección de calidad de embarque no ha sido embarcado), ubicada en la parte superior. Al entrar a la vista se carga la información del embarque, los parámetros de configuración, los tipos de unidades, las sucursales, el remitente del embarque, los formularios (encuestas de productos y unidades), los cuales vienen de unas consultas del backend. Los servicios son los siguientes:

  • Obtener parámetros de configuración

    • URL:
      • /api/ParametrosConfiguracion/GetParametros
  • Obtener tipo unidades

    • URL:
      • /api/TiposUnidades/GetListado
  • Obtener sucursales

    • URL:
      • /api/Sucursales/GetListado
  • Obtener embarque

    • URL:
      • /api/Embarque/GetById/{idEmbarque}
  • Obtener formulario

    • URL:
      • /api/Formulario/GetById/{idFormulario}
  • Obtener remitente

    • URL:
      • /api/Remitentes/GetById/{idRemitente}

    Una vez dentro el usuario responde el formulario y elige si fue aprobado o no la inspección. Para terminar de inspeccionar selecciona el botón Aceptar y hace una llamada al backend a un par de servicios donde se guardan las respuestas y se aprueba el embarque.

  • Guardar repuestos inspección

    • URL:
      • /api/Embarque/GuardarRespuestasInspeccion
    • Body:
      • respuestaFormularios
  • Enviar inspección de calidad

    • URL:
      • /api/Embarque/aprobarCalidad
    • Body:
      • idEmbarque
      • resultado
      • comentarios

Si la operación es exitosa se redirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Marcar salida

Para marcar salida se debe presionar la pestaña Marcar salida (sólo disponible cuando en la configuración se tiene activado Entrega a terceros), ubicada en la parte superior. Al darle clic se despliega una ventana en donde se carga la información del embarque.

  • Obtener embarque

    • URL:
      • /api/Embarque/GetById/{idEmbarque}

    Para marcar salida se necesitan los siguientes datos:

  • Fecha y hora de salida: el usuario ingresa la información

  • Unidad: el usuario ingresa la información

  • Caja: el usuario ingresa la información

  • Operador: el usuario ingresa la información

    Finalmente al darle Aceptar se hace una llamada al backend para guardar la información.

  • Marcar salida embarque

    • URL:
      • /api/Embarque/Salida/{idEmbarque}

Si la operación es exitosa se recarga el listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Marcar llegada

Para marcar llegada se debe presionar la pestaña Marcar llegada(sólo disponible cuando en la configuración se tiene activado Entrega a terceros), ubicada en la parte superior. Al darle clic se despliega una ventana en donde se carga la información del embarque y las evidencias (en caso que tenga evidencias del embarque).

  • Obtener embarque

    • URL:
      • /api/Embarque/GetById/{idEmbarque}

    Para marcar salida se necesitan los siguientes datos:

  • Fecha y hora de llegada: el usuario ingresa la información

  • Evidencias: el usuario sube las evidencias desde el local de su computador

    Finalmente al darle Aceptar se hace una llamada al backend para guardar la información.

  • Marcar llegada embarque

    • URL:
      • /api/Embarque/Llegada/{idEmbarque}
  • Enviar evidencias embarque

    • URL:
      • /Embarque/AgregarEvidencias

Si la operación es exitosa se recarga el listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Inventario físico

Descripción general

En el menú lateral izquierdo, el módulo de Inventario Físico, se muestra el componente Inventario donde se puede visualizar un listado de inventarios, inventariar, consultar, modificar y capturar conteo físico.

El Inventario Físico se realiza cuando se requiere realizar un conteo físico de lo que está dentro del almacén de los productos almacenados.

Inicialización de la vista

Al iniciar la vista se verifica la sesión del usuario: si no hay una sesión iniciada, se redirige al login; si hay una sesión activa se hace una llamada al backend para obtener el listado de Inventarios.
  • Obtener Inventarios
    • URL:
      • /api/Intentariar/GetByFiltros/

Al obtener los datos del servicio se muestran en un DataGrid el cual permite hacer diferentes flujos dependiendo de si ya se realizó el Inventario, que serían los siguientes:

  • La consulta puede realizarse sin importar el estatus.

  • La modificación sólo ocurre cuando aún no se ha realizado el conteo físico.

  • Capturar conteo solo se puede realizar una vez.

    Acciones del usuario

Inventariar

Para inventariar se debe presionar la pestaña *Inventariar* ubicada en la parte superior. Al entrar se carga el servicio de Zonas de almacén el cual viene de una consulta del backend:
  • Obtener Zonas de Almacén

    • URL:
      • /api/ZonasAlmacen/GetByAlmacen/{idAlmacen}

    Dependiendo si del inventario que el usuario requiera realizar hace llamada al backend de los listados:

  • Obtener almacenes mediante la sucursal

    • URL:
      • /Almacenes/GetListadoActivos/BySucursal/{idSucursal}
  • Obtener paginado listado clientes

    • URL:
      • /api/Client/GetListadoPaginado/{pagina}/{idCliente}
  • Obtener listado familias activas

    • URL:
      • /api/Familia/GetListadoActivos
  • Obtener listado almacenes activos por sucursal

    • URL:
      • /api/Almacenes/GetListadoActivos/BySucursal/

    Dentro de la vista está el botón para generar el listado de los productos que se va a realizar inventario, una vez seleccionado sale un mensaje dependiendo el caso, si ¿Desea tomar en cuenta la mercancía agrupada como un solo producto?, en caso que no solo sale mensaje de advertencia que no se generó el listado, pero en caso contrario hace una llamada al backend para generar el listado. El servicio sería el siguiente:

  • Obtener plantilla inventariar

    • URL:
      • /api/Intentariar/GetListadoPlantilla/{idCliente}/{idFamilia}/{idSucursal}/{idAlmacen}

    Finalmente el resultado del servicio se enlista en un DataGrid para consultar.

    Para inventariar se necesitan los siguientes datos:

  • Familia: no es obligatorio y se elige mediante consulta de familias desde la base de datos

  • Cliente: no es obligatorio y viene del ERP

  • Folio Inventario: se genera cuando se termina el flujo de Inventariar

  • Sucursal: viene del ERP

  • Almacén: no es obligatorio y se elige mediante consulta de almacenes desde la base de datos

  • Comentarios Generales: no es obligatorio y lo introduce el usuario

    Finalmente al darle Aceptar primero válida si se generó correctamente el listado, en caso que no es así muestra mensaje de advertencia, pero si al contrario todo está bien, se muestra un Dailog para elegir el formato del reporte y que tipo de archivo para así pasar hacer una llamada al backend para guardar la información.

  • Agregar inventariar

    • URL:
      • /api/Inventariar/Agregar
    • Body:
      • idCliente
      • idAlmacen
      • idSucursal
      • porFamilia
      • porCliente
      • porAlmacen
      • idFamilia
      • observaciones
      • mostrarVistaAgrupados
      • fechaRegistro
      • capturaFisica
      • arrayInventariarInventarioProducto

Si la operación es exitosa se recarga el listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Consultar

Para consultar se debe presionar el icono con forma de ojo ubicado en la columna de acciones del registro que el usuario decida. Al entrar se carga el servicio que consulta la información del Inventario el cual viene de una llamada al backend.

  • Obtener consulta de inventariar

    • URL:
      • /api/Intentariar/GetById/{idInventariar}

    En esta ocasión solo se puede ver la información del registro en los componentes, no se puede realizar nada que no sea regresar a la pestaña del listado.

Modificar

similar a consultar se debe presionar el icono con forma de lápiz ubicado en la columna de acciones del registro que el usuario decida. Al entrar se carga el servicio que consulta la información del Inventario el cual viene de una llamada al backend.
  • Obtener consulta de inventariar

    • URL:
      • /api/Intentariar/GetById/{idInventariar}

    Esta vez al igual que agregar se hacen las siguientes llamadas al backend en caso que el usuario modifique algo.

  • Obtener Zonas de Almacén

    • URL:
      • /api/ZonasAlmacen/GetByAlmacen/{idAlmacen}
  • Obtener almacenes mediante la sucursal

    • URL:
      • /Almacenes/GetListadoActivos/BySucursal/{idSucursal}
  • Obtener paginado listado clientes

    • URL:
      • /api/Client/GetListadoPaginado/{pagina}/{idCliente}
  • Obtener listado familias activas

    • URL:
      • /api/Familia/GetListadoActivos
  • Obtener listado almacenes activos por sucursal

    • URL:
      • /api/Almacenes/GetListadoActivos/BySucursal/

Para modificar inventariar se necesitan los siguientes datos:

  • Familia: no es obligatorio y se elige mediante consulta de familias desde la base de datos

  • Cliente: no es obligatorio y viene del ERP

  • Folio Inventario: se genera cuando se termina el flujo de Inventariar

  • Sucursal: viene del ERP

  • Almacén: no es obligatorio y se elige mediante consulta de almacenes desde la base de datos

  • Comentarios Generales: no es obligatorio y lo introduce el usuario

    Finalmente al darle Aceptar primero válida si se generó correctamente el listado, en caso que no es así muestra mensaje de advertencia, pero si al contrario todo está bien, se muestra un Dailog para elegir el formato del reporte y que tipo de archivo para así pasar hacer una llamada al backend para guardar la información.

  • Modificar inventariar

    • URL:
      • /api/Inventariar/Modificar/{idInventariar}
    • Body:
      • idCliente
      • idAlmacen
      • idSucursal
      • porFamilia
      • porCliente
      • porAlmacen
      • idFamilia
      • observaciones
      • mostrarVistaAgrupados
      • fechaRegistro
      • capturaFisica
      • arrayInventariarInventarioProducto

Si la operación es exitosa se recarga el listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Capturar Conteo Físico

Para Capturar conteo físico se debe presionar el icono con forma de calculadora ubicado en la columna de acciones del registro que el usuario decida. Al entrar se carga el servicio que consulta la información del Inventario el cual viene de una llamada al backend.

  • Obtener consulta de inventariar

    • URL:
      * /api/Intentariar/GetById/{idInventariar}

      Al igual que al consultar el usuario no podrá modificar la información del Inventario físico pero tendrá un par de opciones extras que se pueden utilizar en caso de que haya problemas al inventariar.

      Agregar producto no inventariado
      Esta opción se activa pulsando el botón al el cual desplegará un Dialog donde podremos elegir la siguiente información necesaria:

  • Producto: se elige por el usuario mediante una consulta en la base de datos

  • Lote/Serie: se introduce por el usuario

  • Fecha caducidad: se elige por el usuario

  • Cantidad: se introduce por el usuario

  • Proveedor: viene del ERP, se elige por el usuario

  • Cliente: viene del ERP, se elige por el usuario

  • Almacén: se elige por el usuario mediante una consulta en la base de datos

  • Movimiento de Almacén: se elige por el usuario mediante una consulta en la base de datos

    Las llamadas que se realizan en este flujo son las siguientes:
  • Obtener consulta espacio por cliente

    • URL:

      • /almacen/api/Espacios/GetByCliente/{idCliente}
    • Obtener zonas almacen

      • URL:
        • /api/ZonasAlmacen/GetByAlmacen/{idAlmacen}
    • Obtener pasillos almacen

      • URL:
        • /ZonasAlmacen/GetById/{idPasillo}
    • Obtener espacios almacen

      • URL:
        • /api/Espacios/GetByCliente/
    • Obtener tipos movimentos

      • URL:
        • /api/TiposMovimientos/GetTiposMovimientosConfiguracion
    • Obtener listado productos

      • URL:
        • /api/Productos/GetListado/

      Movimientos
      Esta opción se activa pulsando el icono de flechas en la columna Acciones del listado de productos el cual desplegará un Dialog donde podremos elegir la siguiente información necesaria:

  • Tipo movimiento: se elige por el usuario

  • Movimiento de almacén: se elige por el usuario en el caso que el tipo de movimiento sea diferente a Información Verificada.

  • Cantidad: en caso que el tipo de movimiento sea Menor o Mayor cantidad de productos

  • Motivo: se introduce cuando el tipo de movimiento es No se encontró la mercancía

  • Pasillo*:* se elige cuando el tipo de movimiento es Ubicación Distinta

  • Espacio*:* se elige cuando el tipo de movimiento es Ubicación Distinta

Finalmente al darle Aceptar hace una llamada al backend para guardar la información.

  • Capturar conteo
    • URL:
      • /api/Inventariar/CapturarInventario
    • Body:
      • idInventariar
      • idCliente
      • idAlmacen
      • idSucursal
      • porCliente
      • porAlmacen
      • idFamilia
      • observaciones
      • mostrarVistaAgrupados
      • fechaRegistro
      • capturaFisica
      • arrayInventariarInventarioProducto

Si la operación es exitosa se recarga el listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Parámetros de Configuración

Descripción general

En el menú lateral izquierdo, el módulo de Configuraciones, se muestra el componente Parámetros de configuración donde se puede visualizar un Tab con varias pestañas de los diferentes módulos del sistema.

La Configuración se realiza cuando se requiere definir diferentes parámetros al sistema para su uso.

Inicialización de la vista

Al iniciar la vista se verifica la sesión del usuario: si no hay una sesión iniciada, se redirige al login; si hay una sesión activa se hace una llamada al backend para obtener el diferentes listados que se van a requerir y la consulta a los parámetros de configuración.

Acciones del usuario

Modificar

Para modificar los parámetros de configuración se necesitan los siguientes datos:
  • Escanear productos al cargar Vehículo: se elige por el usuario.

  • Estatus cita pendiente confirmar almacén: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus cita pendiente confirmar cliente: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus cita pendiente recibo: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus cita confirmada: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus cita completada: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus cita completado con incidencias: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus cita cancelada: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus recibo pendiente confirmar: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus recibo confirmado por calidad: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus recibo aprobado por calidad: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus recibo completado: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus recibo completado con incidencias: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus terminado conteo recibo: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus terminado conteo almacén: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus recibo rechazado por calidad: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus recibo cancelado: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus recibo completado logística: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus embarque por embarcar: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus embarque aprobado por calidad: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus embarque rechazado por calidad: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus aprobado con incidencias por calidad: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus embarque embarcado: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus embarque cancelado: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus embarque completado: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus embarque inspeccionado devolución: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus embarque completado con devolución: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus embarque en ruta: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Entrega a terceros: se elige por el usuario.

  • Evaluar calidad de producto antes de Embarque: se elige por el cliente

  • Cuestionario Productos Recibo: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Cuestionario Unidad Recibo: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Cuestionario Productos Embarque: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Cuestionario Unidad Embarque: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Cuestionario Producto Devolución: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Cuestionario Unidad Devolución: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Escanear productos al realizar inventario: se elige por el usuario.

  • Escanear productos al cambiar de ubicación: se elige por el usuario.

  • Estatus correspondientes a espacio libre: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus correspondientes a espacio ocupado: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Estatus correspondientes a espacio reservado: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Tipo de movimiento correspondiente a ENTRADA POR RECIBO: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Tipo de movimiento correspondiente a SALIDA POR EMBARQUE: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Tipo de movimiento correspondiente a SALIDA POR MERMA: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Tipo de movimiento correspondiente a ENTRADA POR DEVOLUCIÓN: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Tipo de movimiento correspondiente a REUBICACIÓN: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Tipo de movimiento correspondiente a Traspaso: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Tipo de movimiento correspondiente a Agrupación: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Timbrado de pruebas: se elige por el usuario

  • Restringir modificación de activado/desactivado de los catálogos: se elige por el usuario

  • correo: se introduce por el usuario

  • Reporte default correspondiente a CITA: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Reporte default correspondiente a RECIBO: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Reporte default correspondiente a EMBARQUE: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Reporte default correspondiente a EMBARQUE LOGÍSTICA: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Reporte default correspondiente a INFORME ÚLTIMA MILLA: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Reporte default correspondiente a INFORME CFDI: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Reporte default correspondiente a MOVIMIENTOS ALMACÉN: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Reporte default correspondiente a REPORTE INSUMOS: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Reporte default correspondiente a REPORTE EXISTENCIA: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

  • Reporte default correspondiente a INVENTARIO FÍSICO: se elige de un listado de una tabla de la base de datos el cual elige el usuario.

    Finalmente al darle al botón de Modificar hace una llamada al backend para guardar la información.

  • Modificar parámetros de configuración

    • URL:
      • /api/ParametrosConfiguracion/Modificar/{idParametroConfiguracion}
    • Body:
      • escanearCargarVehiculo
      • evaluarProductoEmbarque
      • escanearInventario
      • escanearCambioUbicacion
      • idEspacioLibre
      • idEspacioOcupado
      • idEspacioReservado
      • idMovimientoSalidaMerma
      • idMovimientoEntradaDevolucion
      • idMovimientoEntradaRecibo
      • idMovimientoSalidaEmbarque
      • idMovimientoReubicacion
      • idMovimientoTraspaso
      • idCitaPendiente
      • idCitaPendienteConfirmacion
      • idCitaPendienteRecibo
      • idCitaCompletado
      • idCitaCancelada
      • idReciboPendienteConfirmar
      • idReciboConfirmadoPCalidad
      • idReciboAprobadoCalidad
      • idReciboCompletado
      • idReciboCompletadoIncidencias
      • idCitaCompletadoIncidencias
      • idCitaConfirmada
      • idReciboConteoRecibo
      • idReciboConteoAlmacen
      • idReciboRechazadoCalidad
      • idEmbarquePorEmbarcar
      • idEmbarqueAprobadoPorCalidad
      • idEmbarqueRechazadoPorCalidad
      • idEmbarqueAprobadoConIncidencias
      • idEmbarqueEmbarcado
      • idEmbarqueCancelado
      • idEmbarqueCompletado
      • idEmbarqueInspeccionadoDevolucion
      • idEmbarqueCompletadoConDevolucion
      • idCuestionarioReciboProducto
      • idCuestionarioReciboUnidad
      • idCuestionarioEmbarqueProducto
      • idCuestionarioEmbarqueUnidad
      • idMovimientoAgrupacion
      • idCuestionarioDevolucionProducto
      • idCuestionarioDevolucionUnidad
      • timbradoPrueba
      • entregaTerceros
      • restringir
      • idReciboCompletadoLogistica
      • idReciboCancelado
      • idEmbarqueEnRuta
      • idReporteDefaultCitas
      • idReporteDefaultRecibo
      • idReporteDefaultEmbarque
      • idReporteDefaultEmbarqueLogistica
      • idReporteDefaultInformeUltimaMilla
      • idReporteDefaultInformeCFDI
      • idReporteDefaultMovimientosAlmacen
      • idReporteDefaultReporteInsumos
      • idReporteDefaultReporteExistencias
      • idReporteDefaultInventarioFisico
      • correoFacturaUltimaMilla

Si la operación es exitosa se recarga el formulario. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Componente Filtros

Descripción general

El componente *Filtros.js* se inicializa entrando a los diferentes módulos donde se requiera, por ejemplo Citas, Recibo, Embarque, Catálogos entre otros.
Los filtros sirven para cargar listados de búsqueda específicos.

Inicialización de la vista

Al iniciar la vista (dependiendo en donde se utiliza ), se verifica la sesión del usuario: si no hay una sesión iniciada, se redirige al login; si hay una sesión activa se hace una llamada al backend y dependiendo del prop del componente padre. Los servicios que se cargar al inicializar el componente dependiendo de donde se utilice serian los siguiente:
**Zonas Operativas**
  • Obtener listado de zonas operativas

    • URL:

      • /api/ZonaOperativa/GetListadoByCodigoEstado/{codigoZona}/{estado}
    • Obtener listado de sucursales

      • URL:
        • /api/Sucursales/GetListado
    • Obtener listado de paises

      • URL:
        • /api/Pais/GetListado

      Inventario Cliente

  • Obtener listado de inventario

    • URL:

      • /api/Intentario/GetByFiltros/{idCliente}/{idSucursal}/{idZona}/{idEstatus}/{fechaInicial}/{fechaFinal}/${lote}
    • Obtener listado de sucursales por cliente

      • URL:
        • /api/Sucursales/GetListado/{idCliente}
    • Obtener listado de almacenes

      • URL:
        • /api/Almacenes/GetListado
    • Obtener listado estatus espacios

      • URL:
        • /api/EstatusEspacios/GetEstatusEspaciosActivos
    • Obtener listado productos

      • URL:
        • /api/Productos/GetListado/

      Inventarios

  • Obtener listado inventariar

    • URL:

      • /api/Intentariar/GetByFiltros/{folio}/{idCliente}/{idFamilia}/{idSucursal}/{idAlmacen}/{fechaInicial}/{fechaFinal}
    • Obtener listado productos

      • URL:
        • /api/Productos/GetListado/
    • Obtener listado de sucursales

      • URL:
        • /api/Sucursales/GetListado
    • Obtener listado de almacenes

      • URL:
        • /api/Almacenes/GetListado
    • Obtener listado estatus espacios

      • URL:
        • /api/EstatusEspacios/GetEstatusEspaciosActivos
    • Obtener filtro listado clientes

      • URL:
        • /api/Clientes/GetListadoByFiltros/{rfc}/{nombre}/{sucursal}
    • Obtener listado de familias

      • URL:
        • /api/Familia/GetListadoActivos

      Movimientos Almacén

  • Obtener listado de movimientos de almacén

    • URL: /api/MovimientosAlmacen/GetListadoByFiltros/{lote}/{fechaInicial}/{fechaFinal}/{idTipoMovimiento}/{idSucursal}/{idAlmacen}/{cliente}

    • Obtener listado de sucursales

      • URL:
        • /api/Sucursales/GetListado
    • Obtener listado de almacenes

      • URL:
        • /api/Almacenes/GetListado
    • Obtener listado tipo de movimientos

      • URL:
        • /api/TiposMovimientos/GetTiposMovimientosConfiguracion

      Citas

  • Obtener listado de citas

    • URL:
      • /api/Citas/GetListadoByFiltros/{folio}/{fechaCita}/{fechaInical}/{fechaFinal}/{idEstatus}/{idSucursal}
    • Obtener listado de sucursales
      • URL:
        • /api/Sucursales/GetListado
    • Obtener listado de estatus citas
      • URL:
        • /api/EstatusCita/GetListadoActivos

    Recibo

    • Obtener listado de recibo
      • URL:
        • /api/CitasRecibo/GetListadoByFiltros/{folio}/{fechaInical}/{fechaFinal}/{idEstatus}/{idSucursal}
    • Obtener listado de sucursales
      • URL:
        • /api/Sucursales/GetListado
    • Obtener listado de estatus recibo
      • URL:
        • /api/EstatusRecibo/GetListadoActivos

    Productos

    • Obtener listado de productos
      • URL:
        • /api/Productos/GetByFiltros/${nombre}/{codigoProducto}/{idEmbalaje}
    • Obtener listado de embalajes
      • URL:
        • /api/Embalajes/GetListado

    Andenes

    • Obtener listado de andenes
      • URL:
        • /api/Andenes/GetListado

    Clientes Almacén

    • Obtener listado de clientes
      • URL:
        • /api/Clientes/GetListadoByFiltros/{rfc}/{nombre}/{sucursal}
    • Obtener listado de sucursales
      • URL:
        • /api/Sucursales/GetListado

    Embarque

    • Obtener listado de embarques
      • URL:
        • /api/Embarque/GetListadoEmbarqueByFiltros/{folioEmbarque}/{fechaEmbarque}/{fechaInicial}/{fechaFinal}/{sucursal}/{estatus}/{destinatario}
    • Obtener listado de sucursales
      • URL:
        • /api/Sucursales/GetListado
    • Obtener listado de estatus citas
      • URL:
        • /api/EstatusEmbarque/GetEstatusEmbarqueSinPendiente
    • Obtener listado de remitentes
      • URL:
        • /api/Remitentes/GetListado

    Acciones de usuario

Buscar

Al realizar la búsqueda, pediendo donde se use el componente se realiza la búsqueda para obtener la información.
Zonas Operativas

  • Obtener listado de zonas operativas

    • URL:

      • /api/ZonaOperativa/GetListadoByCodigoEstado/{codigoZona}/{estado}

      Inventario Cliente

  • Obtener listado de inventario

    • URL:

      • /api/Intentario/GetByFiltros/{idCliente}/{idSucursal}/{idZona}/{idEstatus}/{fechaInicial}/{fechaFinal}/${lote}

      Inventarios

  • Obtener listado inventariar

    • URL:

      • /api/Intentariar/GetByFiltros/{folio}/{idCliente}/{idFamilia}/{idSucursal}/{idAlmacen}/{fechaInicial}/{fechaFinal}

      Movimientos Almacén

  • Obtener listado de movimientos de almacén

    • URL: /api/MovimientosAlmacen/GetListadoByFiltros/{lote}/{fechaInicial}/{fechaFinal}/{idTipoMovimiento}/{idSucursal}/{idAlmacen}/{cliente}

      Citas

  • Obtener listado de citas

    • URL:
      • /api/Citas/GetListadoByFiltros/{folio}/{fechaCita}/{fechaInical}/{fechaFinal}/{idEstatus}/{idSucursal}

    Recibo

    • Obtener listado de recibo
      • URL:
        • /api/CitasRecibo/GetListadoByFiltros/{folio}/{fechaInical}/{fechaFinal}/{idEstatus}/{idSucursal}

    Productos

    • Obtener listado de productos
      • URL:
        • /api/Productos/GetByFiltros/${nombre}/{codigoProducto}/{idEmbalaje}

    Clientes Almacén

    • Obtener listado de clientes
      • URL:
        • /api/Clientes/GetListadoByFiltros/{rfc}/{nombre}/{sucursal}

    Embarque

    • Obtener listado de embarques
      • URL:
        • /api/Embarque/GetListadoEmbarqueByFiltros/{folioEmbarque}/{fechaEmbarque}/{fechaInicial}/{fechaFinal}/{sucursal}/{estatus}/{destinatario}

Si la operación es exitosa se recarga el formulario. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Importar

Descripción general

Para *importar* se inicia entrando a los diferentes módulos donde se requiera, por ejemplo Citas, Recibo, Embarque, Catálogos entre otros.
Importar sirve para ingresar o agregar una cantidad grande de productos o embarques.

Inicialización

Dependiendo del flujo donde se requiera se utiliza un fileReader() para elegir un archivo del almacenamiento local de la computadora del usuario.

Acciones de usuario

Importar productos en catálogo de productos

Para importar productos se requiere seleccionar importar desde la pestaña superior, en la sección de importar. Una vez seleccionada la pestaña se abrirá una ventana con una mensaje de advertencia que al darle entendido procederá a elegir el archivo tipo excel. Cuando se carga el archivo primero se valida que los datos del archivo sean correctos, si hay algún problema con los datos manda un mensaje de advertencia indicando los errores. En el caso que los datos introducidos sean correctos se hace una llamada al back para guardar la información.

  • Importar productos
    • URL:
      • /api/Productos/Importar
    • Body:
      • codigoProducto
      • producto
      • descripcion
      • familia
      • embalaje
      • temperaturaMin
      • temperaturaMax
      • periodoCaducidad
      • capacidadMin
      • capacidadMax
      • capacidadResurtido
      • largo
      • ancho
      • alto
      • peso
      • unidadMedida
      • claveProducto
      • claveUnidad
      • esMaterialPeligroso
      • claveFraccionArancelaria
      • comercioExterior
      • claveMaterialPeligroso
      • claveEmbalaje
      • descripcionEmbalaje
      • activo
      • restringirAcomodo
      • apilable
      • pesoEstibado
      • piezasPorProducto
      • piezasEstibado
      • piezasEmpaque
      • empaquesTarimas
      • caducidad
      • identificableUnidad
      • sectorCOFEPRIS
      • denominacionGenerica
      • denominacionDistintiva
      • fabricante
      • fechaCaducidadFarmaco
      • loteMedicamento
      • claveFormaFarmaceutica
      • formaFarmaceutica
      • claveCondicionEspecial
      • registroSanitario_folioAutorizacion
      • nombreIngredienteActivo
      • nombreQuimico
      • numeroCAS
      • numRegSanPlagCOFEPRIS
      • datosFabricante
      • datosFormulador
      • datosMaquilador
      • usoAutorizado
      • esFarmaco

Si la operación es exitosa se recarga el formulario. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.
Importar embarques
Para importar productos se requiere seleccionar importar desde la pestaña superior, en la sección de importar. Una vez seleccionada la pestaña se abrirá la pestaña para que el usuario seleccione los siguientes datos requeridos:

  • Fecha de embarque: el usuario lo elige

  • Hora de embarque: el usuario lo elige

  • Cliente: viene de la tabla CatClientesAM de la base de datos

  • Sucursal: viene del ERP

  • Archivo excel: el usuario lo sube desde su local

    Una vez seleccionado el archivo y selecciona Aceptar se valida que la fecha no sea menor al día actual, si hay error con las fechas se manda mensaje de error, pero si todo está bien se hace una llamada al back para guardar los embarques.

  • Importar embarques

    • URL:
      • /api/Embarque/AgregarImportados
    • Body:
      • idCliente
      • idSucursal
      • fecha
      • destinatario
      • codigoProducto
      • cantidad
      • horaEntrega
      • fechaEmbarque

Si la operación es exitosa se dirige al usuario al listado. Si surge algún error se muestra mensaje al usuario y se permanece en el formulario.

Cubicaje

Descripción general

El cubicaje se utiliza en dos módulos del sistema, se utiliza en Movimiento de Almacén y en Logística Almacén. Para el caso de Movimiento de Almacén se utiliza al agregar un nuevo movimiento tipo entrada y en el caso de Logistica Almacen en el listado de recibo al asignar productos.
El Cubicaje se utiliza para agilizar el proceso de acomodo de la mercancía que llega y se va almacenar en los diferentes almacenes, además de poder visualizar como quedará acomodado dependiendo el tamaño del espacio y los productos.

Servicio Cubicar

Empezaremos llamando el servicio desde agregar movimiento en movimientos almacén o asignar producto en logistica almacen, al momento de elegir un producto seleccionamos el botón Buscar Espacio el cual hace una llama al backend y traer la información.

  • Cubicar productos
    • URL:
      • /CubicarMultiple
    • Body:
      • idProducto
      • idSucursal
      • cantidad
      • idCliente
      • idAlmacen
      • cantidad
      • ubicacionesAsignadas

@PostMapping(“/CubicarMultiple”)
public ResponseEntity<?> cubicarMultiple(@RequestBody CubicarRequest request, @RequestHeader(“RFC”) String rfc) throws Exception {
try{
Connection jdbcConnection = dbConection.getconnection(rfc);
List<EspaciosAsignadosReponse> response = cubicarService.cubicarMultiple(request,jdbcConnection);
if (response != null) {
return ResponseEntity.ok(response);
}
return ResponseEntity.status(500).body(“Las dimensiones de los productos sobrepasan las de los espacios.”);

} catch (Exception e){
e.printStackTrace();
return ResponseEntity.status(500).body(“Hubo un error al guardar los cambios. Intente más tarde.”);
}
}

Al hacer la llamada se crea un listado de EspaciosAsignadosResponse para mandar a llamar cubicarMultiple e iremos viendo paso a paso la funcion en java para traer los espacios.

Primero se crean 2 variables donde se guarda la cantidad total y se van a guardar los espacio asignados, además de correr un query para buscar los espacios disponibles:

int cantidadTotal = request.getCantidad();
//listado espaciosAsignados objetos(clase)
List <EspaciosAsignadosReponse> espaciosAsignadosReponse = new ArrayList<>();
String query = “select PIE.IdEspacio,\n” +
” CEA.Espacio, \n” +
” PIE.IdInventarioEspacio, \n” +
” CZAA.IdZonaAlmacen, \n” +
” CAA.IdAlmacenes, \n” +
” CEA.Alto, \n” +
” CEA.Ancho, \n” +
” CEA.Largo, \n” +
” CAA.Almacen, \n” +
” CZAA.ZonaAlmacen, \n” +
” CEA.ConsiderarPeso, \n” +
” ISNULL(CEA.PesoEspacio,0.0) as PesoEspacio \n” +
” from ProInventarioEspaciosAM PIE \n” +
” INNER JOIN CatEspacioAM CEA on PIE.IdEspacio = CEA.IdEspacio \n” +
” inner join CatZonaAlmacenAM CZAA on CEA.IdZonaAlmacen = CZAA.IdZonaAlmacen \n” +
” inner join CatAlmacenesAM CAA on CZAA.IdAlmacen = CAA.IdAlmacenes \n” +
” left join ProInventarioEspaciosProductosAM PIEPA on PIE.IdInventarioEspacio = PIEPA.IdInventarioEspacio\n”+
” where CEA.Activo = 1 \n” +
” and CAA.Activo = 1 \n” +
” and (IdEstatus = (select CPC.IdEspacioReservado from CatParametrosConfiguracionAM CPC) or IdEstatus = (select CPC.IdEspacioLibre from CatParametrosConfiguracionAM CPC) or IdEstatus = (select CPC.IdEspacioOcupado from CatParametrosConfiguracionAM CPC)) \n” +
” and CEA.IdEspacio in (select CEF.IdZonaAlmacen \n” +
” From CatEspacioFamiliasAM CEF \n” +
” where IdFamilia = \n” +
” (select CP.IdFamilia \n” +
” From CatProductosAM CP \n” +
” WHERE CP.IdProducto = ”+ request.getIdProducto() +”))\n” +
” and CAA.IdSucursal = ”+ request.getIdSucursal()+“\n” +
” and (PIE.IdCliente = ”+ request.getIdCliente() +” or PIE.IdCliente = 0) \n” +
” and (CZAA.IdAlmacen = ” + request.getIdAlmacen() + ” or ” + request.getIdAlmacen() + ” = 0) \n” +
” and PIE.EspacioListadoSeleccionado = 0 \n” +
” and CAA.TemperaturaMax <= (select PAM.TemperaturaMax from CatProductosAM PAM where IdProducto = ” + request.getIdProducto()+”)\n” +
” and CAA.TemperaturaMin >= (select PAM2.TemperaturaMin from CatProductosAM PAM2 where IdProducto = ” + request.getIdProducto()+”)” +
“GROUP BY PIE.IdEspacio,\n” +
” CEA.Espacio,\n” +
” PIE.IdInventarioEspacio,\n” +
” CZAA.IdZonaAlmacen,\n” +
” CAA.IdAlmacenes,\n” +
” CEA.Alto,\n” +
” CEA.Ancho,\n” +
” CEA.Largo,\n” +
” CAA.Almacen,\n” +
” CZAA.ZonaAlmacen,\n” +
” CEA.ConsiderarPeso,\n” +
” CEA.PesoEspacio\n” +
“ORDER BY ISNULL(MIN(PIEPA.FechaCaducidad),‘2060-12-12’) ASC”;
Statement statement = jdbcConnection.createStatement();
ResultSet rs = statement.executeQuery(query);
JSONArray espaciosDisponibles = UtilFuctions.convertArray(rs);

Enseguida se corre otro query para obtener la información del producto mediante el idProducto:
query = “select Alto, Ancho, Largo, Peso, ISNULL(Apilable, 0) as Apilable, ISNULL(PesoEstibado,0) as PesoEstibado \n” +
” from CatProductosAM\n” +
” where IdProducto = ” + request.getIdProducto();
statement = jdbcConnection.createStatement();
rs = statement.executeQuery(query);
JSONObject datosProductos = UtilFuctions.convertObject(rs);

Después se consigue el peso de los productos:
BigDecimal pesoProductos = datosProductos.getBigDecimal(“Peso”);
String pesoProducto= pesoProductos.toString();

Ahora dentro de un for, el tamaño depende de los espacios disponibles que se encontraron anteriormente. Dentro del form vamos a crear un container de la librería XFLP y declarar el tipo de container:
for(int i=0; i<espaciosDisponibles.length();i++) {
JSONObject espacio = espaciosDisponibles.getJSONObject(i);
XFLP container = new XFLP();
container.setTypeOfOptimization(XFLPOptType.BEST_FIXED_CONTAINER_PACKER);
BigDecimal pesoEspacio = espacio.getBigDecimal(“PesoEspacio”);
String pesoString = pesoEspacio.toString();

Una vez creado nuestro container procedemos a validar si el espacio se considera el peso o no, si se considera el peso se agrega el pesoString al container y si no, se agrega una cantidad elevada para que no lo tome en cuenta al asignar el espacio:
if (espacio.getBoolean(“ConsiderarPeso”) == true) {
container.addContainer().setLength(espacio.getInt(“Largo”)).setWidth(espacio.getInt(“Ancho”)).setHeight(espacio.getInt(“Alto”)).setMaxWeight(Float.parseFloat(pesoString)).setContainerType(“Caja”);
//container.addContainer();
} else {
container.addContainer().setLength(espacio.getInt(“Largo”)).setWidth(espacio.getInt(“Ancho”)).setHeight(espacio.getInt(“Alto”)).setMaxWeight(10000).setContainerType(“Caja”);
//container.addContainer();
}

Terminando de agregar el tamaño al container se ejecuta el procedimiento usp_ProAgregarProductosCubicaje para buscar los productos que ya están en los espacios que aún están disponibles y los agrega al contenedor:

query = “EXEC uspProAgregarProductosCubicaje ” + espacio.getInt(“IdEspacio”);
statement = jdbcConnection.createStatement();
rs = statement.executeQuery(query);
JSONArray productosEspacio = UtilFuctions._convertArray
(rs);

for (int x = 0; x < productosEspacio.length(); x++) {
JSONObject cantidad = productosEspacio.getJSONObject(x);
//set loading es para que considere lo que ya esta dentro
container.addItem().setExternID(“-1”).setStackingWeightLimit(cantidad.getBoolean(“Apilable”) ? cantidad.getInt(“PesoEstibado”) : 1000).setLength(cantidad.getInt(“Largo”)).setWidth(cantidad.getInt(“Ancho”)).setHeight(cantidad.getInt(“Alto”)).setWeight(cantidad.getInt(“Peso”)).setLoadingLocation(String.valueOf((x)));
//container.addItem();
}

Una vez agregados los productos al contenedor vamos agregar los productos nuevos que se intentan agregar:
int cantidadTotalProductoAsignado = 0;

for (DimensionesEspaciosRequest producto : request.getProductos().stream().filter(e -> e.getIdEspacio() == espacio.getInt(“IdEspacio”)).collect(Collectors.toList())) {
cantidadTotalProductoAsignado += producto.getCantidad();
for (int z = 0; z < producto.getCantidad(); z++) {
if (espacio.getBoolean(“ConsiderarPeso”)) {
container.addItem().setExternID(“2”).setStackingWeightLimit(producto.isApilable() ? producto.getPesoEstibado() : 10000).setLength(producto.getLargo()).setWidth(producto.getAncho()).setHeight(producto.getAlto()).setWeight(producto.getPeso());
} else {
container.addItem().setExternID(“2”).setStackingWeightLimit(producto.isApilable() ? producto.getPesoEstibado() : 10000).setLength(producto.getLargo()).setWidth(producto.getAncho()).setHeight(producto.getAlto()).setWeight(1);
}
}
}
for (int z=0; z<cantidadTotal;z++){
if (espacio.getBoolean(“ConsiderarPeso”)){
container.addItem().setExternID(“1”).setStackingWeightLimit(datosProductos.getBoolean(“Apilable”) ? datosProductos.getInt(“PesoEstibado”) : 1000 ).setLength(datosProductos.getInt(“Largo”)).setWidth(datosProductos.getInt(“Ancho”)).setHeight(datosProductos.getInt(“Alto”)).setWeight(Float.parseFloat(pesoProducto));
}else{
container.addItem().setExternID(“1”).setStackingWeightLimit(datosProductos.getBoolean(“Apilable”) ? datosProductos.getInt(“PesoEstibado”) : 1000 ).setLength(datosProductos.getInt(“Largo”)).setWidth(datosProductos.getInt(“Ancho”)).setHeight(datosProductos.getInt(“Alto”)).setWeight(1);
}
}

Para finalizar vamos a correr una funcion de la libreria donde nos regresa un reporte para ver si efectivamente cabe el o los productos que se quieren almacenar en los espacios disponibles y se ejecuta un procedimiento almacenado para cambiar el estatus de los espacios que se llenen

container.executeLoadPlanning();
LPReport report = container.getReport();

//restar cantidad total producto (del report) y agregar objeto al listado
if(report.getContainerReports().size() \== 0) {
continue;
}
if(report.getContainerReports().get(0).getPackageEvents().stream().filter(r \-\> r.getId().compareToIgnoreCase("-1") \== 0).count() \!= productosEspacio.length() || report.getContainerReports().get(0).getPackageEvents().stream().filter(r \-\> r.getId().compareToIgnoreCase("2") \== 0).count() \!= cantidadTotalProductoAsignado) {
continue;
}
if((int) report.getContainerReports().get(0).getPackageEvents().stream().filter(r \-\> r.getId().compareToIgnoreCase("1") \== 0).count() \== 0) {
continue;
}
cantidadTotal \= cantidadTotal \- ((int) report.getContainerReports().get(0).getPackageEvents().stream().filter(r \-\> r.getId().compareToIgnoreCase("1") \== 0).count());
query \= "EXEC usp\_ProCambioEstatusEspacioAM " \+ espacio.getInt("IdEspacio") \+ ", 2";
statement \= jdbcConnection.createStatement();
statement.executeUpdate(query);
espaciosAsignadosReponse.add(new EspaciosAsignadosReponse(espacio.getInt("IdEspacio"),espacio.getString("Espacio"), espacio.getInt("IdInventarioEspacio"),espacio.getInt("IdZonaAlmacen"), espacio.getInt("IdAlmacenes"), (int) report.getContainerReports().get(0).getPackageEvents().stream().filter(r \-\> r.getId().compareToIgnoreCase("1") \== 0).count(),espacio.getString("Almacen"),espacio.getString("ZonaAlmacen"),espacio.getInt("PesoEspacio")));
//if cantidad total es igual a 0 break,
if (cantidadTotal \<= 0) {
break;
}

}

//regresar listado de obejtos asignados
//EspaciosAsignadosReponse resultado = new EspaciosAsignadosReponse(espaciosAsignadosReponse);
return espaciosAsignadosReponse;

} catch (Exception e) {
e.printStackTrace();
return null;
}

Si la operación es exitosa setea los espacios donde se guardaran los productos. Si surge algún error se muestra un mensaje al usuario diciendo que no hay espacios disponibles.

Servicio Cubicar Visualización

Empezaremos llamando el servicio desde asignar producto en logistica almacén, al momento de elegir un producto seleccionamos el botón Buscar Espacio el cual hace una llama al backend y trae la información para setearla. Una vez elegido el espacio podremos seleccionar el icono de ojo para visualizar el producto en el espacio.

  • Cubicar Visualizar
    • URL:
      • /CubicarVisualizador
    • Body:
      • idEspacio
      • nombreEspacio
      • largoEspacio
      • anchoEspacio
      • altoEspacio
      • productos:
        • idProducto
        • descripcion
        • largo
        • ancho
        • alto
        • peso
        • cantidad

@PostMapping(“/CubicarVisualizador”)
public ResponseEntity<?> cubicarMultipleVisualizador(@RequestBody CubicarRequest request, @RequestHeader(“RFC”) String rfc) throws Exception {
try{
Connection jdbcConnection = dbConection.getconnection(rfc);
CubicarResponse response = cubicarService.cubicarVisualizacion(request,jdbcConnection);
if (response != null) {
return ResponseEntity.ok(response);
}
return ResponseEntity.status(500).body(“Las dimensiones de los productos sobrepasan las de los espacios.”);

} catch (Exception e){
e.printStackTrace();
return ResponseEntity.status(500).body(“Hubo un error al guardar los cambios. Intente más tarde.”);
}
}

Al llamar el servicio primero se corre la funcion cubicarVisualizacion para guardar en el objeto CubicarResponse. La funcion mencionada funciona similar al servicio anterior, con la diferencia que no busca varios espacios, solo uno en especifico y regresa el reporte con las dimensiones para utilizarse en el front:

String query = “select PIE.IdEspacio,\n” +
” CEA.Espacio,\n” +
” PIE.IdInventarioEspacio,\n” +
” CZAA.IdZonaAlmacen,\n” +
” CAA.IdAlmacenes,\n” +
” CEA.Alto,\n” +
” CEA.Ancho,\n” +
” CEA.Largo,\n” +
” CAA.Almacen,\n” +
” CZAA.ZonaAlmacen,\n” +
” CEA.ConsiderarPeso,\n” +
” ISNULL(CEA.PesoEspacio,0) as PesoEspacio\n” +
” from ProInventarioEspaciosAM PIE\n” +
” INNER JOIN CatEspacioAM CEA on PIE.IdEspacio = CEA.IdEspacio\n” +
” inner join CatZonaAlmacenAM CZAA on CEA.IdZonaAlmacen = CZAA.IdZonaAlmacen\n” +
” inner join CatAlmacenesAM CAA on CZAA.IdAlmacen = CAA.IdAlmacenes\n” +
” where CEA.IdEspacio = ” + request.getIdEspacio();
Statement statement = jdbcConnection.createStatement();
ResultSet rs = statement.executeQuery(query);
JSONObject espacio = UtilFuctions.convertObject(rs);

/**JSONArray resultados = new JSONArray();
JSONObject resultado;*/

CubicarResponse response = new CubicarResponse(espacio.getInt(“IdEspacio”), espacio.getString(“Espacio”), espacio.getInt(“Largo”), espacio.getInt(“Ancho”), espacio.getInt(“Alto”), espacio.getInt(“PesoEspacio”),espacio.getBoolean(“ConsiderarPeso”), new ArrayList<>());
XFLP container = new XFLP();
container.setTypeOfOptimization(XFLPOptType.BEST_FIXED_CONTAINER_PACKER);
BigDecimal pesoEspacio = espacio.getBigDecimal(“PesoEspacio”);
String pesoString = pesoEspacio.toString();
System.out.println(Float.parseFloat(pesoString));
if(espacio.getBoolean(“ConsiderarPeso”)){
container.addContainer().setLength(espacio.getInt(“Largo”)).setWidth(espacio.getInt(“Ancho”)).setHeight(espacio.getInt(“Alto”)).setMaxWeight(Float.parseFloat(pesoString)).setContainerType(“Caja”);
//container.addContainer();
}else{
container.addContainer().setLength(espacio.getInt(“Largo”)).setWidth(espacio.getInt(“Ancho”)).setHeight(espacio.getInt(“Alto”)).setMaxWeight(10000).setContainerType(“Caja”);
//container.addContainer();
}

query = “EXEC uspProAgregarProductosCubicaje ” + espacio.getInt(“IdEspacio”);
statement = jdbcConnection.createStatement();
rs = statement.executeQuery(query);
JSONArray productosEspacio = UtilFuctions._convertArray
(rs);

for (int x = 0; x < productosEspacio.length(); x++) {
JSONObject cantidad = productosEspacio.getJSONObject(x);
//set loading es para que considere lo que ya esta dentro
container.addItem().setExternID(cantidad.getInt(“id”) + ”-” + cantidad.getString(“Producto”)+”-“+1).setStackingWeightLimit(cantidad.getBoolean(“Apilable”) ? cantidad.getInt(“PesoEstibado”) : 100000).setLength(cantidad.getInt(“Largo”)).setWidth(cantidad.getInt(“Ancho”)).setHeight(cantidad.getInt(“Alto”)).setWeight(cantidad.getInt(“Peso”));
//container.addItem();
}

for (DimensionesEspaciosRequest producto : request.getProductos()) {
for (int z = 0; z < producto.getCantidad(); z++) {
if (espacio.getBoolean(“ConsiderarPeso”)) {
container.addItem().setExternID(producto.getIdProducto() + ”-” + producto.getDescripcion()+”-“+0).setStackingWeightLimit(producto.isApilable() ? producto.getPesoEstibado() : 100000).setLength(producto.getLargo()).setWidth(producto.getAncho()).setHeight(producto.getAlto()).setWeight(producto.getPeso());
//container.addItem();
} else {
container.addItem().setExternID(producto.getIdProducto() + ”-” + producto.getDescripcion()+”-“+0).setStackingWeightLimit(producto.isApilable() ? producto.getPesoEstibado() : 100000).setLength(producto.getLargo()).setWidth(producto.getAncho()).setHeight(producto.getAlto()).setWeight(1);
// container.addItem();
}
}
}

container.executeLoadPlanning();
LPReport report = container.getReport();
if(report.getContainerReports().size() == 0) {
return null;
}
List<ProductosResponse> productos = new ArrayList<>();
for (LPPackageEvent packageEvent : report.getContainerReports().get(0).getPackageEvents()) {
productos.add(new ProductosResponse(Integer.parseInt(packageEvent.getId().split(”-”)[0]), packageEvent.getId().split(”-”)[1],packageEvent.getLength(), packageEvent.getHeight(),packageEvent.getWidth(),Math.round(packageEvent.getWeight()), packageEvent.getX(),packageEvent.getY(), packageEvent.getZ(),espacio.getInt(“IdEspacio”),Integer.parseInt(packageEvent.getId().split(”-”)[2]) == 1));
}

response.setProductos(productos);
//if cantidad total es igual a 0 break,

//regresar listado de obejtos asignados
//EspaciosAsignadosReponse resultado = new EspaciosAsignadosReponse(espaciosAsignadosReponse);
return response;

} catch (Exception e) {
e.printStackTrace();
return null;
}

Se busca el espacio, se crea el container con la información del espacio, se buscan los productos del espacio y se agregan al container, para finalmente regresar el espacio con los productos más el producto nuevo.
Si la operación es exitosa setea el espacio con sus respectivos productos y abre una ventana amplia donde se muestra en 3d cómo estaría el acomodo de los productos en el espacio, tomando en cuentas sus dimensiones.

Siendo el contenedor más claro el producto que se intenta ingresar al almacén. Este componente tipo Dialog está en el archivo Cubicar.js donde se utiliza un componente Canvas que se Importa con anterioridad y sirve para visualizar la ventana anterior.
<Canvas>
<color attach=“background” args={[‘lightgray’]} />
<PerspectiveCamera
makeDefault
position={[this*.state.data.anchoEspacio + 10, this.state.data.altoEspacio + 2,this.state.data.largoEspacio ]}
fov={60}
zoom={1}
/>
<ambientLight intensity={0.3}/>
<Base
position={[2, 0, 0]}
size={[50, 0,50]}/>
<Box
position={[(this
.state.data.anchoEspacio / 2), (this.state.data.altoEspacio / 2), (this.state.data.largoEspacio / 2)]}
size={[this
.state.data.anchoEspacio, this.state.data.altoEspacio,this.state.data.*largoEspacio]}/>

{/\* *\<mesh*
*position={\[(this.state.data.largoEspacio / 2),0, this.state.data.anchoEspacio \+ 1\]}*
*scale={1}\>*
*\<boxGeometry args={\[this.state.data.anchoEspacio,0.05,0.05\]}/\>*
*\<meshStandardMaterial /\>*
*\</mesh\>*\*/}
\<Html *style*\={{color:"white"}} *position*\={\[(this*.state.data.*anchoEspacio \+ 1.2),0,(this*.state.data.*largoEspacio / 2)\]} *className*\="html-story-label html-story-label-b"\>
Largo: {this*.props.*largoEspacio \+ " cm"}
\</Html\>
{/\**\<mesh*
*position={\[0,(this.state.data.altoEspacio / 2), this.state.data.anchoEspacio \+ 1\]}*
*scale={1}\>*
*\<boxGeometry args={\[0.05,this.state.data.altoEspacio,0.05\]}/\>*
*\<meshStandardMaterial /\>*
*\</mesh\>*\*/}
\<Html *style*\={{color:"white"}} *position*\={\[0,(this*.state.data.*altoEspacio / 2), this*.state.data.*anchoEspacio \+ 1.2\]} *className*\="html-story-label html-story-label-b"\>
Alto: {this*.props.*altoEspacio \+ " cm"}
\</Html\>
{/\**\<mesh*
*position={\[this.state.data.largoEspacio+1,0, this.state.data.anchoEspacio/2\]}*
*scale={1}\>*
*\<boxGeometry args={\[0.05,0.05,this.state.data.anchoEspacio\]}/\>*
*\<meshStandardMaterial /\>*
*\</mesh\>*\*/}
\<Html *style*\={{color:"white"}} *position*\={\[this*.state.data.*anchoEspacio/2,0,this*.state.data.*largoEspacio\+1.2 \]} *className*\="html-story-label html-story-label-b"\>
Ancho: {this*.props.*anchoEspacio \+ " cm"}
\</Html\>
{
this*.state.data.*productos &&
this*.state.data.productos.filter*(p \=\> \!*p.*asignado)*.map*((p,index) \=\> {
*return* (
\<BoxPack *key*\={*getRandomId*()} *color*\={'red'} *descripcion*\={*p.*descripcion} *positionLabel*\={\[this*.state.data.*anchoEspacio \+5, this*.state.data.*altoEspacio , this*.state.data.*largoEspacio/2\]}
*position*\={\[*p.*posicionX \+ (*p.*ancho / 2) , *p.*posicionZ \+ (*p.*alto / 2), *p.*posicionY \+ (*p.*largo / 2) \]}
*size*\={\[*p.*ancho, *p.*alto, *p.*largo\]}/\>
)
})
}
{
this*.state.data.*productos &&
this*.state.data.productos.filter*(p \=\> *p.*asignado)*.map*((p, index) \=\> {
*return* (
\<BoxPackAssigned *key*\={*getRandomId*()} *color*\={'red'} *descripcion*\={*p.*descripcion} *positionLabel*\={\[this*.state.data.*anchoEspacio \+5, this*.state.data.*altoEspacio , this*.state.data.*largoEspacio/2\]}
*position*\={\[*p.*posicionX \+ (*p.*ancho / 2) , *p.*posicionZ \+ (*p.*alto / 2), *p.*posicionY \+ (*p.*largo / 2) \]}
*size*\={\[*p.*ancho, *p.*alto, *p.*largo\]}/\>
)
})
}
\<OrbitControls *enableZoom*\={true}
*enablePan*\={true}
*enableRotate*\={true}
*target*\={\[(this*.state.data.*anchoEspacio / 2), (this*.state.data.*altoEspacio / 2),(this*.state.data.*largoEspacio / 2) \]}/\>
\</Canvas\>

Si surge algún error en el servicio se muestra un mensaje de error al usuario y se mantiene en el formulario.

Reportes

Descripción general

Los reportes se utilizan en gran parte del sistema, principalmente en Citas, Recibo, Embarque, Almacenes, Inventario y Última milla. Para generar estos reportes se utiliza una llamada al back pero estos servicios están en WebDev y la mayoría de los reportes utiliza un solo servicio que comparten varios reportes creados por WebDev.
Los reportes sirven para descargar de manera física los procesos importantes del sistema.

Inicialización

Para generar el reporte se hace una llamada al backend y se trae la información del servicio el cual dependiendo del tipo de reporte que se requiera, puede ser en formato PDF o Excel.

Acciones de usuario

Reportes Cita, Embarque y Recibo

En los listados de Citas, Embarque y Recibo, en sus respectivos módulos, dependiendo el estatus estará disponible la acción Generar Reporte en la columna de Acciones con un icono en forma de archivo o libro.

  • Imprimir formato
    • URL:
      • /ImprimirFormato/

Si la operación es exitosa se genera el archivo en una nueva pestaña o directo a descargas. Si surge algún error se muestra un mensaje al usuario o no realiza nada.

Reportes Almacenes y Movimientos de almacén

En los módulos correspondientes hay una pestaña en la parte superior donde puedes elegir la sección de Reportes, al seleccionar el reporte que se requiera, de igual forma se hace una llamada al backend para generar el reporte.

  • Imprimir formato
    • URL:
      • /ImprimirFormato/

Si la operación es exitosa se genera el archivo en una nueva pestaña o directo a descargas. Si surge algún error se muestra un mensaje al usuario.

Última milla

Descripción general

En el menú lateral izquierdo, el módulo de Última milla, se muestra el componente UltimaMilla.js donde redirige al usuario a un mapa con opciones para elegir embarque, rutas, unidades etc.
La última milla se utiliza para embarcar los Embarques a las diferentes rutas y destinatarios y supervisar en el mapa las salidas y llegadas de los operadores que llevan la mercancía a los destinatarios.

Inicialización

Al iniciar la vista se verifica la sesión del usuario: si no hay una sesión iniciada, se redirige al login; si hay una sesión activa se hace una llamada al backend para obtener el diferentes listados que se van a requerir.
  • Obtener listado de remolques
    • URL:
      • /api/Unidades/GetListadoRemolques
  • Obtener listado de operadores
    • URL:
      • /api/Operadores/GetListado
  • Obtener listado de sucursales
    • URL:
      • /api/Sucursales/GetListado
  • Obtener listado de unidades
    • URL:
      • /api/Unidades/GetListado

Al obtener los listado se muestran en varios select que permite hacer algunas acciones con ellos: enviar ruta a operadores, cronograma, enviar mensajes a operador, generar reporte, cancelar ruta, reemplazar paquete, cambiar ubicación parada, generar CFDI traslado, descargar xml traslado, eliminar parada, entre otros.

Acciones del usuario

Enviar ruta a operadores

Para poder enviar la ruta los operadores se debe seleccionar el botón Generar Rutas que se localiza en la parte superior de la ventana. Se necesitan los siguientes datos:

Si la operación es exitosa se recarga el formulario y se muestran las secciones de cronómetro, mensajes y resumen de paradas. Si surge algún error se muestra mensaje de error al usuario y se permanece en la pantalla.

Cronograma

Para abrir el cronograma el usuario debe seleccionar la pestaña Cronograma que se localiza en la parte inferior de la ventana, donde se va abrir una sección con la información del repartidor, unidad, tiempo, estatus de entrega y paradas. Además de mostrarle la información al usuario también está la opción de reasignar el operador, la cuenta entra cuando se hace clic en el círculo de las paradas y se elige el operador, enseguida se realiza una llamada al backend para re asignar el operador.

  • Reasignar operador de la guia
    • URL:
      • /api/ReasignarGuia/{idUnidadSeleccionada}/{idParadaFuente}/{idGuia}

Si la operación es exitosa se recarga el formulario. Si surge algún error se muestra mensaje de error al usuario y se permanece en la pantalla.

Enviar mensajes a operador

Para poder enviar mensaje al operador primero el usuario debe seleccionar el botón con icono de sobre y seleccionar a los repartidores, lo cual hace una llamada al backend para obtener los mensaje que anteriormente se hayan enviado.
  • Mensajes por usuario y operador

    • URL:
      • /api/UltimaMilla/Chat/GetMensajesPorUsuarioOperador/{idUsuario}/{idOperador}

    Al cargar los mensajes (en el caso que haya), se despliega un cuadro y los mensajes, además de un campo para enviar mensajes nuevos. Al introducir el mensaje se hace llamada al backend para guardar el mensaje.

  • Guardar chat última milla

    • URL:
      • /api/UltimaMilla/Chat
    • Body:
      • esOperador
      • fechaHora
      • idOperador
      • idUsuario
      • mensaje

Si la operación es exitosa se recarga el formulario y se muestra el nuevo mensaje. Si surge algún error se muestra mensaje de error al usuario y se permanece en la pantalla.

Generar reporte

Para generar reporte el usuario debe de seleccionar el botón de la ventana en la parte derecha, al seleccionar se abre un recuadro donde se muestra el Resumen de Paradas. Dentro del resumen se muestran las paradas pendientes, exitosas y/o fallidas, además de las paradas con sus respectivos operadores y unidades. Para generar el reporte se hace clic en el icono de archivo el cual abre un recuadro para elegir el reporte y una vez elegido se hace una llamada al backend para traer el reporte.

Si la operación es exitosa se recarga el formulario y se abre una ventana con el reporte. Si surge algún error se muestra mensaje de error al usuario y se permanece en la pantalla.

Cancelar ruta

Dentro del resumen de paradas se debe presionar el icono de cancelar el cual primero envía un mensaje de advertencia y al continuar se realiza la llamada al backend para cancelar la ruta.

  • Eliminar ruta
    • URL:
      • /api/UltimaMilla/EliminarRuta/{idParadaUltimaMilla}

Si la operación es exitosa se recarga el formulario y en la sección de resumen de parada se puede observar la parada pero en estatus cancelada quitando las demás opciones disponibles. Si surge algún error se muestra mensaje de error al usuario y se permanece en la pantalla.

Reemplazar paquete

Dentro del resumen de paradas se debe seleccionar el embarque, desplegando más información y una sección de Acciones. Seleccionamos el botón de Reemplazar lo cual abre un recuadro y hace una llamada al backend para traer un listado de paquetes a embarcar.

  • Obtener listado de paquetes

    • URL:
      • /api/UltimaMilla/GetListadoPaquetes

    Al seleccionar el paquete se manda a llamar el backend para reemplazar el paquete.

  • Reemplazar parada

    • URL:
      • /api/UltimaMilla/RemplazarParada/{idParadaUltimaMilla}/{idPaqueteViejo}
    • Body:
      • esRecoleccion
      • idNuevaGuia
      • nuevoEsRecoleccion
      • lat
      • lng
      • idEstatusGuia

Si la operación es exitosa se recarga el formulario. Si surge algún error se muestra mensaje de error al usuario y se permanece en la pantalla.

Cambiar ubicación

Dentro del resumen de paradas se debe seleccionar el embarque, desplegando más información y una sección de Acciones. Seleccionamos el botón de cambiar ubicación lo cual abre un recuadro donde el usuario debe ingresar la ubicación manualmente o mediante el mapa que se muestra.
Al llenar la información se realiza una llamada en el backend para guardar la información.

  • Actualizar coordenadas
    • URL:
      • /api/ultimaMilla/ActualizarCoordenadas/{idEmbarque}/{latitud}/{longitud}

Si la operación es exitosa se recarga el formulario. Si surge algún error se muestra mensaje de error al usuario y se permanece en la pantalla.

Generar CFDI traslado

Dentro del resumen de paradas se debe seleccionar el embarque, desplegando más información y una sección de Acciones. Seleccionamos el botón de cambiar ubicación lo cual abre un mensaje de advertencia y se hace la llamada al backend para generar el reporte.

Si la operación es exitosa se recarga el formulario. Si surge algún error se muestra mensaje de error al usuario y se permanece en la pantalla.

Descargar XML Traslado

Dentro del resumen de paradas se debe seleccionar el embarque, desplegando más información y una sección de Acciones. Seleccionamos el botón Descargar XML Traslado e inmediatamente hacemos una llamada al backend para traer el xml.

Si la operación es exitosa se descarga el xml automáticamente. Si surge algún error se muestra mensaje de error al usuario y se permanece en la pantalla.

Eliminar parada

Dentro del resumen de paradas se debe seleccionar el embarque, desplegando más información y una sección de Acciones. Seleccionamos el botón Eliminar y sale un mensaje de advertencia. Al continuar hace una llamada al backend para eliminar la parada.

  • Eliminar parada
    • URL:
      • /api/UltimaMilla/EliminarParadaOperador

Si la operación es exitosa se descarga el xml automáticamente. Si surge algún error se muestra mensaje de error al usuario y se permanece en la pantalla.