Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4859-export_db

This commit is contained in:
Alex Moreno 2022-11-30 10:28:21 +01:00
commit 67cf4ebbea
22 changed files with 413 additions and 117 deletions

View File

@ -51,7 +51,7 @@ module.exports = Self => {
const dstFile = path.join(dmsContainer.client.root, pathHash, dms.file); const dstFile = path.join(dmsContainer.client.root, pathHash, dms.file);
await fs.unlink(dstFile); await fs.unlink(dstFile);
} catch (err) { } catch (err) {
if (err.code != 'ENOENT') if (err.code != 'ENOENT' && dms.file)
throw err; throw err;
} }

View File

@ -2569,10 +2569,6 @@ UPDATE `vn`.`route`
UPDATE `vn`.`route` UPDATE `vn`.`route`
SET `invoiceInFk`=2 SET `invoiceInFk`=2
WHERE `id`=2; WHERE `id`=2;
INSERT INTO `bs`.`salesPerson` (`workerFk`, `year`, `month`)
VALUES
(18, YEAR(util.VN_CURDATE()), MONTH(util.VN_CURDATE())),
(19, YEAR(util.VN_CURDATE()), MONTH(util.VN_CURDATE()));
INSERT INTO `bs`.`sale` (`saleFk`, `amount`, `dated`, `typeFk`, `clientFk`) INSERT INTO `bs`.`sale` (`saleFk`, `amount`, `dated`, `typeFk`, `clientFk`)
VALUES VALUES

View File

