Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5092-listado-ticketsSinFacturar
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Alexandre Riera 2023-03-20 09:54:02 +01:00
commit a94e47e622
14 changed files with 476 additions and 224 deletions

View File

@ -1,215 +0,0 @@
const md5 = require('md5');
const fs = require('fs-extra');
module.exports = Self => {
Self.remoteMethodCtx('saveSign', {
description: 'Save sign',
accessType: 'WRITE',
accepts:
[
{
arg: 'signContent',
type: 'string',
required: true,
description: 'The sign content'
}, {
arg: 'tickets',
type: ['number'],
required: true,
description: 'The tickets'
}, {
arg: 'signedTime',
type: 'date',
description: 'The signed time'
}, {
arg: 'addressFk',
type: 'number',
required: true,
description: 'The address fk'
}
],
returns: {
type: 'Object',
root: true
},
http: {
path: `/saveSign`,
verb: 'POST'
}
});
async function createGestDoc(ticketId, userFk) {
const models = Self.app.models;
if (!await gestDocExists(ticketId)) {
const result = await models.Ticket.findOne({
where: {
id: ticketId
},
include: [
{
relation: 'warehouse',
scope: {
fields: ['id']
}
}, {
relation: 'client',
scope: {
fields: ['name']
}
}, {
relation: 'route',
scope: {
fields: ['id']
}
}
]
});
const warehouseFk = result.warehouseFk;
const companyFk = result.companyFk;
const client = result.client.name;
const route = result.route.id;
const resultDmsType = await models.DmsType.findOne({
where: {
code: 'Ticket'
}
});
const resultDms = await models.Dms.create({
dmsTypeFk: resultDmsType.id,
reference: ticketId,
description: `Ticket ${ticketId} Cliente ${client} Ruta ${route}`,
companyFk: companyFk,
warehouseFk: warehouseFk,
workerFk: userFk
});
return resultDms.insertId;
}
}
async function gestDocExists(ticket) {
const models = Self.app.models;
const result = await models.TicketDms.findOne({
where: {
ticketFk: ticket
},
fields: ['dmsFk']
});
if (result == null)
return false;
const isSigned = await models.Ticket.findOne({
where: {
id: ticket
},
fields: ['isSigned']
});
if (isSigned)
return true;
else
await models.Dms.destroyById(ticket);
}
async function dmsRecover(ticket, signContent) {
const models = Self.app.models;
await models.DmsRecover.create({
ticketFk: ticket,
sign: signContent
});
}
async function ticketGestdoc(ticket, dmsFk) {
const models = Self.app.models;
models.TicketDms.replaceOrCreate({
ticketFk: ticket,
dmsFk: dmsFk
});
const queryVnTicketSetState = `CALL vn.ticket_setState(?, ?)`;
await Self.rawSql(queryVnTicketSetState, [ticket, 'DELIVERED']);
}
async function updateGestdoc(file, ticket) {
const models = Self.app.models;
models.Dms.updateOne({
where: {
id: ticket
},
file: file,
contentType: 'image/png'
});
}
Self.saveSign = async(ctx, signContent, tickets, signedTime) => {
const models = Self.app.models;
let tx = await Self.beginTransaction({});
try {
const userId = ctx.req.accessToken.userId;
const dmsDir = `storage/dms`;
let image = null;
for (let i = 0; i < tickets.length; i++) {
const alertLevel = await models.TicketState.findOne({
where: {
ticketFk: tickets[i]
},
fields: ['alertLevel']
});
signedTime ? signedTime != undefined : signedTime = Date.vnNew();
if (alertLevel >= 2) {
let dir;
let id = null;
let fileName = null;
if (!await gestDocExists(tickets[i])) {
id = await createGestDoc(tickets[i], userId);
const hashDir = md5(id).substring(0, 3);
dir = `${dmsDir}/${hashDir}`;
if (!fs.existsSync(dir))
fs.mkdirSync(dir);
fileName = `${id}.png`;
image = `${dir}/${fileName}`;
} else
if (image != null) {
if (!fs.existsSync(dir))
dmsRecover(tickets[i], signContent);
else {
fs.writeFile(image, signContent, 'base64', async function(err) {
if (err) {
await tx.rollback();
return err.message;
}
});
}
} else
dmsRecover(tickets[i], signContent);
if (id != null && fileName.length > 0) {
ticketGestdoc(tickets[i], id);
updateGestdoc(id, fileName);
}
}
}
if (tx) await tx.commit();
return 'OK';
} catch (err) {
await tx.rollback();
throw err.message;
}
};
};

