diff --git a/back/methods/dms/deleteTrashFiles.js b/back/methods/dms/deleteTrashFiles.js index 63d7021c5..f14e65e9f 100644 --- a/back/methods/dms/deleteTrashFiles.js +++ b/back/methods/dms/deleteTrashFiles.js @@ -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; } diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index f41d1eb53..5fa2d1655 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -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 diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index 2796931a0..f30878b9f 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -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); diff --git a/modules/client/back/methods/client/filter.js b/modules/client/back/methods/client/filter.js index 3e1ea43bb..1ae569fd3 100644 --- a/modules/client/back/methods/client/filter.js +++ b/modules/client/back/methods/client/filter.js @@ -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)); diff --git a/modules/route/back/methods/route/getTickets.js b/modules/route/back/methods/route/getTickets.js index 18e5abaf2..708644c1a 100644 --- a/modules/route/back/methods/route/getTickets.js +++ b/modules/route/back/methods/route/getTickets.js @@ -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 = {}; diff --git a/modules/worker/back/methods/worker-time-control-mail/checkInbox.js b/modules/worker/back/methods/worker-time-control-mail/checkInbox.js new file mode 100644 index 000000000..7825f38b8 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control-mail/checkInbox.js @@ -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 + }); + } +}; diff --git a/modules/worker/back/methods/worker-time-control/sendMail.js b/modules/worker/back/methods/worker-time-control/sendMail.js index cd9cacbd7..b38405c1d 100644 --- a/modules/worker/back/methods/worker-time-control/sendMail.js +++ b/modules/worker/back/methods/worker-time-control/sendMail.js @@ -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; diff --git a/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js b/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js index 4cc6e54e3..24bfd6904 100644 --- a/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js +++ b/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js @@ -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({}); diff --git a/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js b/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js new file mode 100644 index 000000000..0cf614e57 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js @@ -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(); + }; +}; diff --git a/modules/worker/back/models/worker-time-control-mail.js b/modules/worker/back/models/worker-time-control-mail.js new file mode 100644 index 000000000..36f3851b6 --- /dev/null +++ b/modules/worker/back/models/worker-time-control-mail.js @@ -0,0 +1,3 @@ +module.exports = Self => { + require('../methods/worker-time-control-mail/checkInbox')(Self); +}; diff --git a/modules/worker/back/models/worker-time-control.js b/modules/worker/back/models/worker-time-control.js index 9f802511a..7339f5d15 100644 --- a/modules/worker/back/models/worker-time-control.js +++ b/modules/worker/back/models/worker-time-control.js @@ -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') diff --git a/print/common/css/misc.css b/print/common/css/misc.css index df8bf571a..ce6c641a0 100644 --- a/print/common/css/misc.css +++ b/print/common/css/misc.css @@ -49,4 +49,10 @@ .page-break-after { page-break-after: always; +} + +.ellipsize { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } \ No newline at end of file diff --git a/print/templates/email/weekly-hour-record/assets/css/import.js b/print/templates/email/weekly-hour-record/assets/css/import.js new file mode 100644 index 000000000..1582b82c5 --- /dev/null +++ b/print/templates/email/weekly-hour-record/assets/css/import.js @@ -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(); + diff --git a/print/templates/email/weekly-hour-record/locale/en.yml b/print/templates/email/weekly-hour-record/locale/en.yml new file mode 100644 index 000000000..817e5451e --- /dev/null +++ b/print/templates/email/weekly-hour-record/locale/en.yml @@ -0,0 +1,6 @@ +subject: Weekly time log +title: Record of hours week {0} year {1} +dear: Dear worker +description: Access the following link:

+ {0}

+ Click 'SATISFIED' if you agree with the hours worked. Otherwise, press 'NOT SATISFIED', detailing the cause of the disagreement. diff --git a/print/templates/email/weekly-hour-record/locale/es.yml b/print/templates/email/weekly-hour-record/locale/es.yml new file mode 100644 index 000000000..b70862f16 --- /dev/null +++ b/print/templates/email/weekly-hour-record/locale/es.yml @@ -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:

