Commit inicial - Sistema de Gestion Hotelera Hacienda San Angel
- Backend Node.js/Express con PostgreSQL - Frontend React 19 con Vite - Docker Compose para orquestacion - Documentacion completa en README.md - Scripts SQL para base de datos - Configuracion de ejemplo (.env.example)
This commit is contained in:
650
backend/hotel_hacienda/src/controllers/mail.controller.js
Normal file
650
backend/hotel_hacienda/src/controllers/mail.controller.js
Normal file
@@ -0,0 +1,650 @@
|
||||
const pool = require('../db/connection');
|
||||
const transporter = require('../services/mailService');
|
||||
|
||||
const contractsNearToEnd = async (req, res) => {
|
||||
try{
|
||||
const countResult = await pool.query('SELECT contratcsneartoend() as total');
|
||||
const contractsResult = await pool.query('SELECT * FROM contractsneartoenddata()');
|
||||
const userEmails = await pool.query('SELECT * FROM getusersmailneartoend()');
|
||||
const totalExpiring = countResult.rows[0].total;
|
||||
const contracts = contractsResult.rows;
|
||||
const users = userEmails.rows;
|
||||
|
||||
if(totalExpiring >= 1 && users.length > 0){
|
||||
|
||||
const recipients = users.map(u => u.email);
|
||||
|
||||
const mailOptions = {
|
||||
from: 'soporte@horuxfin.com',
|
||||
to: recipients,
|
||||
subject: 'Contratos próximos a expirar',
|
||||
html: `
|
||||
<div style="max-width:600px;margin:0 auto;font-family:Arial,Helvetica,sans-serif;
|
||||
background-color:#ffffff;border:1px solid #e5e5e5;border-radius:8px;
|
||||
overflow:hidden;">
|
||||
<!-- Header -->
|
||||
<div style="background-color:#7a0c1e;padding:18px 22px;">
|
||||
<h2 style="margin:0;color:#ffffff;font-size:20px;font-weight:600;">
|
||||
Notificación de contratos
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div style="padding:22px;color:#333333;font-size:14px;line-height:1.6;">
|
||||
<p style="margin-top:0;">
|
||||
Actualmente hay <b>${totalExpiring}</b> contrato(s) por expirar.
|
||||
</p>
|
||||
<p style="margin-top:0;">
|
||||
Los siguientes contratos son los que expiran:
|
||||
</p>
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;border:1px solid #ddd;">
|
||||
<thead>
|
||||
<tr style="background-color:#f4f4f4;">
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">ID contract</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">RFC</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Nombre</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Fecha inicio contrato</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Fecha fin contrato</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Salario diario</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${contracts.map(c => {
|
||||
const inicio = formatDateDMY(c.start_contract);
|
||||
const fin = formatDateDMY(c.end_contract);
|
||||
return `
|
||||
<tr>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.id_contract}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.rfc_emp}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.name_employee}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${inicio}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${fin}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.dailypay}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- CTA -->
|
||||
<div style="text-align:center;margin-top:32px;">
|
||||
<a href="https://hacienda.consultoria-as.com/"
|
||||
style="background-color:#7a0c1e;color:#ffffff;
|
||||
text-decoration:none;font-weight:bold;
|
||||
padding:14px 28px;border-radius:6px;
|
||||
display:inline-block;">
|
||||
Revisar en la web
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p style="margin-top:16px;font-size:12px;color:#777777;text-align:center;">
|
||||
Por favor considere si seran recontratados o finalizados.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div style="background-color:#f6f6f6;padding:12px 20px;
|
||||
font-size:12px;color:#888888;text-align:center;">
|
||||
Sistema de gestión financiera · Consultoría AS
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`
|
||||
};
|
||||
await transporter.sendMail(mailOptions);
|
||||
res.json({
|
||||
message: 'Correo enviado con contratos por expirar.',
|
||||
data:totalExpiring
|
||||
});
|
||||
}else{
|
||||
res.json({
|
||||
message: 'No hay contratos por expirar',
|
||||
data:totalExpiring
|
||||
});
|
||||
}
|
||||
}catch(error){
|
||||
console.error(error);
|
||||
res.status(500).json({ status: 500, message: 'No se pudo mandar el correo con los contratos por expirar' });
|
||||
}
|
||||
};
|
||||
const paymentDeadLine = async (req, res) => {
|
||||
try{
|
||||
const deadLineCount = await pool.query('SELECT countdelaypayments() as total');
|
||||
const deadLineResult = await pool.query('SELECT * FROM getdelaypayments()');
|
||||
const usersDeadLine = await pool.query('SELECT * FROM getusersmailpaymentdelay()');
|
||||
const totalDeadline = deadLineCount.rows[0].total;
|
||||
const deadLines = deadLineResult.rows;
|
||||
const users = usersDeadLine.rows;
|
||||
|
||||
if(totalDeadline >= 1 && users.length >0){
|
||||
const recipients = users.map(u => u.email);
|
||||
const mailOptions = {
|
||||
from: 'soporte@horuxfin.com',
|
||||
to: recipients,
|
||||
subject: 'WARNING: OVERDUE PAYMENTS',
|
||||
html: `
|
||||
<div style="max-width:600px;margin:0 auto;font-family:Arial,Helvetica,sans-serif;
|
||||
background-color:#ffffff;border:1px solid #e5e5e5;border-radius:8px;
|
||||
overflow:hidden;">
|
||||
<!-- Header -->
|
||||
<div style="background-color:#7a0c1e;padding:18px 22px;">
|
||||
<h2 style="margin:0;color:#ffffff;font-size:20px;font-weight:600;">
|
||||
Notification regarding overdue payments
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div style="padding:22px;color:#333333;font-size:14px;line-height:1.6;">
|
||||
<p style="margin-top:0;">
|
||||
Currently there are <b>${totalDeadline}</b> overdue payment(s).
|
||||
</p>
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;border:1px solid #ddd;">
|
||||
<thead>
|
||||
<tr style="background-color:#f4f4f4;">
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">ID</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Description</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Deadline</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${deadLines.map(c => {
|
||||
const fecha = new Date(c.payment_deadline);
|
||||
const fechaFormateada = fecha.toLocaleDateString('en-US', {
|
||||
day: '2-digit',
|
||||
month: 'long'
|
||||
});
|
||||
return `
|
||||
<tr>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.id_expense}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.description}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${fechaFormateada}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.total || 'N/A'}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- CTA -->
|
||||
<div style="text-align:center;margin-top:32px;">
|
||||
<a href="https://hacienda.consultoria-as.com/"
|
||||
style="background-color:#7a0c1e;color:#ffffff;
|
||||
text-decoration:none;font-weight:bold;
|
||||
padding:14px 28px;border-radius:6px;
|
||||
display:inline-block;">
|
||||
Please review and pay as soon as possible.
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p style="margin-top:16px;font-size:12px;color:#777777;text-align:center;">
|
||||
We recommend that review your overdue expenses as soon as possible.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div style="background-color:#f6f6f6;padding:12px 20px;
|
||||
font-size:12px;color:#888888;text-align:center;">
|
||||
Financial management system · Consultoría AS
|
||||
</div>
|
||||
|
||||
</div>`
|
||||
};
|
||||
await transporter.sendMail(mailOptions);
|
||||
res.json({
|
||||
message: 'Correo enviado con pagos atrasados.',
|
||||
data:totalDeadline
|
||||
});
|
||||
}else{
|
||||
res.json({
|
||||
message: 'No hay pagos con atraso',
|
||||
data:totalDeadline
|
||||
});
|
||||
}
|
||||
}catch(error){
|
||||
console.error(error);
|
||||
res.status(500).json({ status: 500, message: 'No se pudo mandar el correo con con los pagos atrasados' });
|
||||
}
|
||||
};
|
||||
|
||||
const expensesNearToDeadline = async (req, res) => {
|
||||
try{
|
||||
const deadLineCount = await pool.query('SELECT COUNT(*) FROM expensesneartodeadline()');
|
||||
const deadLineResult = await pool.query('SELECT * FROM expensesneartodeadline()');
|
||||
const usersDeadLine = await pool.query('SELECT * FROM getusersmailpaymentdelay()');
|
||||
|
||||
const totalDeadline = deadLineCount.rows[0].count;
|
||||
const deadLines = deadLineResult.rows;
|
||||
const users = usersDeadLine.rows;
|
||||
console.log(totalDeadline);
|
||||
if(totalDeadline >= 1 && users.length >0){
|
||||
const recipients = users.map(u => u.email);
|
||||
const mailOptions = {
|
||||
from: 'soporte@horuxfin.com',
|
||||
to: recipients,
|
||||
subject: 'WARNING: PAYMENTS DUE SOON FOR LATE PAYMENTS',
|
||||
html: `
|
||||
<div style="max-width:600px;margin:0 auto;font-family:Arial,Helvetica,sans-serif;
|
||||
background-color:#ffffff;border:1px solid #e5e5e5;border-radius:8px;
|
||||
overflow:hidden;">
|
||||
<!-- Header -->
|
||||
<div style="background-color:#7a0c1e;padding:18px 22px;">
|
||||
<h2 style="margin:0;color:#ffffff;font-size:20px;font-weight:600;">
|
||||
Notification of payments approaching a deadline
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div style="padding:22px;color:#333333;font-size:14px;line-height:1.6;">
|
||||
<p style="margin-top:0;">
|
||||
Currently there are <b>${totalDeadline}</b> payment(s) close to deadline.
|
||||
</p>
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;border:1px solid #ddd;">
|
||||
<thead>
|
||||
<tr style="background-color:#f4f4f4;">
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">ID</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Description</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Deadline</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${deadLines.map(c => {
|
||||
const fecha = new Date(c.payment_deadline);
|
||||
const fechaFormateada = fecha.toLocaleDateString('en-US', {
|
||||
day: '2-digit',
|
||||
month: 'long'
|
||||
});
|
||||
return `
|
||||
<tr>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.id_expense}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.description}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${fechaFormateada}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.total || 'N/A'}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- CTA -->
|
||||
<div style="text-align:center;margin-top:32px;">
|
||||
<a href="https://hacienda.consultoria-as.com/"
|
||||
style="background-color:#7a0c1e;color:#ffffff;
|
||||
text-decoration:none;font-weight:bold;
|
||||
padding:14px 28px;border-radius:6px;
|
||||
display:inline-block;">
|
||||
Review and pay spendings
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p style="margin-top:16px;font-size:12px;color:#777777;text-align:center;">
|
||||
We recommend reviewing this application before the deadline.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div style="background-color:#f6f6f6;padding:12px 20px;
|
||||
font-size:12px;color:#888888;text-align:center;">
|
||||
Financial management system · Consultoría AS
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`
|
||||
};
|
||||
await transporter.sendMail(mailOptions);
|
||||
res.json({
|
||||
message: 'Correo enviado con pagos por expirar.',
|
||||
data:totalDeadline
|
||||
});
|
||||
}else{
|
||||
res.json({
|
||||
message: 'No hay pagos con atraso',
|
||||
data:totalDeadline
|
||||
});
|
||||
}
|
||||
}catch(error){
|
||||
console.error(error);
|
||||
res.status(500).json({ status: 500, message: 'No se pudo mandar el correo con los pagos prontos a fecha limite' });
|
||||
}
|
||||
};
|
||||
|
||||
const expensesspecial = async (req, res) => {
|
||||
try{
|
||||
const expensesCount = await pool.query('SELECT COUNT(*) AS total FROM getexpensesspecial()');
|
||||
const expensesResult = await pool.query('SELECT * FROM getexpensesspecial()');
|
||||
const totalExpenses = expensesCount.rows[0].total;
|
||||
const expenses = expensesResult.rows;
|
||||
|
||||
if(totalExpenses >= 1){
|
||||
const mailOptions = {
|
||||
from: 'soporte@horuxfin.com',
|
||||
to: 'soporte@horuxfin.com',
|
||||
subject: 'Recordatorio: Hay gastos por aprobar',
|
||||
html: `
|
||||
<h2>Notificación de gastos por aprobar</h2>
|
||||
<p>Actualmente hay <b>${totalExpenses}</b> gasto(s) por aprobar</p>
|
||||
<ul>Los siguientes gastos faltan por aprobar</ul>
|
||||
<ul>
|
||||
${expenses.map(c => {
|
||||
const fecha = new Date(c.payment_deadline);
|
||||
const fechaFormateada = fecha.toLocaleDateString('es-MX');
|
||||
const product = Array.isArray(c.products) && c.products.length > 0
|
||||
? c.products.map(p => `${p.name_product} ($${p.total})`).join(', ')
|
||||
: 'N/A';
|
||||
return `<li>
|
||||
ID:${c.id_expense} - Descripción: ${c.description}
|
||||
- Productos: ${product} -
|
||||
fecha limite de pago: ${fechaFormateada} -
|
||||
total del gasto: ${c.total || 'N/A'}
|
||||
</li>`;
|
||||
}).join('')}
|
||||
</ul>`
|
||||
};
|
||||
await transporter.sendMail(mailOptions);
|
||||
res.json({
|
||||
message: 'Correo enviado con gastos por aprobar',
|
||||
data:totalExpenses
|
||||
});
|
||||
}else{
|
||||
res.json({
|
||||
message: 'No hay pagos por aprobar',
|
||||
data:totalExpenses
|
||||
});
|
||||
}
|
||||
}catch(error){
|
||||
console.error(error);
|
||||
res.status(500).json({ status: 500, message: 'No se pudo mandar el correo con los contratos por expirar' });
|
||||
}
|
||||
};
|
||||
|
||||
const emailbirthday = async (req, res) => {
|
||||
try{
|
||||
const endpointName = 'emailbirthday';
|
||||
const current = new Date();
|
||||
const mes = current.getMonth() + 1;
|
||||
const anio = current.getFullYear();
|
||||
const validateEndpoint = await pool.query('SELECT validateendpoint($1,$2::int,$3::int) AS total',[endpointName,mes,anio]);
|
||||
|
||||
if (validateEndpoint.rows[0].total > 0) {
|
||||
return res.status(400).json({
|
||||
message: `Este endpoint ya fue ejecutado en ${current.toLocaleString('es-MX', { month: 'long' })}.`
|
||||
});
|
||||
}else {
|
||||
await pool.query(`INSERT INTO endpoint_logs (endpoint_name) VALUES ($1)`,[endpointName]);
|
||||
const birthdayCount = await pool.query('SELECT COUNT(*) AS total FROM getbirthdaysemployees()');
|
||||
const birthdayResult = await pool.query('SELECT * FROM getbirthdaysemployees()');
|
||||
const userEmails = await pool.query('SELECT * FROM getusersmailbirthday()');
|
||||
|
||||
const totalBirthday = birthdayCount.rows[0].total;
|
||||
const birthdays = birthdayResult.rows;
|
||||
const users = userEmails.rows;
|
||||
const currentMonth = new Date().toLocaleString('en-US', { month: 'long' });
|
||||
|
||||
if(totalBirthday >= 1 && users.length >0){
|
||||
|
||||
const recipients = users.map(u => u.email);
|
||||
|
||||
const mailOptions = {
|
||||
from: 'soporte@horuxfin.com',
|
||||
to: recipients,
|
||||
subject: `Friendly reminder: Birthdays of the month of ${currentMonth}`,
|
||||
html: `
|
||||
<div style="max-width:600px;margin:0 auto;font-family:Arial,Helvetica,sans-serif;
|
||||
background-color:#ffffff;border:1px solid #e5e5e5;border-radius:8px;
|
||||
overflow:hidden;">
|
||||
<!-- Header -->
|
||||
<div style="background-color:#7a0c1e;padding:18px 22px;">
|
||||
<h2 style="margin:0;color:#ffffff;font-size:20px;font-weight:600;">
|
||||
In this month there are ${totalBirthday} birthday person(s)
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div style="padding:22px;color:#333333;font-size:14px;line-height:1.6;">
|
||||
<p style="margin-top:0;">
|
||||
The following employees are celebrating their birthdays this month:
|
||||
</p>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;border:1px solid #ddd;">
|
||||
<thead>
|
||||
<tr style="background-color:#f4f4f4;">
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">RFC</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Name</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Birthday</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${birthdays.map(c => {
|
||||
const fecha = new Date(c.birthday);
|
||||
const fechaFormateada = fecha.toLocaleDateString('en-US', {
|
||||
day: '2-digit',
|
||||
month: 'long'
|
||||
});
|
||||
return `
|
||||
<tr>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.rfc}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.fullname}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${fechaFormateada}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<p style="margin-top:16px;font-size:12px;color:#777777;text-align:center;">
|
||||
Remember to congratulate them on their special day!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div style="background-color:#f6f6f6;padding:12px 20px;
|
||||
font-size:12px;color:#888888;text-align:center;">
|
||||
Financial management system · Consultoría AS
|
||||
</div>
|
||||
|
||||
</div>`};
|
||||
await transporter.sendMail(mailOptions);
|
||||
res.json({
|
||||
message: 'Correo enviado con los cumpleaños',
|
||||
data:totalBirthday
|
||||
});
|
||||
}else{
|
||||
res.json({
|
||||
message: 'No hay cumpleañeros este mes',
|
||||
data:totalBirthday
|
||||
});
|
||||
}}
|
||||
}catch(error){
|
||||
console.error(error);
|
||||
res.status(500).json({ status: 500, message: 'No se pudo enviar correo con los cumpleañeros' });
|
||||
}
|
||||
};
|
||||
const expiredcontracts = async (req, res) => {
|
||||
try{
|
||||
const expiredCountTotal = await pool.query('SELECT expiredcontracts() as total');
|
||||
const expiredResult = await pool.query('SELECT * FROM getexpirecontracts()');
|
||||
const expiredCountDay = await pool.query('SELECT COUNT(*) AS total FROM getexpirecontracts()');
|
||||
const totalExpired = expiredCountTotal.rows[0].total;
|
||||
const totalDayExpired = expiredCountDay.rows[0].total;
|
||||
const expiredContracts = expiredResult.rows;
|
||||
|
||||
if(totalExpired >= 1){
|
||||
const mailOptions = {
|
||||
from: 'soporte@horuxfin.com',
|
||||
to: 'soporte@horuxfin.com',
|
||||
subject: 'Contratos expirados el día de hoy',
|
||||
html: `
|
||||
<h2>Notificación contratos expirados el día de hoy</h2>
|
||||
<p>Cantidad de contratos <b>${totalDayExpired}</b> que expiraron hoy.</p>
|
||||
<ul>Los contratos expirados son</ul>
|
||||
<ul>
|
||||
${expiredContracts.map(c => {
|
||||
const fecha = new Date(c.end_contract);
|
||||
const fechaFormateada = fecha.toLocaleDateString('es-MX');
|
||||
return `<li>
|
||||
ID:${c.id_contract} - RFC: ${c.rfc_employee}
|
||||
- Nombre: ${c.name_employee} -
|
||||
fecha de expiración: ${fechaFormateada}
|
||||
</li>`;
|
||||
}).join('')}
|
||||
</ul>
|
||||
<p>Por favor considere si seran recontratados o no para que se inicie el proceso.</p>`
|
||||
};
|
||||
await transporter.sendMail(mailOptions);
|
||||
res.json({
|
||||
message: 'Correo enviado con contratos expirados hoy',
|
||||
data:totalDayExpired
|
||||
});
|
||||
}else{
|
||||
res.json({
|
||||
message: 'Hoy no expiro ningun contrato',
|
||||
data:totalDayExpired
|
||||
});
|
||||
}
|
||||
}catch(error){
|
||||
console.error(error);
|
||||
res.status(500).json({ status: 500, message: 'No se pudo mandar el correo con los contratos por expirar' });
|
||||
}
|
||||
};
|
||||
const expiredContractsMonth = async (req, res) => {
|
||||
try{
|
||||
// Verifica si hoy es el último día del mes
|
||||
if (isLastDayOfMonth()) {
|
||||
return res.status(200).json({
|
||||
message: 'El proceso solo se ejecuta el último día del mes',
|
||||
executed: false
|
||||
});
|
||||
}
|
||||
const endpointName = 'expiredContractsMonth';
|
||||
const current = new Date();
|
||||
const mes = current.getMonth() + 1;
|
||||
const anio = current.getFullYear();
|
||||
const validateEndpoint = await pool.query('SELECT validateendpoint($1,$2::int,$3::int) AS total',[endpointName,mes,anio]);
|
||||
if (validateEndpoint.rows[0].total > 0) {
|
||||
return res.status(400).json({
|
||||
message: `Este endpoint ya fue ejecutado en ${current.toLocaleString('es-MX', { month: 'long' })}.`
|
||||
});
|
||||
}else
|
||||
{
|
||||
await pool.query(`INSERT INTO endpoint_logs (endpoint_name) VALUES ($1)`,[endpointName]);
|
||||
const expiredCountTotal = await pool.query('SELECT COUNT(*) AS total FROM getexpirecontractsmonth()');
|
||||
const expiredResult = await pool.query('SELECT * FROM getexpirecontractsmonth()');
|
||||
const userEmails = await pool.query('SELECT * FROM getusersmailneartoend()');
|
||||
|
||||
const totalExpired = expiredCountTotal.rows[0].total;
|
||||
const expiredContracts = expiredResult.rows;
|
||||
const currentMonth = new Date().toLocaleString('es-MX', { month: 'long' });
|
||||
const users = userEmails.rows;
|
||||
|
||||
if(totalExpired >= 1 && users.length > 0){
|
||||
const recipients = users.map(u => u.email);
|
||||
const mailOptions = {
|
||||
from: 'soporte@horuxfin.com',
|
||||
to: recipients,
|
||||
subject: `Contratos expirados en el mes ${currentMonth} `,
|
||||
html: `
|
||||
<div style="max-width:600px;margin:0 auto;font-family:Arial,Helvetica,sans-serif;
|
||||
background-color:#ffffff;border:1px solid #e5e5e5;border-radius:8px;
|
||||
overflow:hidden;">
|
||||
<!-- Header -->
|
||||
<div style="background-color:#7a0c1e;padding:18px 22px;">
|
||||
<h2 style="margin:0;color:#ffffff;font-size:20px;font-weight:600;">
|
||||
Notificación contratos expirados del mes de ${currentMonth}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div style="padding:22px;color:#333333;font-size:14px;line-height:1.6;">
|
||||
<p style="margin-top:0;">
|
||||
En este mes expiraron <b>${totalExpired}.
|
||||
</p>
|
||||
<p style="margin-top:0;">
|
||||
Los contratos expirados son en el mes fueron:
|
||||
</p>
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;border:1px solid #ddd;">
|
||||
<thead>
|
||||
<tr style="background-color:#f4f4f4;">
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">ID contract</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">RFC</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Nombre</th>
|
||||
<th align="left" style="padding:8px;border:1px solid #ddd;">Fecha que expiro:</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${expiredContracts.map(c => {
|
||||
const fin = formatDateDMY(c.end_contract);
|
||||
return `
|
||||
<tr>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.id_contract}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.rfc_employee}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${c.name_employee}</td>
|
||||
<td style="padding:8px;border:1px solid #ddd;">${fin}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- CTA -->
|
||||
<div style="text-align:center;margin-top:32px;">
|
||||
<a href="https://hacienda.consultoria-as.com/"
|
||||
style="background-color:#7a0c1e;color:#ffffff;
|
||||
text-decoration:none;font-weight:bold;
|
||||
padding:14px 28px;border-radius:6px;
|
||||
display:inline-block;">
|
||||
Revisar en la web
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p style="margin-top:16px;font-size:12px;color:#777777;text-align:center;">
|
||||
Le recomendamos generar el contrato en caso de recontratación.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div style="background-color:#f6f6f6;padding:12px 20px;
|
||||
font-size:12px;color:#888888;text-align:center;">
|
||||
Sistema de gestión financiera · Consultoría AS
|
||||
</div>
|
||||
|
||||
</div>`
|
||||
};
|
||||
await transporter.sendMail(mailOptions);
|
||||
res.json({
|
||||
message: 'Correo enviado con contratos expirados en el mes',
|
||||
data:totalExpired
|
||||
});
|
||||
}else{
|
||||
res.json({
|
||||
message: 'Este mes no expiro ningun contrato',
|
||||
data:totalExpired
|
||||
});
|
||||
}}
|
||||
}catch(error){
|
||||
console.error(error);
|
||||
res.status(500).json({ status: 500, message: 'No se pudo mandar el correo con los contratos por expirar' });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const isLastDayOfMonth = () => {
|
||||
const today = new Date();
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(today.getDate() + 1);
|
||||
return tomorrow.getDate() === 1;
|
||||
};
|
||||
const formatDateDMY = (value) => {
|
||||
if (!value) return '';
|
||||
const date = value instanceof Date ? value : new Date(value);
|
||||
if (isNaN(date.getTime())) return '';
|
||||
return date.toLocaleDateString('es-MX', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {contractsNearToEnd,paymentDeadLine,expensesspecial,
|
||||
emailbirthday,expiredcontracts,expiredContractsMonth,expensesNearToDeadline};
|
||||
Reference in New Issue
Block a user