View File

@ -9,17 +9,29 @@
"properties": { "properties": {
"id": { "id": {
"id": true, "id": true,
"type": "number", "type": "number"
"forceId": false
}, },
"date": { "created": {
"type": "date" "type": "date"
}, },
"m3":{ "longitude":{
"type": "number" "type": "number"
}, },
"warehouseFk":{ "latitude":{
"type": "number"
},
"dated":{
"type": "date"
},
"ticketFk":{
"type": "number" "type": "number"
} }
},
"relations": {
"ticket": {
"type": "belongsTo",
"model": "Ticket",
"foreignKey": "ticketFk"
}
} }
} }

View File

@ -6,7 +6,6 @@ module.exports = Self => {
require('../methods/dms/removeFile')(Self); require('../methods/dms/removeFile')(Self);
require('../methods/dms/updateFile')(Self); require('../methods/dms/updateFile')(Self);
require('../methods/dms/deleteTrashFiles')(Self); require('../methods/dms/deleteTrashFiles')(Self);
require('../methods/dms/saveSign')(Self);
Self.checkRole = async function(ctx, id) { Self.checkRole = async function(ctx, id) {
const models = Self.app.models; const models = Self.app.models;

View File

@ -0,0 +1,74 @@
DROP TABLE `vn`.`dmsRecover`;
ALTER TABLE `vn`.`delivery` DROP FOREIGN KEY delivery_FK;
ALTER TABLE `vn`.`delivery` DROP COLUMN addressFk;
ALTER TABLE `vn`.`delivery` ADD ticketFk INT NOT NULL;
ALTER TABLE `vn`.`delivery` ADD CONSTRAINT delivery_ticketFk_FK FOREIGN KEY (`ticketFk`) REFERENCES `vn`.`ticket`(`id`);
DELETE FROM `salix`.`ACL` WHERE `property` = 'saveSign';
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
VALUES
('Ticket','saveSign','WRITE','ALLOW','employee');
DROP PROCEDURE IF EXISTS vn.route_getTickets;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`route_getTickets`(vRouteFk INT)
BEGIN
/**
* Pasado un RouteFk devuelve la información
* de sus tickets.
*
* @param vRouteFk
*
* @select Información de los tickets
*/
SELECT
t.id Id,
t.clientFk Client,
a.id Address,
t.packages Packages,
a.street AddressName,
a.postalCode PostalCode,
a.city City,
sub2.itemPackingTypeFk PackingType,
c.phone ClientPhone,
c.mobile ClientMobile,
a.phone AddressPhone,
a.mobile AddressMobile,
d.longitude Longitude,
d.latitude Latitude,
wm.mediaValue SalePersonPhone,
tob.Note Note,
t.isSigned Signed
FROM ticket t
JOIN client c ON t.clientFk = c.id
JOIN address a ON t.addressFk = a.id
LEFT JOIN delivery d ON t.id = d.ticketFk
LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk
LEFT JOIN
(SELECT tob.description Note, t.id
FROM ticketObservation tob
JOIN ticket t ON tob.ticketFk = t.id
JOIN observationType ot ON ot.id = tob.observationTypeFk
WHERE t.routeFk = vRouteFk
AND ot.code = 'delivery'
)tob ON tob.id = t.id
LEFT JOIN
(SELECT sub.ticketFk,
CONCAT('(', GROUP_CONCAT(DISTINCT sub.itemPackingTypeFk ORDER BY sub.items DESC SEPARATOR ','), ') ') itemPackingTypeFk
FROM (SELECT s.ticketFk , i.itemPackingTypeFk, COUNT(*) items
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
WHERE t.routeFk = vRouteFk
GROUP BY t.id,i.itemPackingTypeFk)sub
GROUP BY sub.ticketFk
) sub2 ON sub2.ticketFk = t.id
WHERE t.routeFk = vRouteFk
GROUP BY t.id
ORDER BY t.priority;
END$$
DELIMITER ;

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES ('Operator', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Operator', '*', 'WRITE', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,159 @@
ALTER TABLE `vn`.`operator` ADD sectorFk int(11) NULL;
ALTER TABLE `vn`.`operator` ADD labelerFk tinyint(3) unsigned NULL;
ALTER TABLE `vn`.`operator` ADD CONSTRAINT operator_FK_5 FOREIGN KEY (labelerFk) REFERENCES `vn`.`printer`(id) ON DELETE CASCADE ON UPDATE CASCADE;
UPDATE `vn`.`operator` o
JOIN (SELECT id, sectorFk, labelerFk
FROM `vn`.`worker`) sub ON sub.id = o.workerFk
SET o.sectorFk = sub.sectorFk,
o.labelerFk = sub.labelerFk;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`collection_printSticker`(
vSelf INT,
vLabelCount INT
)
BEGIN
/**
* Prints a yellow label from a collection or a ticket
*
* @param vSelf collection or ticket
* @param vLabelCount number of times the collection has been printed
*/
DECLARE vPrintArgs JSON DEFAULT JSON_OBJECT('collectionOrTicketFk', vSelf);
IF vLabelCount IS NULL THEN
INSERT INTO ticketTrolley
SELECT ticketFk, 1
FROM ticketCollection
WHERE collectionFk = vSelf
ON DUPLICATE KEY UPDATE labelCount = labelCount + 1;
ELSE
SET vPrintArgs = JSON_MERGE_PATCH(vPrintArgs, JSON_OBJECT('labelCount', vLabelCount));
END IF;
CALL report_print(
'LabelCollection',
(SELECT o.labelerFk FROM operator o WHERE o.workerFk = account.myUser_getId()),
account.myUser_getId(),
vPrintArgs,
'high'
);
END$$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`expeditionPallet_printLabel`(vSelf INT)
BEGIN
/**
* Calls the report_print procedure and passes it
* the necessary parameters for printing.
*
* @param vSelf expeditioPallet id.
*/
DECLARE vPrinterFk INT;
DECLARE vUserFk INT DEFAULT account.myUser_getId();
SELECT o.labelerFk INTO vPrinterFk
FROM operator o
WHERE o.workerFk = vUserFk;
CALL vn.report_print(
'LabelPalletExpedition',
vPrinterFk,
account.myUser_getId(),
JSON_OBJECT('palletFk', vSelf, 'userFk', vUserFk),
'high'
);
UPDATE vn.expeditionPallet
SET isPrint = TRUE
WHERE id = vSelf;
END$$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemShelving_getAlternatives`(vShelvingFk VARCHAR(10))
BEGIN
/**
* Devuelve un listado de posibles ubicaciones alternativas a ubicar los item de la matricula
* del carro que se le ha pasado.
*
* @param vShelvingFk matricula del carro
*/
SELECT is2.id,is2.shelvingFk , p.code, is2.itemFk , is2.visible, p.pickingOrder
FROM itemShelving is2
JOIN shelving sh ON sh.code = is2.shelvingFk
JOIN parking p ON p.id = sh.parkingFk
JOIN sector s ON s.id = p.sectorFk
LEFT JOIN operator o ON o.sectorFk = s.id
LEFT JOIN worker w ON w.sectorFk = s.id AND w.id = account.myUser_getId()
JOIN warehouse wh ON wh.id = s.warehouseFk
JOIN itemShelving is3 ON is3.itemFk = is2.itemFk AND is3.shelvingFk = vShelvingFk COLLATE utf8_unicode_ci
WHERE is2.shelvingFk <> vShelvingFk COLLATE utf8_unicode_ci
GROUP BY is2.id
ORDER BY p.pickingOrder DESC;
END$$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`operator_beforeInsert`
BEFORE INSERT ON `operator`
FOR EACH ROW
BEGIN
CALL vn.printer_checkSector(NEW.labelerFk, NEW.sectorFk);
END$$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`operator_beforeUpdate`
BEFORE UPDATE ON `operator`
FOR EACH ROW
BEGIN
IF NOT (NEW.labelerFk <=> OLD.labelerFk AND NEW.sectorFk <=> OLD.sectorFk) THEN
CALL vn.printer_checkSector(NEW.labelerFk, NEW.sectorFk);
END IF;
END$$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`worker_beforeUpdate`
BEFORE UPDATE ON `worker`
FOR EACH ROW
BEGIN
IF NOT (NEW.labelerFk <=> OLD.labelerFk AND NEW.sectorFk <=> OLD.sectorFk) THEN
CALL vn.printer_checkSector(NEW.labelerFk, NEW.sectorFk);
INSERT IGNORE INTO vn.operator (workerFk)
VALUES (NEW.id);
UPDATE operator
SET labelerFk = NEW.labelerFk,
sectorFk = NEW.sectorFk
WHERE workerFk = NEW.id;
END IF;
END$$
DELIMITER ;
CREATE OR REPLACE DEFINER=`root`@`localhost`
SQL SECURITY DEFINER
VIEW `vn`.`operatorWorkerCode`
AS SELECT `o`.`workerFk` AS `workerFk`,
concat(`w`.`firstName`, ' ', `w`.`lastName`) AS `fullName`,
`w`.`code` AS `code`,
`o`.`numberOfWagons` AS `numberOfWagons`
FROM (
(
`vn`.`worker` `w`
JOIN `vn`.`operator` `o` ON(`o`.`workerFk` = `w`.`id`)
)
JOIN `vn`.`sector` `s` ON(`o`.`sectorFk` = `s`.`id`)
)
WHERE `o`.`sectorFk` IS NOT NULL
AND `s`.`code` IN (
'H2',
'H2',
'PEQUES_H',
'ALTILLO COMP',
'ALTILLO ARTI'
)

View File

@ -173,6 +173,10 @@ INSERT INTO `vn`.`sector`(`id`, `description`, `warehouseFk`, `isPreviousPrepare
(1, 'First sector', 1, 1, 'FIRST'), (1, 'First sector', 1, 1, 'FIRST'),
(2, 'Second sector', 2, 0, 'SECOND'); (2, 'Second sector', 2, 0, 'SECOND');
INSERT INTO `vn`.`operator` (`workerFk`, `numberOfWagons`, `trainFk`, `itemPackingTypeFk`, `warehouseFk`, `sectorFk`, `labelerFk`)
VALUES ('1106', '1', '1', 'H', '1', '1', '1'),
('1107', '1', '1', 'V', '1', '2', '1');
INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAddress`) INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAddress`)
VALUES VALUES
(1, 'printer1', 'path1', 0, 1 , NULL), (1, 'printer1', 'path1', 0, 1 , NULL),

View File

@ -271,5 +271,6 @@
"This locker has already been assigned": "Esta taquilla ya ha sido asignada", "This locker has already been assigned": "Esta taquilla ya ha sido asignada",
"Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}", "Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}",
"Not exist this branch": "La rama no existe", "Not exist this branch": "La rama no existe",
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado",
"Insert a date range": "Inserte un rango de fechas" "Insert a date range": "Inserte un rango de fechas"
} }

View File

@ -0,0 +1,132 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('saveSign', {
description: 'Save sign',
accessType: 'WRITE',
accepts:
[
{
arg: 'tickets',
type: ['number'],
required: true,
description: 'The tickets'
},
{
arg: 'location',
type: 'object',
description: 'The employee location the moment the sign is saved'
},
{
arg: 'signedTime',
type: 'date',
description: 'The signed time'
}
],
http: {
path: `/saveSign`,
verb: 'POST'
}
});
Self.saveSign = async(ctx, options) => {
const args = Object.assign({}, ctx.args);
const models = Self.app.models;
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
async function setLocation(ticketId) {
await models.Delivery.create({
ticketFk: ticketId,
longitude: args.location.Longitude,
latitude: args.location.Latitude,
dated: args.signedTime || new Date()
}, myOptions);
}
async function gestDocExists(ticketId) {
const ticketDms = await models.TicketDms.findOne({
where: {ticketFk: ticketId},
fields: ['dmsFk']
}, myOptions);
if (!ticketDms) return false;
const ticket = await models.Ticket.findById(ticketId, {fields: ['isSigned']}, myOptions);
if (ticket.isSigned == true)
return true;
else
await models.Dms.destroyAll({where: {reference: ticketId}}, myOptions);
return false;
}
async function createGestDoc(id) {
const ticket = await models.Ticket.findById(id,
{include: [
{
relation: 'warehouse',
scope: {
fields: ['id']
}
}, {
relation: 'client',
scope: {
fields: ['name']
}
}, {
relation: 'route',
scope: {
fields: ['id']
}
}
]
}, myOptions);
const dmsType = await models.DmsType.findOne({where: {code: 'Ticket'}, fields: ['id']}, myOptions);
const ctxUploadFile = Object.assign({}, ctx);
ctxUploadFile.args = {
warehouseId: ticket.warehouseFk,
companyId: ticket.companyFk,
dmsTypeId: dmsType.id,
reference: id,
description: `Ticket ${id} Cliente ${ticket.client().name} Ruta ${ticket.route().id}`,
hasFile: true
};
await models.Ticket.uploadFile(ctxUploadFile, id, myOptions);
}
try {
for (let i = 0; i < args.tickets.length; i++) {
const ticketState = await models.TicketState.findOne(
{where: {ticketFk: args.tickets[i]},
fields: ['alertLevel']
}, myOptions);
const packedAlertLevel = await models.AlertLevel.findOne({where: {code: 'PACKED'},
fields: ['id']
}, myOptions);
if (ticketState.alertLevel < packedAlertLevel.id)
throw new UserError('This ticket cannot be signed because it has not been boxed');
else if (!await gestDocExists(args.tickets[i])) {
if (args.location) setLocation(args.tickets[i]);
await createGestDoc(args.tickets[i]);
await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [args.tickets[i], 'DELIVERED'], myOptions);
}
}
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,32 @@
const models = require('vn-loopback/server/server').models;
describe('Ticket saveSign()', () => {
const FormData = require('form-data');
const data = new FormData();
let ctx = {req: {
accessToken: {userId: 9},
headers: {
...data.getHeaders()
}
}};
it(`should throw error if the ticket's alert level is lower than 2`, async() => {
const tx = await models.TicketDms.beginTransaction({});
const ticketWithOkState = 12;
let error;
try {
const options = {transaction: tx};
ctx.args = {tickets: [ticketWithOkState]};
await models.Ticket.saveSign(ctx, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error).toBeDefined();
});
});

View File

@ -39,4 +39,5 @@ module.exports = function(Self) {
require('../methods/ticket/isRoleAdvanced')(Self); require('../methods/ticket/isRoleAdvanced')(Self);
require('../methods/ticket/collectionLabel')(Self); require('../methods/ticket/collectionLabel')(Self);
require('../methods/ticket/expeditionPalletLabel')(Self); require('../methods/ticket/expeditionPalletLabel')(Self);
require('../methods/ticket/saveSign')(Self);
}; };

View File

@ -44,6 +44,9 @@
"isDeleted": { "isDeleted": {
"type": "boolean" "type": "boolean"
}, },
"isSigned": {
"type": "boolean"
},
"priority": { "priority": {
"type": "number" "type": "number"
}, },

View File

@ -94,6 +94,9 @@
}, },
"WorkerTimeControlMail": { "WorkerTimeControlMail": {
"dataSource": "vn" "dataSource": "vn"
},
"Operator": {
"dataSource": "vn"
} }
} }

View File

@ -0,0 +1,44 @@
{
"name": "Operator",
"base": "VnModel",
"options": {
"mysql": {
"table": "operator"
}
},
"properties": {
"workerFk": {
"id": true,
"type": "number",
"required": true
},
"numberOfWagons": {
"type": "number"
},
"trainFk": {
"type": "number",
"required": true
},
"itemPackingTypeFk": {
"type": "string",
"required": true
},
"warehouseFk": {
"type": "number",
"required": true
},
"sectorFk ": {
"type": "number"
},
"labelerFk ": {
"type": "number"
}
},
"relations": {
"sector": {
"type": "belongsTo",
"model": "Sector",
"foreignKey": "sectorFk"
}
}
}