@ -37,7 +37,7 @@ export default class Controller extends Section {
const validations = window.validations; const validations = window.validations;
value.forEach(log => { value.forEach(log => {
const locale = validations[log.changedModel].locale ? validations[log.changedModel].locale : {}; const locale = validations[log.changedModel] && validations[log.changedModel].locale ? validations[log.changedModel].locale : {};
log.oldProperties = this.getInstance(log.oldInstance, locale); log.oldProperties = this.getInstance(log.oldInstance, locale);
log.newProperties = this.getInstance(log.newInstance, locale); log.newProperties = this.getInstance(log.newInstance, locale);

View File

@ -91,7 +91,18 @@ module.exports = Self => {
case 'search': case 'search':
return /^\d+$/.test(value) return /^\d+$/.test(value)
? {'c.id': {inq: value}} ? {'c.id': {inq: value}}
: {'c.name': {like: `%${value}%`}}; : {or: [
{'c.name': {like: `%${value}%`}},
{'c.socialName': {like: `%${value}%`}},
]};
case 'phone':
return {or: [
{'c.phone': {like: `%${value}%`}},
{'c.mobile': {like: `%${value}%`}},
]};
case 'zoneFk':
param = 'a.postalCode';
return {[param]: {inq: postalCode}};
case 'name': case 'name':
case 'salesPersonFk': case 'salesPersonFk':
case 'fi': case 'fi':
@ -100,12 +111,8 @@ module.exports = Self => {
case 'postcode': case 'postcode':
case 'provinceFk': case 'provinceFk':
case 'email': case 'email':
case 'phone':
param = `c.${param}`; param = `c.${param}`;
return {[param]: value}; return {[param]: {like: `%${value}%`}};
case 'zoneFk':
param = 'a.postalCode';
return {[param]: {inq: postalCode}};
} }
}); });
@ -119,6 +126,7 @@ module.exports = Self => {
c.fi, c.fi,
c.socialName, c.socialName,
c.phone, c.phone,
c.mobile,
c.city, c.city,
c.postcode, c.postcode,
c.email, c.email,
@ -132,7 +140,7 @@ module.exports = Self => {
LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN account.user u ON u.id = c.salesPersonFk
LEFT JOIN province p ON p.id = c.provinceFk LEFT JOIN province p ON p.id = c.provinceFk
JOIN vn.address a ON a.clientFk = c.id JOIN vn.address a ON a.clientFk = c.id
` `
); );
stmt.merge(conn.makeWhere(filter.where)); stmt.merge(conn.makeWhere(filter.where));

View File

@ -33,36 +33,36 @@ module.exports = Self => {
const stmt = new ParameterizedSQL( const stmt = new ParameterizedSQL(
`SELECT `SELECT
t.id, t.id,
t.packages, t.packages,
t.warehouseFk, t.warehouseFk,
t.nickname, t.nickname,
t.clientFk, t.clientFk,
t.priority, t.priority,
t.addressFk, t.addressFk,
st.code AS ticketStateCode, st.code AS ticketStateCode,
st.name AS ticketStateName, st.name AS ticketStateName,
wh.name AS warehouseName, wh.name AS warehouseName,
tob.description AS ticketObservation, tob.description AS ticketObservation,
a.street, a.street,
a.postalCode, a.postalCode,
a.city, a.city,
am.name AS agencyModeName, am.name AS agencyModeName,
u.nickname AS userNickname, u.nickname AS userNickname,
vn.ticketTotalVolume(t.id) AS volume, vn.ticketTotalVolume(t.id) AS volume,
tob.description tob.description
FROM route r FROM vn.route r
JOIN ticket t ON t.routeFk = r.id JOIN ticket t ON t.routeFk = r.id
LEFT JOIN ticketState ts ON ts.ticketFk = t.id LEFT JOIN ticketState ts ON ts.ticketFk = t.id
LEFT JOIN state st ON st.id = ts.stateFk LEFT JOIN state st ON st.id = ts.stateFk
LEFT JOIN warehouse wh ON wh.id = t.warehouseFk LEFT JOIN warehouse wh ON wh.id = t.warehouseFk
LEFT JOIN ticketObservation tob ON tob.ticketFk = t.id LEFT JOIN observationType ot ON ot.code = 'delivery'
LEFT JOIN observationType ot ON tob.observationTypeFk = ot.id LEFT JOIN ticketObservation tob ON tob.ticketFk = t.id
AND ot.code = 'delivery' AND tob.observationTypeFk = ot.id
LEFT JOIN address a ON a.id = t.addressFk LEFT JOIN address a ON a.id = t.addressFk
LEFT JOIN agencyMode am ON am.id = t.agencyModeFk LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
LEFT JOIN account.user u ON u.id = r.workerFk LEFT JOIN account.user u ON u.id = r.workerFk
LEFT JOIN vehicle v ON v.id = r.vehicleFk` LEFT JOIN vehicle v ON v.id = r.vehicleFk`
); );
if (!filter.where) filter.where = {}; if (!filter.where) filter.where = {};

View File

@ -0,0 +1,181 @@
const Imap = require('imap');
module.exports = Self => {
Self.remoteMethod('checkInbox', {
description: 'Check an email inbox and process it',
accessType: 'READ',
returns:
{
arg: 'body',
type: 'file',
root: true
},
http: {
path: `/checkInbox`,
verb: 'POST'
}
});
Self.checkInbox = async() => {
let imapConfig = await Self.app.models.WorkerTimeControlParams.findOne();
let imap = new Imap({
user: imapConfig.mailUser,
password: imapConfig.mailPass,
host: imapConfig.mailHost,
port: 993,
tls: true
});
let isEmailOk;
let uid;
let emailBody;
function openInbox(cb) {
imap.openBox('INBOX', true, cb);
}
imap.once('ready', function() {
openInbox(function(err, box) {
if (err) throw err;
const totalMessages = box.messages.total;
if (totalMessages == 0)
imap.end();
let f = imap.seq.fetch('1:*', {
bodies: ['HEADER.FIELDS (FROM SUBJECT)', '1'],
struct: true
});
f.on('message', function(msg, seqno) {
isEmailOk = false;
msg.on('body', function(stream, info) {
let buffer = '';
let bufferCopy = '';
stream.on('data', function(chunk) {
buffer = chunk.toString('utf8');
if (info.which === '1' && bufferCopy.length == 0)
bufferCopy = buffer.replace(/\s/g, ' ');
});
stream.on('end', function() {
if (bufferCopy.length > 0) {
emailBody = bufferCopy.toUpperCase().trim();
const bodyPositionOK = emailBody.match(/\bOK\b/i);
if (bodyPositionOK != null && (bodyPositionOK.index == 0 || bodyPositionOK.index == 122))
isEmailOk = true;
else
isEmailOk = false;
}
});
msg.once('attributes', function(attrs) {
uid = attrs.uid;
});
msg.once('end', function() {
if (info.which === 'HEADER.FIELDS (FROM SUBJECT)') {
if (isEmailOk) {
imap.move(uid, 'exito', function(err) {
});
emailConfirm(buffer);
} else {
imap.move(uid, 'error', function(err) {
});
emailReply(buffer, emailBody);
}
}
});
});
});
f.once('end', function() {
imap.end();
});
});
});
imap.connect();
return 'Leer emails de gestion horaria';
};
async function emailConfirm(buffer) {
const now = new Date();
const from = JSON.stringify(Imap.parseHeader(buffer).from);
const subject = JSON.stringify(Imap.parseHeader(buffer).subject);
const timeControlDate = await getEmailDate(subject);
const week = timeControlDate[0];
const year = timeControlDate[1];
const user = await getUser(from);
let workerMail;
if (user.id != null) {
workerMail = await Self.app.models.WorkerTimeControlMail.findOne({
where: {
week: week,
year: year,
workerFk: user.id
}
});
}
if (workerMail != null) {
await workerMail.updateAttributes({
updated: now,
state: 'CONFIRMED'
});
}
}
async function emailReply(buffer, emailBody) {
const now = new Date();
const from = JSON.stringify(Imap.parseHeader(buffer).from);
const subject = JSON.stringify(Imap.parseHeader(buffer).subject);
const timeControlDate = await getEmailDate(subject);
const week = timeControlDate[0];
const year = timeControlDate[1];
const user = await getUser(from);
let workerMail;
if (user.id != null) {
workerMail = await Self.app.models.WorkerTimeControlMail.findOne({
where: {
week: week,
year: year,
workerFk: user.id
}
});
if (workerMail != null) {
await workerMail.updateAttributes({
updated: now,
state: 'REVISE',
reason: emailBody
});
} else
await sendMail(user, subject, emailBody);
}
}
async function getUser(workerEmail) {
const userEmail = workerEmail.match(/(?<=<)(.*?)(?=>)/);
let [user] = await Self.rawSql(`SELECT u.id,u.name FROM account.user u
LEFT JOIN account.mailForward m on m.account = u.id
WHERE forwardTo =? OR
CONCAT(u.name,'@verdnatura.es') = ?`,
[userEmail[0], userEmail[0]]);
return user;
}
async function getEmailDate(subject) {
const date = subject.match(/\d+/g);
return date;
}
async function sendMail(user, subject, emailBody) {
const sendTo = 'rrhh@verdnatura.es';
const emailSubject = subject + ' ' + user.name;
await Self.app.models.Mail.create({
receiver: sendTo,
subject: emailSubject,
body: emailBody
});
}
};

View File

@ -332,18 +332,9 @@ module.exports = Self => {
}, myOptions); }, myOptions);
const timestamp = started.getTime() / 1000; const timestamp = started.getTime() / 1000;
await models.Mail.create({ const url = `${salix.url}worker/${previousWorkerFk}/time-control?timestamp=${timestamp}`;
receiver: previousReceiver,
subject: $t('Record of hours week', {
week: args.week,
year: args.year
}),
body: `${salix.url}worker/${previousWorkerFk}/time-control?timestamp=${timestamp}`
}, myOptions);
query = `INSERT IGNORE INTO workerTimeControlMail (workerFk, year, week) await models.WorkerTimeControl.weeklyHourRecordEmail(ctx, previousReceiver, args.week, args.year, url);
VALUES (?, ?, ?);`;
await Self.rawSql(query, [previousWorkerFk, args.year, args.week], myOptions);
previousWorkerFk = day.workerFk; previousWorkerFk = day.workerFk;
previousReceiver = day.receiver; previousReceiver = day.receiver;

