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);
await fs.unlink(dstFile);
} catch (err) {
if (err.code != 'ENOENT')
if (err.code != 'ENOENT' && dms.file)
throw err;
}

View File

@ -2569,10 +2569,6 @@ UPDATE `vn`.`route`
UPDATE `vn`.`route`
SET `invoiceInFk`=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`)
VALUES

View File

@ -37,7 +37,7 @@ export default class Controller extends Section {
const validations = window.validations;
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.newProperties = this.getInstance(log.newInstance, locale);

View File

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

View File

@ -33,36 +33,36 @@ module.exports = Self => {
const stmt = new ParameterizedSQL(
`SELECT
t.id,
t.packages,
t.warehouseFk,
t.nickname,
t.clientFk,
t.priority,
t.addressFk,
st.code AS ticketStateCode,
st.name AS ticketStateName,
wh.name AS warehouseName,
tob.description AS ticketObservation,
a.street,
a.postalCode,
a.city,
am.name AS agencyModeName,
u.nickname AS userNickname,
vn.ticketTotalVolume(t.id) AS volume,
tob.description
FROM route r
JOIN ticket t ON t.routeFk = r.id
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
LEFT JOIN state st ON st.id = ts.stateFk
LEFT JOIN warehouse wh ON wh.id = t.warehouseFk
LEFT JOIN ticketObservation tob ON tob.ticketFk = t.id
LEFT JOIN observationType ot ON tob.observationTypeFk = ot.id
AND ot.code = 'delivery'
LEFT JOIN address a ON a.id = t.addressFk
LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
LEFT JOIN account.user u ON u.id = r.workerFk
LEFT JOIN vehicle v ON v.id = r.vehicleFk`
t.id,
t.packages,
t.warehouseFk,
t.nickname,
t.clientFk,
t.priority,
t.addressFk,
st.code AS ticketStateCode,
st.name AS ticketStateName,
wh.name AS warehouseName,
tob.description AS ticketObservation,
a.street,
a.postalCode,
a.city,
am.name AS agencyModeName,
u.nickname AS userNickname,
vn.ticketTotalVolume(t.id) AS volume,
tob.description
FROM vn.route r
JOIN ticket t ON t.routeFk = r.id
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
LEFT JOIN state st ON st.id = ts.stateFk
LEFT JOIN warehouse wh ON wh.id = t.warehouseFk
LEFT JOIN observationType ot ON ot.code = 'delivery'
LEFT JOIN ticketObservation tob ON tob.ticketFk = t.id
AND tob.observationTypeFk = ot.id
LEFT JOIN address a ON a.id = t.addressFk
LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
LEFT JOIN account.user u ON u.id = r.workerFk
LEFT JOIN vehicle v ON v.id = r.vehicleFk`
);
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);
const timestamp = started.getTime() / 1000;
await models.Mail.create({
receiver: previousReceiver,
subject: $t('Record of hours week', {
week: args.week,
year: args.year
}),
body: `${salix.url}worker/${previousWorkerFk}/time-control?timestamp=${timestamp}`
}, myOptions);
const url = `${salix.url}worker/${previousWorkerFk}/time-control?timestamp=${timestamp}`;
query = `INSERT IGNORE INTO workerTimeControlMail (workerFk, year, week)
VALUES (?, ?, ?);`;
await Self.rawSql(query, [previousWorkerFk, args.year, args.week], myOptions);
await models.WorkerTimeControl.weeklyHourRecordEmail(ctx, previousReceiver, args.week, args.year, url);
previousWorkerFk = day.workerFk;
previousReceiver = day.receiver;

View File

@ -2,15 +2,12 @@ const models = require('vn-loopback/server/server').models;
describe('workerTimeControl sendMail()', () => {
const workerId = 18;
const ctx = {
req: {
__: value => {
return value;
}
},
args: {}
const activeCtx = {
getLocale: () => {
return 'en';
}
};
const ctx = {req: activeCtx, args: {}};
it('should fill time control of a worker without records in Journey and with rest', async() => {
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/sendMail')(Self);
require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self);
require('../methods/worker-time-control/weeklyHourRecordEmail')(Self);
Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY')

View File

@ -49,4 +49,10 @@
.page-break-after {
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 {
font-family: "Roboto";
margin-top: -7px;
margin-top: -6px;
}
* {
box-sizing: border-box;
@ -9,7 +9,7 @@ html {
}
#vertical {
writing-mode: vertical-rl;
height: 226px;
height: 240px;
margin-left: -13px;
}
.outline {
@ -18,6 +18,7 @@ html {
}
#nickname {
font-size: 22px;
max-width: 50px;
}
#agencyDescripton {
font-size: 32px;

View File

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

View File

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

View File

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

View File

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