+ {0}

+ Pulse 'CONFORME' si esta de acuerdo con las horas trabajadas. En caso contrario pulse 'NO CONFORME', detallando la causa de la disconformidad. diff --git a/print/templates/email/weekly-hour-record/weekly-hour-record.html b/print/templates/email/weekly-hour-record/weekly-hour-record.html new file mode 100644 index 000000000..84abb4c61 --- /dev/null +++ b/print/templates/email/weekly-hour-record/weekly-hour-record.html @@ -0,0 +1,9 @@ + +
+
+

{{ $t('title', [week, year]) }}

+

{{$t('dear')}},

+

+
+
+
diff --git a/print/templates/email/weekly-hour-record/weekly-hour-record.js b/print/templates/email/weekly-hour-record/weekly-hour-record.js new file mode 100755 index 000000000..8fdaea0ce --- /dev/null +++ b/print/templates/email/weekly-hour-record/weekly-hour-record.js @@ -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 + } + } +}; diff --git a/print/templates/reports/collection-label/assets/css/style.css b/print/templates/reports/collection-label/assets/css/style.css index fe1975445..597921c92 100644 --- a/print/templates/reports/collection-label/assets/css/style.css +++ b/print/templates/reports/collection-label/assets/css/style.css @@ -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; diff --git a/print/templates/reports/collection-label/collection-label.html b/print/templates/reports/collection-label/collection-label.html index eeb82ac4a..6716d1fe5 100644 --- a/print/templates/reports/collection-label/collection-label.html +++ b/print/templates/reports/collection-label/collection-label.html @@ -1,35 +1,36 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{labelData.levelV}}{{labelData.ticketFk}} ⬸ {{labelData.clientFk}}{{labelData.shipped}}
{{labelData.workerCode}}
{{labelData.labelCount}}
{{labelData.size}}
{{labelData.agencyDescription}}
{{labelData.lineCount}}
{{labelData.nickName}}{{labelData.agencyHour}}
- -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{labelData.collectionFk ? `${labelData.collectionFk} ~ ${labelData.wagon}-${labelData.level}` : '-'.repeat(23)}} + + {{labelData.clientFk ? `${labelData.ticketFk} « ${labelData.clientFk}` : labelData.ticketFk}} + {{labelData.shipped ? labelData.shipped : '---'}}
{{labelData.workerCode ? labelData.workerCode : '---'}}
{{labelData.labelCount ? labelData.labelCount : 0}}
{{labelData.code == 'plant' ? labelData.size + 'cm' : labelData.volume + 'm³'}}
{{labelData.agencyDescription}}
{{labelData.lineCount ? labelData.lineCount : 0}}
{{labelData.nickName ? labelData.nickName : '---'}}{{labelData.shipped ? labelData.shippedHour : labelData.zoneHour}}
+ + \ No newline at end of file diff --git a/print/templates/reports/collection-label/collection-label.js b/print/templates/reports/collection-label/collection-label.js index 7bc25ad79..d2d5f6417 100644 --- a/print/templates/reports/collection-label/collection-label.js +++ b/print/templates/reports/collection-label/collection-label.js @@ -40,7 +40,7 @@ module.exports = { format: 'code128', displayValue: false, width: 3.8, - height: 110, + height: 115, }); return xmlSerializer.serializeToString(svgNode); }, diff --git a/print/templates/reports/collection-label/options.json b/print/templates/reports/collection-label/options.json index 175b3c1db..ae88e6c0c 100644 --- a/print/templates/reports/collection-label/options.json +++ b/print/templates/reports/collection-label/options.json @@ -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" }, diff --git a/print/templates/reports/collection-label/sql/labelsData.sql b/print/templates/reports/collection-label/sql/labelsData.sql index 6f5b47a54..b799b289b 100644 --- a/print/templates/reports/collection-label/sql/labelsData.sql +++ b/print/templates/reports/collection-label/sql/labelsData.sql @@ -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)), 'm³') 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`; \ No newline at end of file