/**
* Nota
* @typedef {Object} Nota
* @property {number} id_nota identificador de la nota
* @property {number} id_cliente identificador de cliente
* @property {number} userid identificador del usuario
* @property {"EN_PROCESO" | "ACEPTADO" | "ENTREGADA" | "POSPUESTO" | "CANCELADA"} status estado de la orden en ese momento
* @property {string} creacion timestamp de creacion de la nota
* @property {string} descripcion_nota descripcion de la nota de entrega
* @property {string} fecha_entrega fecha de entrega del pedido se completa si el usuario pasa el pedido a ENTREGADA
* @property {Array<NotaProducto>} productos lista de productos seleccionados
* @property {number} [total_order] total de la orden
*/
const { Notification, dialog, BrowserWindow } = require('electron');
const { Database } = require('../database/database');
const CRUD = require('../database/CRUD');
const TIME = require('../util-functions/time');
const { NotasProductosController } = require('./notas-productos-controller');
const { PdfController } = require('./pdf-controller');
const excelModule = require('../util-functions/excel');
const FILE = require('../util-functions/file');
const modelNota = require('../models/note');
const { ProductosController } = require('./productos-controllers');
/** Clase que gestiona las notas de entregas */
class NotasController {
/** @type {?Database} */
databaseInstance = null;
/** Propiedad get database retorna una nueva instancia de la clase Database */
static get database() {
return this.databaseInstance || ( this.databaseInstance = new Database() );
}
/**
* Exporta los productos en un archivo de excel
*/
static exportarNotas() {
return new Promise(( resolve, reject ) => {
// colocar codigo aqui
const notificacion = new Notification();
const extensiones = ['.json', '.xls', '.xlsx']
/** @type {Electron.SaveDialogOptions} */
const opciones = {
title: 'Exportar Nota',
filters: [
{ name: 'Nota excel', extensions: ['xls', 'xlsx'] },
{ name: 'Nota json', extensions: ['json'] },
],
};
let path = '';
let message = 'Cancelada';
// 1.- crear la ventana de exportacion
dialog.showSaveDialog( BrowserWindow.getFocusedWindow(), opciones )
.then( respuesta => {
// 2.- validar la cancelacion y el formato
if ( respuesta.canceled ) {
throw message;
}
let validacion = extensiones.some(( extension ) => {
return respuesta.filePath.includes( extension );
});
if ( validacion === false ) {
message = 'La extensión del archivo no es valida';
notificacion.title = 'Atención';
notificacion.body = message;
notificacion.show();
throw message;
}
// si no lo colocamos no pasa la validacion
path = respuesta.filePath;
// 3.- realizar la consulta de las notas + productos asociados con la misma ( generar el SQL )
return NotasController.obtenerNotasArray();
})
.then( consultaDB => {
if ( path.includes( extensiones[0] ) ) {
return excelModule.exportJSON( path, consultaDB );
}
return excelModule.generarLibroNotasExcel( path, consultaDB );
})
.then( respuesta => {
notificacion.body = respuesta;
notificacion.title = 'Exito';
notificacion.show();
resolve( respuesta );
})
.catch( error => {
console.log( error );
reject( error );
});
});
}
/**
* Importa los productos en un archivo excel
*/
static importarNotas() {
let message = 'Cancelada';
/** metodo que muestra un mensaje de adventencia */
const mostrarMensaje = () => {
message = 'El orden de los campos importados son incorrectos';
dialog.showErrorBox(
'Error',
(
'Los campos en el archivo son incorrectos.\n\n' +
'Consulta el manual para obtener más información\n' +
'sobre como importar archivos.'
)
);
throw message;
};
return new Promise(( resolve, reject ) => {
const notificacion = new Notification();
const extensiones = ['.json', '.xls', '.xlsx'];
/** @type {Electron.OpenDialogOptions} */
const opciones = {
title: 'Importar Nota',
filters: [
{ name: 'Nota excel', extensions: ['xls', 'xlsx'] },
{ name: 'Nota json', extensions: ['json'] },
],
};
let path = '';
// 1.- crear la ventana de importacion
dialog.showOpenDialog( BrowserWindow.getFocusedWindow(), opciones )
.then( respuestaVentana => {
// 2.- validar la cancelacion y el formato
if ( respuestaVentana.canceled ) {
throw message;
}
path = respuestaVentana.filePaths[0];
const validacion = extensiones.some( extension => path.includes( extension ) );
if ( !validacion ) {
message = 'La extensión del archivo no es valida';
notificacion.title = 'Atención';
notificacion.body = message;
notificacion.show();
throw message;
}
// 3.- importar el arhivo
if ( path.includes( extensiones[0] ) ) {
return FILE.readFilePromiseJSON( path, true );
}
return excelModule.readFileExcelNotes( path );
})
.then( notas => {
// console.log( notas );
// 4.- validar los campos del archivo
let validacion = notas.every( nota => modelNota.validate( nota ) );
if ( !validacion ) {
mostrarMensaje();
}
// 5.- verifica si las notas tengan status entregada
// si es asi asigna la fecha
validacion = notas.some( nota => nota.status === 'ENTREGADA' );
if ( validacion ) {
notas = notas.map( nota => {
if ( nota.status === 'ENTREGADA' ) {
return { ...nota, fecha_entrega: TIME.dateToString() };
}
return nota;
});
}
const arrayPromesas = notas.map( nota => NotasController.crearNota( nota, false ) );
// ejecutamos cada promesa al momento de insertar
return Promise.all( arrayPromesas );
})
.then(( _ ) => {
notificacion.title = 'Exito';
notificacion.body = 'Notas creadas con éxito';
notificacion.show();
resolve();
})
.catch( error => {
console.log( error );
reject( error );
});
});
}
/** Permite obtener un array de las notas con los productos */
static obtenerNotasArray() {
// esta es una promesa almecenada una posicion en memoria
const obtenerNotasProductos = ( nota ) => new Promise (( resolve, reject ) => {
// aqui hacemos la consulta a notas productos
this.database.consult( CRUD.obtenerNotaProducto, { id_nota: nota.id_nota }, ( error, productos ) => {
if ( error ) {
reject( error );
throw error;
}
resolve({ ...nota, productos: productos });
});
});
return new Promise(( resolve, reject ) => {
this.database.consult( CRUD.exportarNotas, null, async ( error, notas ) => {
if ( error ) {
reject( error );
throw error;
}
const arrayPromises = notas.map( nota => obtenerNotasProductos( nota ) );
try {
const notasProductos = await Promise.all( arrayPromises );
// console.log(notasProductos);
resolve( notasProductos );
} catch ( error ) {
console.log( error );
reject( error );
}
});
});
}
/**
* crea un registro de una nota de entrega
* @param {Nota} nota instancia de la nota
* @param {boolean} [notificacion] muesta la notificacion para cada nota insertada
*/
static crearNota( nota, showNotificacion = true ) {
return new Promise( async ( resolve, reject ) => {
// nota tiene todas las propiedades del nota de entrega + productos,
// no puedes enviar todo eso por sino mostrara un error. si haces un log de nota te trae
// toda la informacion
// console.log( nota )
// combinamos 2 consultas sql para crear la nota y obtener el ultimo registro
const sql = ( CRUD.crearNota + " " + CRUD.ultimoRegistro );
// Debes extraer sus propiedades y crear el objeto para cada consulta
let nuevaNota = {
userid: nota['userid'],
status: nota['status'],
descripcion_nota: nota['descripcion_nota'],
id_cliente: nota['id_cliente'],
fecha_entrega: nota['fecha_entrega'] ?? null
};
let arrayProductos = nota['productos'] ?? [];
// verificamos si llegan productos y validamos la cantidad seleccionada
if ( arrayProductos.length > 0 ) {
const arrayProductosPromise = arrayProductos.map( async producto => {
if ( !producto.cantidad ) {
return {
...producto,
cantidad: ( await ProductosController.obtenerProducto(
producto.productoid
)
).cantidad
}
}
return producto;
})
arrayProductos = await Promise.all( arrayProductosPromise );
// console.log( arrayProductos );
const validarCantidad = arrayProductos.every(( producto ) => {
return producto['cantidad_seleccionada'] <= producto['cantidad'];
});
if ( validarCantidad === false ) {
dialog.showMessageBox( null, {
type: 'warning',
title: 'advertencia',
message: 'la cantidad seleccionada no debe ser mayor a la cantidad disponible'
});
// throw new Error("La cantidad seleccionada no debe ser mayor a la cantidad disponible");
reject("La cantidad seleccionada no debe ser mayor a la cantidad disponible");
return;
}
}
this.database.insert( sql, nuevaNota, ( error, results ) => {
const notificacion = new Notification({ title: '', body: '' });
if ( error ) {
notificacion['title'] = 'Error!!';
notificacion['body'] = 'Error al crear Nota';
notificacion.show();
console.log( error );
reject( error );
return;
}
// verifica si existen productos en nota de entrega al momento de importar
if ( arrayProductos.length === 0 ) {
resolve('Nota insertada con exito');
return;
}
// extraemos para obtener el ultimo registro
const ultimoRegistro = results[1][0];
// console.log( ultimoRegistro );
arrayProductos = arrayProductos.map(( producto ) => {
return {
id_nota: ultimoRegistro['id_nota'],
id_producto: producto['productoid'],
cantidad_seleccionada: producto['cantidad_seleccionada'],
cantidad: producto['cantidad'],
};
});
arrayProductos.forEach(( notaProducto, index ) => {
// callback de notificacion
const callback = (index === (arrayProductos.length - 1)) ?
() => {
if ( showNotificacion ) {
notificacion['title'] = 'Éxito';
notificacion['body'] = 'Nota creada con éxito';
notificacion.show();
}
resolve('Nota + productos creada con exito');
}
: null
NotasProductosController.insertarNotasProductos.call(
NotasProductosController.database,
notaProducto,
callback
);
});
});
});
}
/**
* Funcion que obtiene el ultimo id de nota registrado
*
* @return {Promise<Nota>} retorna una promesa que devuelve la utlima nota registada
* @example
* let utlimoRegistro = await NotasController.obtenerUlitmaNota();
*/
static obtenerUltimaNota() {
return new Promise( ( resolve, reject ) => {
this.database.consult( CRUD.ultimoRegistro, null, ( error, resultado ) => {
const notificacion = new Notification({
title: 'Error en obtener los registros',
body: 'No se pudo obtener el registro'
});
if ( error ) {
notificacion.show();
console.log( error );
reject( error );
return;
}
const ultimoRegistro = resultado[0];
// el resolve es el valor de vuelta asignado
resolve( ultimoRegistro );
});
});
}
/**
* Obtiene la nota desde la BD.
*
* @param {number} idNota identificador de la nota
* @return {Promise<Nota>} retorna una promesa con la nota solicitada
*/
static obtenerNota( idNota ) {
return new Promise(( resolve, reject ) => {
this.database.consult( CRUD.obtenerNota, { id_nota: idNota }, ( error, resultadoNota ) => {
if ( error ) {
console.log( error );
reject( error );
return;
}
let nota = resultadoNota[0];
// newDate parsea la fecha para pasar a la funcion dateSpanish la cual la envia en formato español
// si existe la fecha de entrega
if ( nota['fecha_entrega'] ) {
nota['fecha_entrega'] = TIME.dateToString( new Date( nota['fecha_entrega'] ) );
}
this.database.consult( CRUD.obtenerNotaProducto, { id_nota: idNota }, ( error, resultadoNotaProducto ) => {
if ( error ) {
console.log( error );
reject( error );
return;
}
// console.log( ">>>>>>>", { nota, resultadoNotaProducto }, "<<<<<<<" );
resolve({
...nota,
productos: resultadoNotaProducto,
total_order: NotasController.calcularTotalNotas( resultadoNotaProducto )
});
});
});
});
}
/**
* Calcula el total de la nota.
*
* @param {Array<NotaProducto>} arrayNotaProducto array de nota producto
* @return {number} retorna el calculo total de la orden
*/
static calcularTotalNotas( arrayNotaProducto ) {
const reducer = ( accumulator, currentValue ) => {
return accumulator = accumulator + ( currentValue['cantidad_seleccionada'] * currentValue['precio'] );
}
return Number.parseFloat( arrayNotaProducto.reduce(reducer, 0).toFixed(2) );
}
/**
* Obtiene el total de las notas
* @returns {Promise<{ totalPaginas: number, totalRegistros: number }>}
* un objeto con las prpiedades antes mencionadas
*/
static obtenerTotalNotas() {
return new Promise( ( resolve, reject ) => {
this.database.getTotalRecords( CRUD.obtenerTotalNotas, ( error, resultado ) => {
const notificacion = new Notification({
title: 'Error en obtener los registros',
body: 'No se pudo obtener el total de registros'
});
if ( error ) {
notificacion.show();
console.log( error );
reject( error );
return;
}
const totalRegistros = resultado[0]['COUNT(*)'];
let totalPaginas = ( totalRegistros / 10 );
resolve({
totalPaginas: Math.ceil( totalPaginas ),
totalRegistros: totalRegistros
});
});
});
}
/**
* Lista las notas en forma paginada
*
* @param {Array<number>} pagination description
* @return {Promise<Array<Nota>>} devuelve una promesa que contiene un arreglo de notas
*/
static listarNotas( pagination ) {
return new Promise(( resolve, reject ) => {
pagination = { start: pagination[0], limit: pagination[1] };
this.database.consult( CRUD.listarNotas, pagination, ( error, results ) => {
if ( error ) {
console.log( error );
reject( error );
return;
}
resolve( results );
});
});
}
/**
* Buscar notas en la BD
*
* @param {Object} search objeto de busqueda
* @param {string} search.search busqueda de la nota
* @return {Promise<Array<Nota>>} devuelve una promesa que contiene un arreglo de notas
*/
static buscarNota( search ) {
return new Promise(( resolve, reject ) => {
// console.log( CRUD.buscarNotas );
this.database.find( CRUD.buscarNotas, search, ( error, results ) => {
const notificacion = new Notification({
title: '',
body: ''
});
if ( error ) {
// throw error;
notificacion['title'] = 'Error!!';
notificacion['body'] = 'Error al buscar Nota';
notificacion.show();
console.log( error );
reject( error );
return;
}
resolve( results );
});
});
}
/**
* muestra una alerta al usuario desde el proceso principal
* @param {string} type tipo de alerta
* @param {string} title titulo de la alerta
* @param {string} message mensaje al usuario
*
*/
static mostrarAlerta( type = 'info', title, message ) {
dialog.showMessageBox( null, { message, type, title });
}
/**
* actualiza la nota
* @param {Nota} nota instancia de la nota
*/
static actualizarNota( nota ) {
let notaActualizada = {
userid: nota['userid'],
status: nota['status'],
descripcion_nota: nota['descripcion_nota'],
id_cliente: nota['id_cliente'],
fecha_entrega: nota['fecha_entrega'],
id_nota: nota['id_nota']
};
let arrayProductos = nota['productos'] ?? [];
if ( arrayProductos.length > 0 ) {
const validarCantidad = arrayProductos.every(( producto ) => {
let total = producto['cantidad_seleccionada'] + producto['cantidad'];
return producto['cantidad_seleccionada'] <= total;
});
if ( validarCantidad === false ) {
dialog.showMessageBox( null, {
type: 'warning',
title: 'advertencia',
message: 'la cantidad seleccionada no debe ser mayor a la cantidad disponible'
});
// throw new Error("La cantidad seleccionada no debe ser mayor a la cantidad disponible");
return;
}
}
// actualiza la nota
this.database.update( CRUD.editarNotas, notaActualizada, ( error ) => {
if ( error ) {
console.log( error );
// throw error;
return;
}
let idNota = nota['id_nota'];
// consulta el array desde la BD y por cada elemento la compara
this.database.consult( CRUD.listarNotasProductos, { id_nota : idNota }, ( error, results ) => {
if ( error ) {
console.log( error );
// throw error;
return;
}
const notaProductoDB = results;
arrayProductos.forEach(( notaProducto, idx ) => {
// verifica si el elemento esta en la BD
let index = notaProductoDB.findIndex(( notaProductoDB ) => {
return notaProducto['id_NP'] === notaProductoDB['id_NP'];
});
// callback de notificacion se ejecuta en el ultimo ciclo
let callback = ( idx === ( arrayProductos.length - 1 ) ) ?
() => {
const notificacion = new Notification({
title: 'Exito !!',
body: 'Nota actualizada con exito'
});
notificacion.show();
} : null;
if ( index === -1 ) {
// crea la nota si no esta en el array
NotasProductosController.insertarNotasProductos.call(
NotasProductosController.database,
{
id_nota: idNota,
cantidad_seleccionada: notaProducto['cantidad_seleccionada'],
id_producto: notaProducto['productoid'],
cantidad: notaProducto['cantidad']
},
callback
);
} else {
// si existe actualiza el valor de cantidad_seleccionada y actualiza notas_productos + producto
let cantidadBD = notaProductoDB[index]['cantidad_seleccionada'];
let cantidadActualizada = notaProducto['cantidad_seleccionada'];
let sumaAlgebraica = ( cantidadBD - cantidadActualizada );
NotasProductosController.actualizarNotasProductos( notaProducto, sumaAlgebraica, callback );
}
});
});
});
}
/**
* Genera el PDF de la nota de entrega
* @param {number} idNota identificador de la nota
*/
static generarPDFNota( idNota ) {
const pdfController = new PdfController(); // creas un objeto PDF controller
const options = {
title : 'Guardar Archivo',
buttonLabel : 'Guardar' ,
filters : [{ name: 'pdf', extensions: ['pdf'] }],
defaultPath: FILE.getHomePath()
};
const notification = new Notification({
title: 'Éxito',
body: 'Documento generado con éxito'
});
dialog.showSaveDialog( null, options )
.then( async ( response ) => {
/* Documentación
* response == { canceled: boolean, filePath: string }
* extensions = 'extensiones permitidas'
*/
const extensions = ['.pdf'];
if ( response['canceled'] ) {
return;
}
// verifica la extension del archivo que sea pdf
if ( !response['filePath'].includes( extensions[0] ) ) {
notification['title'] = 'Error !!';
notification['body'] = 'Extension del archivo no valida';
notification.show();
// console.log( response['filePath'] );
return;
}
if( FILE.checkAsset( response['filePath'], false ) ){
FILE.deleteFileSync( response['filePath'] );
}
const nota = await NotasController.obtenerNota( idNota );
const data = await pdfController.createPdf( nota );
FILE.appendFile( response['filePath'], data, ( error ) => {
if ( error ) {
console.log( error );
notification['title'] = 'Error!!';
notification['body'] = 'Error al guardar archivo';
notification.show();
// throw error;
return;
}
notification.show();
});
})
.catch( ( error ) => {
notification['title'] = 'Error !!';
notification['body'] = 'Error al abrir ventana guardar';
notification.show();
console.log( error );
});
}
static obtenerNotasArray() {
// promesa de notas productos
const promesaNotaProductos = ( nota ) => new Promise(( resolve, reject ) => {
this.database.consult( CRUD.exportarNotasProducto, { id_nota: nota.id_nota }, ( error, productos ) => {
if ( error ) {
console.log( error );
reject( error );
throw error;
}
// asigna cada producto al arreglo padre
resolve({ ...nota, productos });
});
});
return new Promise(( resolve, reject ) => {
this.database.consult( CRUD.exportarNotas, null, async ( error, notas ) => {
if ( error ) {
console.log( error );
reject( error );
throw error;
}
const arrayPromises = notas.map( nota => promesaNotaProductos( nota ) );
try {
const notasProductos = await Promise.all( arrayPromises );
resolve( notasProductos );
} catch ( error ) {
console.log( error );
reject( error );
}
});
});
}
}
module.exports = { NotasController };