View File

@ -2,15 +2,12 @@ const models = require('vn-loopback/server/server').models;
describe('workerTimeControl sendMail()', () => { describe('workerTimeControl sendMail()', () => {
const workerId = 18; const workerId = 18;
const ctx = { const activeCtx = {
req: { getLocale: () => {
__: value => { return 'en';
return value; }
}
},
args: {}
}; };
const ctx = {req: activeCtx, args: {}};
it('should fill time control of a worker without records in Journey and with rest', async() => { it('should fill time control of a worker without records in Journey and with rest', async() => {
const tx = await models.WorkerTimeControl.beginTransaction({}); const tx = await models.WorkerTimeControl.beginTransaction({});

View File

@ -0,0 +1,53 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('weeklyHourRecordEmail', {
description: 'Sends the buyer waste email',
accessType: 'WRITE',
accepts: [
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'week',
type: 'number',
required: true,
},
{
arg: 'year',
type: 'number',
required: true
},
{
arg: 'url',
type: 'string',
required: true
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/weekly-hour-hecord-email',
verb: 'POST'
}
});
Self.weeklyHourRecordEmail = async(ctx, recipient, week, year, url) => {
const params = {
recipient: recipient,
lang: ctx.req.getLocale(),
week: week,
year: year,
url: url
};
const email = new Email('weekly-hour-record', params);
return email.send();
};
};

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/worker-time-control-mail/checkInbox')(Self);
};

View File

@ -7,6 +7,7 @@ module.exports = Self => {
require('../methods/worker-time-control/updateTimeEntry')(Self); require('../methods/worker-time-control/updateTimeEntry')(Self);
require('../methods/worker-time-control/sendMail')(Self); require('../methods/worker-time-control/sendMail')(Self);
require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self); require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self);
require('../methods/worker-time-control/weeklyHourRecordEmail')(Self);
Self.rewriteDbError(function(err) { Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY') if (err.code === 'ER_DUP_ENTRY')

View File

@ -49,4 +49,10 @@
.page-break-after { .page-break-after {
page-break-after: always; page-break-after: always;
}
.ellipsize {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
} }

View File

@ -0,0 +1,12 @@
const Stylesheet = require(`vn-print/core/stylesheet`);
const path = require('path');
const vnPrintPath = path.resolve('print');
module.exports = new Stylesheet([
`${vnPrintPath}/common/css/spacing.css`,
`${vnPrintPath}/common/css/misc.css`,
`${vnPrintPath}/common/css/layout.css`,
`${vnPrintPath}/common/css/email.css`])
.mergeStyles();

View File

@ -0,0 +1,6 @@
subject: Weekly time log
title: Record of hours week {0} year {1}
dear: Dear worker
description: Access the following link:<br/><br/>
{0} <br/><br/>
Click 'SATISFIED' if you agree with the hours worked. Otherwise, press 'NOT SATISFIED', detailing the cause of the disagreement.

View File

@ -0,0 +1,6 @@
subject: Registro de horas semanal
title: Registro de horas semana {0} año {1}
dear: Estimado trabajador
description: Acceda al siguiente enlace:<br/><br/>
{0} <br/><br/>
Pulse 'CONFORME' si esta de acuerdo con las horas trabajadas. En caso contrario pulse 'NO CONFORME', detallando la causa de la disconformidad.

View File

@ -0,0 +1,9 @@
<email-body v-bind="$props">
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title', [week, year]) }}</h1>
<p>{{$t('dear')}},</p>
<p v-html="$t('description', [url])"></p>
</div>
</div>
</email-body>

View File

@ -0,0 +1,23 @@
const Component = require(`vn-print/core/component`);
const emailBody = new Component('email-body');
module.exports = {
name: 'weekly-hour-record',
components: {
'email-body': emailBody.build()
},
props: {
week: {
type: Number,
required: true
},
year: {
type: Number,
required: true
},
url: {
type: String,
required: true
}
}
};

View File

@ -1,6 +1,6 @@
html { html {
font-family: "Roboto"; font-family: "Roboto";
margin-top: -7px; margin-top: -6px;
} }
* { * {
box-sizing: border-box; box-sizing: border-box;
@ -9,7 +9,7 @@ html {
} }
#vertical { #vertical {
writing-mode: vertical-rl; writing-mode: vertical-rl;
height: 226px; height: 240px;
margin-left: -13px; margin-left: -13px;
} }
.outline { .outline {
@ -18,6 +18,7 @@ html {
} }
#nickname { #nickname {
font-size: 22px; font-size: 22px;
max-width: 50px;
} }
#agencyDescripton { #agencyDescripton {
font-size: 32px; font-size: 32px;

View File

@ -1,35 +1,36 @@
<report-body v-bind="$props"> <!DOCTYPE html>
<template v-slot:header> <html>
<span></span> <body>
</template> <table v-for="labelData in labelsData" style="break-before: page">
<table v-for="labelData in labelsData"> <tbody>
<tbody> <tr>
<tr> <td rowspan="6"><span id="vertical" class="ellipsize">
<td rowspan="6"><span id="vertical">{{labelData.levelV}}</span></td> {{labelData.collectionFk ? `${labelData.collectionFk} ~ ${labelData.wagon}-${labelData.level}` : '-'.repeat(23)}}
<td id="ticketFk">{{labelData.ticketFk}} ⬸ {{labelData.clientFk}}</td> </span></td>
<td colspan="2" id="shipped">{{labelData.shipped}}</td> <td id="ticketFk">
</tr> {{labelData.clientFk ? `${labelData.ticketFk} « ${labelData.clientFk}` : labelData.ticketFk}}
<tr> </td>
<td rowspan="3"><div v-html="getBarcode(labelData.ticketFk)" id="barcode"></div></td> <td colspan="2" id="shipped">{{labelData.shipped ? labelData.shipped : '---'}}</td>
<td class="outline">{{labelData.workerCode}}</td> </tr>
</tr> <tr>
<tr> <td rowspan="3"><div v-html="getBarcode(labelData.ticketFk)" id="barcode"></div></td>
<td class="outline">{{labelData.labelCount}}</td> <td class="outline">{{labelData.workerCode ? labelData.workerCode : '---'}}</td>
</tr> </tr>
<tr> <tr>
<td class="outline">{{labelData.size}}</td> <td class="outline">{{labelData.labelCount ? labelData.labelCount : 0}}</td>
</tr> </tr>
<tr> <tr>
<td><div id="agencyDescripton">{{labelData.agencyDescription}}</div></td> <td class="outline">{{labelData.code == 'plant' ? labelData.size + 'cm' : labelData.volume + 'm³'}}</td>
<td id="bold">{{labelData.lineCount}}</td> </tr>
</tr> <tr>
<tr> <td><div id="agencyDescripton" class="ellipsize">{{labelData.agencyDescription}}</div></td>
<td id="nickname">{{labelData.nickName}}</td> <td id="bold">{{labelData.lineCount ? labelData.lineCount : 0}}</td>
<td id="bold">{{labelData.agencyHour}}</td> </tr>
</tr> <tr>
</tbody> <td id="nickname" class="ellipsize">{{labelData.nickName ? labelData.nickName : '---'}}</td>
</table> <td id="bold">{{labelData.shipped ? labelData.shippedHour : labelData.zoneHour}}</td>
<template v-slot:footer> </tr>
<span></span> </tbody>
</template> </table>
</report-body> </body>
</html>

View File

@ -40,7 +40,7 @@ module.exports = {
format: 'code128', format: 'code128',
displayValue: false, displayValue: false,
width: 3.8, width: 3.8,
height: 110, height: 115,
}); });
return xmlSerializer.serializeToString(svgNode); return xmlSerializer.serializeToString(svgNode);
}, },

