Merge branch 'dev' into 5425-modCamposBasicos(pesoycaja)
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Carlos Satorres 2023-03-22 13:28:07 +00:00
commit 1ab8570ed3
32 changed files with 726 additions and 411 deletions

View File

@ -5,5 +5,6 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"search.useIgnoreFiles": false
"search.useIgnoreFiles": false,
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}

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": {
"id": {
"id": true,
"type": "number",
"forceId": false
"type": "number"
},
"date": {
"created": {
"type": "date"
},
"m3":{
"longitude":{
"type": "number"
},
"warehouseFk":{
"latitude":{
"type": "number"
},
"dated":{
"type": "date"
},
"ticketFk":{
"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/updateFile')(Self);
require('../methods/dms/deleteTrashFiles')(Self);
require('../methods/dms/saveSign')(Self);
Self.checkRole = async function(ctx, id) {
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'),
(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`)
VALUES
(1, 'printer1', 'path1', 0, 1 , NULL),
@ -295,7 +299,8 @@ INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt
INSERT INTO `vn`.`payDem`(`id`, `payDem`)
VALUES
(1, 10),
(2, 20);
(2, 20),
(7, 0);
INSERT INTO `vn`.`autonomy`(`id`, `name`, `countryFk`)
VALUES

View File

@ -524,7 +524,7 @@ export default {
},
itemLog: {
anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr',
fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(4) td.after',
fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(5) td.after',
},
ticketSummary: {
header: 'vn-ticket-summary > vn-card > h5',
@ -1040,7 +1040,6 @@ export default {
boss: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.bossFk"]',
role: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.roleFk"]',
iban: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.iban"]',
switft: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.bankEntityFk"]',
createButton: 'vn-worker-create vn-submit[label="Create"]',
},
workerPda: {

View File

@ -27,7 +27,7 @@ describe('Worker time control path', () => {
date.setMonth(date.getMonth() + 1);
let month = date.toLocaleString('default', {month: 'long'});
await page.click(selectors.workerTimeControl.nextMonthButton);
await page.waitToClick(selectors.workerTimeControl.nextMonthButton);
let result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
@ -36,7 +36,7 @@ describe('Worker time control path', () => {
date.setDate(1);
month = date.toLocaleString('default', {month: 'long'});
await page.click(selectors.workerTimeControl.previousMonthButton);
await page.waitToClick(selectors.workerTimeControl.previousMonthButton);
result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
@ -49,7 +49,7 @@ describe('Worker time control path', () => {
await page.loginAndModule('salesBoss', 'worker');
await page.goto(`http://localhost:5000/#!/worker/${hankPymId}/time-control?timestamp=${timestamp}`);
await page.click(selectors.workerTimeControl.secondWeekDay);
await page.waitToClick(selectors.workerTimeControl.secondWeekDay);
result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');

View File

@ -26,7 +26,6 @@ describe('Worker create path', () => {
await page.write(selectors.workerCreate.street, 'S/ Doomstadt');
await page.write(selectors.workerCreate.email, 'doctorDoom@marvel.com');
await page.write(selectors.workerCreate.iban, 'ES9121000418450200051332');
await page.autocompleteSearch(selectors.workerCreate.switft, 'BBKKESMMMMM');
// should check for autocompleted worker code and worker user name
const workerCode = await page

View File

@ -54,7 +54,7 @@ describe('Ticket Future path', () => {
it('should search with the origin IPT', async() => {
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.ticketFuture.ipt, 'Horizontal');
await page.autocompleteSearch(selectors.ticketFuture.ipt, 'H');
await page.waitToClick(selectors.ticketFuture.submit);
expect(httpRequest).toContain('ipt=H');
@ -65,7 +65,7 @@ describe('Ticket Future path', () => {
await page.clearInput(selectors.ticketFuture.ipt);
await page.autocompleteSearch(selectors.ticketFuture.futureIpt, 'Horizontal');
await page.autocompleteSearch(selectors.ticketFuture.futureIpt, 'H');
await page.waitToClick(selectors.ticketFuture.submit);
expect(httpRequest).toContain('futureIpt=H');
@ -108,7 +108,7 @@ describe('Ticket Future path', () => {
it('should search in smart-table with an IPT Destination', async() => {
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.autocompleteSearch(selectors.ticketFuture.tableFutureIpt, 'Horizontal');
await page.autocompleteSearch(selectors.ticketFuture.tableFutureIpt, 'H');
expect(httpRequest).toContain('futureIpt');
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);

View File

@ -54,7 +54,7 @@ describe('Ticket Advance path', () => {
it('should search with the origin IPT', async() => {
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.ticketAdvance.futureIpt, 'Horizontal');
await page.autocompleteSearch(selectors.ticketAdvance.futureIpt, 'H');
await page.waitToClick(selectors.ticketAdvance.submit);
expect(httpRequest).toContain('futureIpt=H');
@ -66,7 +66,7 @@ describe('Ticket Advance path', () => {
it('should search with the destination IPT', async() => {
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.ticketAdvance.ipt, 'Horizontal');
await page.autocompleteSearch(selectors.ticketAdvance.ipt, 'H');
await page.waitToClick(selectors.ticketAdvance.submit);
expect(httpRequest).toContain('ipt=H');
@ -78,7 +78,7 @@ describe('Ticket Advance path', () => {
it('should search in smart-table with an IPT Origin', async() => {
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.autocompleteSearch(selectors.ticketAdvance.tableFutureIpt, 'Vertical');
await page.autocompleteSearch(selectors.ticketAdvance.tableFutureIpt, 'V');
expect(httpRequest).toContain('futureIpt');
@ -89,7 +89,7 @@ describe('Ticket Advance path', () => {
it('should search in smart-table with an IPT Destination', async() => {
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.autocompleteSearch(selectors.ticketAdvance.tableIpt, 'Vertical');
await page.autocompleteSearch(selectors.ticketAdvance.tableIpt, 'V');
expect(httpRequest).toContain('ipt');

View File

@ -114,12 +114,14 @@ export default class Calendar extends FormInput {
let day = date.getDate();
let wday = date.getDay();
let month = date.getMonth();
let year = date.getFullYear();
const currentDay = Date.vnNew().getDate();
const currentMonth = Date.vnNew().getMonth();
const currentYear = Date.vnNew().getFullYear();
let classes = {
today: day === currentDay && month === currentMonth,
today: day === currentDay && month === currentMonth && year === currentYear,
weekend: wday === 6 || wday === 0,
previous: month < this.month,
current: month == this.month,

View File

@ -270,5 +270,6 @@
"Warehouse inventory not set": "El almacén inventario no está establecido",
"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}}",
"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"
}

View File

@ -1,49 +0,0 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('getTransactions', {
description: 'Returns last entries',
accessType: 'READ',
accepts: [{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/getTransactions`,
verb: 'GET'
}
});
Self.getTransactions = async(filter, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const conn = Self.dataSource.connector;
const stmt = new ParameterizedSQL(`
SELECT
t.id,
t.clientFk,
t.created,
t.amount / 100 amount,
t.receiptFk IS NOT NULL AS isConfirmed,
tt.message responseMessage,
te.message errorMessage
FROM hedera.tpvTransaction t
JOIN hedera.tpvMerchant m ON m.id = t.merchantFk
LEFT JOIN hedera.tpvResponse tt ON tt.id = t.response
LEFT JOIN hedera.tpvError te ON te.code = errorCode`);
stmt.merge(conn.makeSuffix(filter, 't'));
return Self.rawStmt(stmt, myOptions);
};
};

View File

@ -1,21 +0,0 @@
const models = require('vn-loopback/server/server').models;
describe('Client getTransations', () => {
it('should call getTransations() method to receive a list of Web Payments from Bruce Wayne', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {where: {clientFk: 1101}};
const result = await models.Client.getTransactions(filter, options);
expect(result[1].id).toBeTruthy();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,66 @@
const models = require('vn-loopback/server/server').models;
describe('Client transactions', () => {
it('should call transactions() method to receive a list of Web Payments from Bruce Wayne', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {};
const filter = {where: {clientFk: 1101}};
const result = await models.Client.transactions(ctx, filter, options);
expect(result[1].id).toBeTruthy();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should call transactions() method filtering by orderFk', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {args: {orderFk: 6}};
const filter = {};
const result = await models.Client.transactions(ctx, filter, options);
const firstRow = result[0];
expect(result.length).toEqual(1);
expect(firstRow.id).toEqual(6);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should call transactions() method filtering by amount', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {args: {amount: 40}};
const filter = {};
const result = await models.Client.transactions(ctx, filter, options);
const randomIndex = Math.floor(Math.random() * result.length);
const transaction = result[randomIndex];
expect(transaction.amount).toEqual(40);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,84 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const buildFilter = require('vn-loopback/util/filter').buildFilter;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethodCtx('transactions', {
description: 'Returns customer transactions',
accessType: 'READ',
accepts: [
{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
},
{
arg: 'orderFk',
type: 'number',
http: {source: 'query'}
},
{
arg: 'clientFk',
type: 'number',
http: {source: 'query'}
},
{
arg: 'amount',
type: 'number',
http: {source: 'query'}
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/transactions`,
verb: 'GET'
}
});
Self.transactions = async(ctx, filter, options) => {
const args = ctx.args;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const where = buildFilter(args, (param, value) => {
switch (param) {
case 'orderFk':
return {'t.id': value};
case 'clientFk':
return {'t.clientFk': value};
case 'amount':
return {'t.amount': (value * 100)};
}
});
filter = mergeFilters(filter, {where});
const conn = Self.dataSource.connector;
const stmt = new ParameterizedSQL(`
SELECT
t.id,
t.clientFk,
c.name AS customerName,
t.created,
t.amount / 100 amount,
t.receiptFk IS NOT NULL AS isConfirmed,
tt.message responseMessage,
te.message errorMessage
FROM hedera.tpvTransaction t
JOIN client c ON c.id = t.clientFk
JOIN hedera.tpvMerchant m ON m.id = t.merchantFk
LEFT JOIN hedera.tpvResponse tt ON tt.id = t.response
LEFT JOIN hedera.tpvError te ON te.code = errorCode`);
stmt.merge(conn.makeSuffix(filter, 't'));
return Self.rawStmt(stmt, myOptions);
};
};

View File

@ -13,7 +13,7 @@ module.exports = Self => {
require('../methods/client/getCard')(Self);
require('../methods/client/getDebt')(Self);
require('../methods/client/getMana')(Self);
require('../methods/client/getTransactions')(Self);
require('../methods/client/transactions')(Self);
require('../methods/client/hasCustomerRole')(Self);
require('../methods/client/isValidClient')(Self);
require('../methods/client/lastActiveTickets')(Self);

View File

@ -1,55 +1,39 @@
<vn-crud-model
vn-id="model"
url="clients/getTransactions"
link="{clientFk: $ctrl.$params.id}"
limit="20"
data="transactions"
order="created DESC"
auto-load="true">
<vn-crud-model vn-id="model" url="clients/transactions" link="{clientFk: $ctrl.$params.id}" limit="20"
data="transactions" order="created DESC" auto-load="true">
</vn-crud-model>
<vn-data-viewer
model="model"
class="vn-w-md">
<vn-data-viewer model="model" class="vn-w-md">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th shrink>State</vn-th>
<vn-th field="id" number>Id</vn-th>
<vn-th field="created" expand>Date</vn-th>
<vn-th field="amount" number>Amount</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="transaction in transactions">
<vn-td shrink>
<vn-icon
vn-tooltip="{{::$ctrl.getFormattedMessage(transaction)}}"
ng-show="::((transaction.errorMessage || transaction.responseMessage) && !transaction.isConfirmed)"
icon="clear">
</vn-icon>
<vn-icon
vn-tooltip="Confirmed"
ng-show="::(transaction.isConfirmed)"
icon="check">
</vn-icon>
</vn-td>
<vn-td number>{{::transaction.id}}</vn-td>
<vn-td>{{::transaction.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
<vn-td number>{{::transaction.amount | currency: 'EUR':2}}</vn-td>
<vn-td shrink>
<vn-icon-button
icon="done_all"
vn-acl="administrative"
vn-acl-action="remove"
translate-attr="{title: 'Confirm transaction'}"
ng-show="::!transaction.isConfirmed"
ng-click="$ctrl.confirm(transaction)">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th shrink>State</vn-th>
<vn-th field="id" number>Id</vn-th>
<vn-th field="created" expand>Date</vn-th>
<vn-th field="amount" number>Amount</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="transaction in transactions">
<vn-td shrink>
<vn-icon vn-tooltip="{{::$ctrl.getFormattedMessage(transaction)}}"
ng-show="::((transaction.errorMessage || transaction.responseMessage) && !transaction.isConfirmed)"
icon="clear">
</vn-icon>
<vn-icon vn-tooltip="Confirmed" ng-show="::(transaction.isConfirmed)" icon="check">
</vn-icon>
</vn-td>
<vn-td number>{{::transaction.id}}</vn-td>
<vn-td>{{::transaction.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
<vn-td number>{{::transaction.amount | currency: 'EUR':2}}</vn-td>
<vn-td shrink>
<vn-icon-button icon="done_all" vn-acl="administrative" vn-acl-action="remove"
translate-attr="{title: 'Confirm transaction'}" ng-show="::!transaction.isConfirmed"
ng-click="$ctrl.confirm(transaction)">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>

View File

@ -86,11 +86,11 @@
class="expendable">
<span
vn-tooltip="
{{::$ctrl.$t('Cost')}}: {{::entry.buyingValue | currency: 'EUR':2 | dashIfEmpty}}<br>
{{::$ctrl.$t('Package')}}: {{::entry.packageValue | currency: 'EUR':2 | dashIfEmpty}}<br>
{{::$ctrl.$t('Freight')}}: {{::entry.freightValue | currency: 'EUR':2 | dashIfEmpty}}<br>
{{::$ctrl.$t('Comission')}}: {{::entry.comissionValue | currency: 'EUR':2 | dashIfEmpty}}">
{{::entry.cost | currency: 'EUR':2 | dashIfEmpty}}
{{::$ctrl.$t('Cost')}}: {{::entry.buyingValue | currency: 'EUR':3 | dashIfEmpty}}<br>
{{::$ctrl.$t('Package')}}: {{::entry.packageValue | currency: 'EUR':3 | dashIfEmpty}}<br>
{{::$ctrl.$t('Freight')}}: {{::entry.freightValue | currency: 'EUR':3 | dashIfEmpty}}<br>
{{::$ctrl.$t('Comission')}}: {{::entry.comissionValue | currency: 'EUR':3 | dashIfEmpty}}">
{{::entry.cost | currency: 'EUR':3 | dashIfEmpty}}
</span>
</vn-td>
<vn-td number>{{::entry.weight | dashIfEmpty}}</vn-td>

View File

@ -1,11 +1,10 @@
module.exports = Self => {
Self.remoteMethod('newSupplier', {
Self.remoteMethodCtx('newSupplier', {
description: 'Creates a new supplier and returns it',
accessType: 'WRITE',
accepts: [{
arg: 'params',
type: 'object',
http: {source: 'body'}
arg: 'name',
type: 'string'
}],
returns: {
type: 'string',
@ -17,29 +16,18 @@ module.exports = Self => {
}
});
Self.newSupplier = async params => {
Self.newSupplier = async(ctx, options) => {
const models = Self.app.models;
const args = ctx.args;
const myOptions = {};
if (typeof(params) == 'string')
params = JSON.parse(params);
if (typeof options == 'object')
Object.assign(myOptions, options);
params.nickname = params.name;
delete args.ctx;
const data = {...args, ...{nickname: args.name}};
const supplier = await models.Supplier.create(data, myOptions);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const supplier = await models.Supplier.create(params, myOptions);
if (tx) await tx.commit();
return supplier;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
return supplier;
};
};

View File

@ -1,31 +1,39 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('Supplier newSupplier()', () => {
const newSupp = {
name: 'TestSupplier-1'
};
const administrativeId = 5;
const activeCtx = {
accessToken: {userId: administrativeId},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
const ctx = {req: activeCtx};
it('should create a new supplier containing only the name', async() => {
pending('https://redmine.verdnatura.es/issues/5203');
const activeCtx = {
accessToken: {userId: administrativeId},
};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
let result = await app.models.Supplier.newSupplier(JSON.stringify(newSupp));
it('should create a new supplier containing only the name', async() => {
const tx = await models.Supplier.beginTransaction({});
expect(result.name).toEqual('TestSupplier-1');
expect(result.id).toEqual(443);
try {
const options = {transaction: tx};
ctx.args = {
name: 'newSupplier'
};
const createdSupplier = await app.models.Supplier.findById(result.id);
const result = await models.Supplier.newSupplier(ctx, options);
expect(createdSupplier.id).toEqual(result.id);
expect(createdSupplier.name).toEqual(result.name);
expect(createdSupplier.payDemFk).toEqual(7);
expect(createdSupplier.nickname).toEqual(result.name);
expect(result.name).toEqual('newSupplier');
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,7 +1,8 @@
<vn-watcher
vn-id="watcher"
url="suppliers/newSupplier"
url="Suppliers/newSupplier"
data="$ctrl.supplier"
params="$ctrl.supplier"
insert-mode="true"
form="form">
</vn-watcher>
@ -9,8 +10,8 @@
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
label="Supplier name"
ng-model="$ctrl.supplier.name"
label="Supplier name"
ng-model="$ctrl.supplier.name"
vn-focus>
</vn-textfield>
</vn-horizontal>

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/collectionLabel')(Self);
require('../methods/ticket/expeditionPalletLabel')(Self);
require('../methods/ticket/saveSign')(Self);
};

View File

@ -36,7 +36,7 @@
"type": "number"
},
"updated": {
"type": "date",
"type": "date",
"mysql": {
"columnName": "created"
}
@ -44,6 +44,9 @@
"isDeleted": {
"type": "boolean"
},
"isSigned": {
"type": "boolean"
},
"priority": {
"type": "number"
},
@ -136,4 +139,4 @@
"foreignKey": "zoneFk"
}
}
}
}

View File

@ -94,6 +94,9 @@
},
"WorkerTimeControlMail": {
"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"
}
}
}