View File

@ -2,8 +2,8 @@
"width": "10.4cm", "width": "10.4cm",
"height": "4.8cm", "height": "4.8cm",
"margin": { "margin": {
"top": "0cm", "top": "0.3cm",
"right": "0.5cm", "right": "0.6cm",
"bottom": "0cm", "bottom": "0cm",
"left": "0cm" "left": "0cm"
}, },

View File

@ -1,21 +1,23 @@
SELECT c.itemPackingTypeFk, SELECT tc.collectionFk,
CONCAT(tc.collectionFk, ' ', LEFT(cc.code, 4)) color, SUBSTRING('ABCDEFGH', tc.wagon, 1) wagon,
CONCAT(tc.collectionFk, ' ', SUBSTRING('ABCDEFGH',tc.wagon, 1), '-', tc.`level`) levelV, tc.`level`,
tc.ticketFk, t.id ticketFk,
LEFT(COALESCE(et.description, zo.name, am.name),12) agencyDescription, COALESCE(et.description, zo.name, am.name) agencyDescription,
am.name, am.name,
t.clientFk, t.clientFk,
CONCAT(CAST(SUM(sv.volume) AS DECIMAL(5, 2)), '') m3 , CAST(SUM(sv.volume) AS DECIMAL(5, 2)) volume,
CAST(IF(ic.code = 'plant', CONCAT(MAX(i.`size`),' cm'), COUNT(*)) AS CHAR) size, MAX(i.`size`) `size`,
ic.code,
w.code workerCode, w.code workerCode,
tt.labelCount, TIME_FORMAT(t.shipped, '%H:%i') shippedHour,
IF(HOUR(t.shipped), TIME_FORMAT(t.shipped, '%H:%i'), TIME_FORMAT(zo.`hour`, '%H:%i')) agencyHour, TIME_FORMAT(zo.`hour`, '%H:%i') zoneHour,
DATE_FORMAT(t.shipped, '%d/%m/%y') shipped, DATE_FORMAT(t.shipped, '%d/%m/%y') shipped,
COUNT(*) lineCount, t.nickName,
t.nickName tt.labelCount,
COUNT(*) lineCount
FROM vn.ticket t FROM vn.ticket t
JOIN vn.ticketCollection tc ON tc.ticketFk = t.id LEFT JOIN vn.ticketCollection tc ON tc.ticketFk = t.id
JOIN vn.collection c ON c.id = tc.collectionFk LEFT JOIN vn.collection c ON c.id = tc.collectionFk
LEFT JOIN vn.collectionColors cc ON cc.shelve = tc.`level` LEFT JOIN vn.collectionColors cc ON cc.shelve = tc.`level`
AND cc.wagon = tc.wagon AND cc.wagon = tc.wagon
AND cc.trainFk = c.trainFk AND cc.trainFk = c.trainFk
@ -24,12 +26,12 @@ SELECT c.itemPackingTypeFk,
JOIN vn.item i ON i.id = s.itemFk JOIN vn.item i ON i.id = s.itemFk
JOIN vn.itemType it ON it.id = i.typeFk JOIN vn.itemType it ON it.id = i.typeFk
JOIN vn.itemCategory ic ON ic.id = it.categoryFk JOIN vn.itemCategory ic ON ic.id = it.categoryFk
JOIN vn.worker w ON w.id = c.workerFk LEFT JOIN vn.worker w ON w.id = c.workerFk
JOIN vn.agencyMode am ON am.id = t.agencyModeFk JOIN vn.agencyMode am ON am.id = t.agencyModeFk
LEFT JOIN vn.ticketTrolley tt ON tt.ticket = t.id LEFT JOIN vn.ticketTrolley tt ON tt.ticket = t.id
LEFT JOIN vn.`zone` zo ON t.zoneFk = zo.id LEFT JOIN vn.`zone` zo ON t.zoneFk = zo.id
LEFT JOIN vn.routesMonitor rm ON rm.routeFk = t.routeFk LEFT JOIN vn.routesMonitor rm ON rm.routeFk = t.routeFk
LEFT JOIN vn.expeditionTruck et ON et.id = rm.expeditionTruckFk LEFT JOIN vn.expeditionTruck et ON et.id = rm.expeditionTruckFk
WHERE tc.ticketFk IN (?) WHERE t.id IN (?)
GROUP BY t.id GROUP BY t.id
ORDER BY cc.`code`; ORDER BY cc.`code`;