Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 1877-ticket_basic-data_update
gitea/salix/pipeline/head There was a failure building this commit Details
gitea/salix/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Alex Moreno 2023-07-25 08:48:48 +02:00
commit 694ff746df
159 changed files with 3583 additions and 1152 deletions

View File

@ -14,6 +14,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
## [2330.01] - 2023-07-27
### Added
- (Artículos -> Vista Previa) Añadido campo "Plástico reciclado"
- (Rutas -> Troncales) Nueva sección
- (Tickets -> Opciones) Opción establecer peso
- (Clientes -> SMS) Nueva sección
### Changed
- (General -> Iconos) Añadidos nuevos iconos
- (Clientes -> Razón social) Nuevas restricciones por pais
### Fixed
## [2328.01] - 2023-07-13
### Added

View File

@ -1,7 +1,5 @@
const axios = require('axios');
module.exports = Self => {
Self.remoteMethodCtx('checkFile', {
Self.remoteMethod('checkFile', {
description: 'Check if exist docuware file',
accessType: 'READ',
accepts: [
@ -17,12 +15,16 @@ module.exports = Self => {
required: true,
description: 'The fileCabinet name'
},
{
arg: 'filter',
type: 'object',
description: 'The filter'
},
{
arg: 'signed',
type: 'boolean',
required: true,
description: 'If pdf is necessary to be signed'
}
},
],
returns: {
type: 'object',
@ -34,7 +36,7 @@ module.exports = Self => {
}
});
Self.checkFile = async function(ctx, id, fileCabinet, signed) {
Self.checkFile = async function(id, fileCabinet, filter, signed) {
const models = Self.app.models;
const action = 'find';
@ -45,39 +47,34 @@ module.exports = Self => {
}
});
const searchFilter = {
condition: [
{
DBName: docuwareInfo.findById,
Value: [id]
}
],
sortOrder: [
{
Field: 'FILENAME',
Direction: 'Desc'
}
]
};
if (!filter) {
filter = {
condition: [
{
DBName: docuwareInfo.findById,
Value: [id]
}
],
sortOrder: [
{
Field: 'FILENAME',
Direction: 'Desc'
}
]
};
}
if (signed) {
filter.condition.push({
DBName: 'ESTADO',
Value: ['Firmado']
});
}
try {
const options = await Self.getOptions();
const fileCabinetId = await Self.getFileCabinet(fileCabinet);
const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId);
const response = await axios.post(
`${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`,
searchFilter,
options.headers
);
const [documents] = response.data.Items;
const response = await Self.get(fileCabinet, filter);
const [documents] = response.Items;
if (!documents) return false;
const state = documents.Fields.find(field => field.FieldName == 'ESTADO');
if (signed && state.Item != 'Firmado') return false;
return {id: documents.Id};
} catch (error) {
return false;

View File

@ -1,59 +1,6 @@
const axios = require('axios');
module.exports = Self => {
/**
* Returns the dialog id
*
* @param {string} code - The fileCabinet name
* @param {string} action - The fileCabinet name
* @param {string} fileCabinetId - Optional The fileCabinet name
* @return {number} - The fileCabinet id
*/
Self.getDialog = async(code, action, fileCabinetId) => {
const docuwareInfo = await Self.app.models.Docuware.findOne({
where: {
code: code,
action: action
}
});
if (!fileCabinetId) fileCabinetId = await Self.getFileCabinet(code);
const options = await Self.getOptions();
if (!process.env.NODE_ENV)
return Math.round();
const response = await axios.get(`${options.url}/FileCabinets/${fileCabinetId}/dialogs`, options.headers);
const dialogs = response.data.Dialog;
const dialogId = dialogs.find(dialogs => dialogs.DisplayName === docuwareInfo.dialogName).Id;
return dialogId;
};
/**
* Returns the fileCabinetId
*
* @param {string} code - The fileCabinet code
* @return {number} - The fileCabinet id
*/
Self.getFileCabinet = async code => {
const options = await Self.getOptions();
const docuwareInfo = await Self.app.models.Docuware.findOne({
where: {
code: code
}
});
if (!process.env.NODE_ENV)
return Math.round();
const fileCabinetResponse = await axios.get(`${options.url}/FileCabinets`, options.headers);
const fileCabinets = fileCabinetResponse.data.FileCabinet;
const fileCabinetId = fileCabinets.find(fileCabinet => fileCabinet.Name === docuwareInfo.fileCabinetName).Id;
return fileCabinetId;
};
/**
* Returns basic headers
*
@ -75,4 +22,139 @@ module.exports = Self => {
headers
};
};
/**
* Returns the dialog id
*
* @param {string} code - The fileCabinet name
* @param {string} action - The fileCabinet name
* @param {string} fileCabinetId - Optional The fileCabinet name
* @return {number} - The fileCabinet id
*/
Self.getDialog = async(code, action, fileCabinetId) => {
if (!process.env.NODE_ENV)
return Math.floor(Math.random() + 100);
const docuwareInfo = await Self.app.models.Docuware.findOne({
where: {
code,
action
}
});
if (!fileCabinetId) fileCabinetId = await Self.getFileCabinet(code);
const options = await Self.getOptions();
const response = await axios.get(`${options.url}/FileCabinets/${fileCabinetId}/dialogs`, options.headers);
const dialogs = response.data.Dialog;
const dialogId = dialogs.find(dialogs => dialogs.DisplayName === docuwareInfo.dialogName).Id;
return dialogId;
};
/**
* Returns the fileCabinetId
*
* @param {string} code - The fileCabinet code
* @return {number} - The fileCabinet id
*/
Self.getFileCabinet = async code => {
if (!process.env.NODE_ENV)
return Math.floor(Math.random() + 100);
const options = await Self.getOptions();
const docuwareInfo = await Self.app.models.Docuware.findOne({
where: {
code
}
});
const fileCabinetResponse = await axios.get(`${options.url}/FileCabinets`, options.headers);
const fileCabinets = fileCabinetResponse.data.FileCabinet;
const fileCabinetId = fileCabinets.find(fileCabinet => fileCabinet.Name === docuwareInfo.fileCabinetName).Id;
return fileCabinetId;
};
/**
* Returns docuware data
*
* @param {string} code - The fileCabinet code
* @param {object} filter - The filter for docuware
* @param {object} parse - The fields parsed
* @return {object} - The data
*/
Self.get = async(code, filter, parse) => {
if (!process.env.NODE_ENV) return;
const options = await Self.getOptions();
const fileCabinetId = await Self.getFileCabinet(code);
const dialogId = await Self.getDialog(code, 'find', fileCabinetId);
const data = await axios.post(
`${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`,
filter,
options.headers
);
return parser(data.data, parse);
};
/**
* Returns docuware data
*
* @param {string} code - The fileCabinet code
* @param {any} id - The id of docuware
* @param {object} parse - The fields parsed
* @return {object} - The data
*/
Self.getById = async(code, id, parse) => {
if (!process.env.NODE_ENV) return;
const docuwareInfo = await Self.app.models.Docuware.findOne({
fields: ['findById'],
where: {
code,
action: 'find'
}
});
const filter = {
condition: [
{
DBName: docuwareInfo.findById,
Value: [id]
}
]
};
return Self.get(code, filter, parse);
};
/**
* Returns docuware data filtered
*
* @param {array} data - The data
* @param {object} parse - The fields parsed
* @return {object} - The data parsed
*/
function parser(data, parse) {
if (!(data && data.Items)) return data;
const parsed = [];
for (item of data.Items) {
const itemParsed = {};
item.Fields.map(field => {
if (field.ItemElementName.includes('Date')) field.Item = toDate(field.Item);
if (!parse) return itemParsed[field.FieldLabel] = field.Item;
if (parse[field.FieldLabel])
itemParsed[parse[field.FieldLabel]] = field.Item;
});
parsed.push(itemParsed);
}
return parsed;
}
function toDate(value) {
if (!value) return;
return new Date(Number(value.substring(6, 19)));
}
};

View File

@ -3,7 +3,7 @@ const axios = require('axios');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('download', {
Self.remoteMethod('download', {
description: 'Download an docuware PDF',
accessType: 'READ',
accepts: [
@ -16,8 +16,12 @@ module.exports = Self => {
{
arg: 'fileCabinet',
type: 'string',
description: 'The file cabinet',
http: {source: 'path'}
description: 'The file cabinet'
},
{
arg: 'filter',
type: 'object',
description: 'The filter'
}
],
returns: [
@ -36,14 +40,15 @@ module.exports = Self => {
}
],
http: {
path: `/:id/download/:fileCabinet`,
path: `/:id/download`,
verb: 'GET'
}
});
Self.download = async function(ctx, id, fileCabinet) {
Self.download = async function(id, fileCabinet, filter) {
const models = Self.app.models;
const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, true);
const docuwareFile = await models.Docuware.checkFile(id, fileCabinet, filter);
if (!docuwareFile) throw new UserError('The DOCUWARE PDF document does not exists');
const fileCabinetId = await Self.getFileCabinet(fileCabinet);

View File

@ -1,57 +1,15 @@
const models = require('vn-loopback/server/server').models;
const axios = require('axios');
describe('docuware download()', () => {
const ticketId = 1;
const userId = 9;
const ctx = {
req: {
accessToken: {userId: userId},
headers: {origin: 'http://localhost:5000'},
}
};
const docuwareModel = models.Docuware;
const fileCabinetName = 'deliveryNote';
beforeAll(() => {
spyOn(docuwareModel, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
spyOn(docuwareModel, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
});
it('should return false if there are no documents', async() => {
const response = {
data: {
Items: []
}
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve({Items: []}))));
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
expect(result).toEqual(false);
});
it('should return false if the document is unsigned', async() => {
const response = {
data: {
Items: [
{
Id: 1,
Fields: [
{
FieldName: 'ESTADO',
Item: 'Unsigned'
}
]
}
]
}
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true);
expect(result).toEqual(false);
});
@ -59,23 +17,21 @@ describe('docuware download()', () => {
it('should return the document data', async() => {
const docuwareId = 1;
const response = {
data: {
Items: [
{
Id: docuwareId,
Fields: [
{
FieldName: 'ESTADO',
Item: 'Firmado'
}
]
}
]
}
Items: [
{
Id: docuwareId,
Fields: [
{
FieldName: 'ESTADO',
Item: 'Firmado'
}
]
}
]
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve(response))));
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true);
expect(result.id).toEqual(docuwareId);
});

View File

@ -0,0 +1,135 @@
const axios = require('axios');
const models = require('vn-loopback/server/server').models;
describe('Docuware core', () => {
beforeAll(() => {
process.env.NODE_ENV = 'testing';
});
afterAll(() => {
delete process.env.NODE_ENV;
});
describe('getOptions()', () => {
it('should return url and headers', async() => {
const result = await models.Docuware.getOptions();
expect(result.url).toBeDefined();
expect(result.headers).toBeDefined();
});
});
describe('getDialog()', () => {
it('should return dialogId', async() => {
const dialogs = {
data: {
Dialog: [
{
DisplayName: 'find',
Id: 'getDialogTest'
}
]
}
};
spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs)));
const result = await models.Docuware.getDialog('deliveryNote', 'find', 'randomFileCabinetId');
expect(result).toEqual('getDialogTest');
});
});
describe('getFileCabinet()', () => {
it('should return fileCabinetId', async() => {
const code = 'deliveryNote';
const docuwareInfo = await models.Docuware.findOne({
where: {
code
}
});
const dialogs = {
data: {
FileCabinet: [
{
Name: docuwareInfo.fileCabinetName,
Id: 'getFileCabinetTest'
}
]
}
};
spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs)));
const result = await models.Docuware.getFileCabinet(code);
expect(result).toEqual('getFileCabinetTest');
});
});
describe('get()', () => {
it('should return data without parse', async() => {
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
const data = {
data: {
id: 1
}
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
const result = await models.Docuware.get('deliveryNote');
expect(result.id).toEqual(1);
});
it('should return data with parse', async() => {
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
const data = {
data: {
Items: [{
Fields: [
{
ItemElementName: 'integer',
FieldLabel: 'firstRequiredField',
Item: 1
},
{
ItemElementName: 'string',
FieldLabel: 'secondRequiredField',
Item: 'myName'
},
{
ItemElementName: 'integer',
FieldLabel: 'notRequiredField',
Item: 2
}
]
}]
}
};
const parse = {
'firstRequiredField': 'id',
'secondRequiredField': 'name',
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
const [result] = await models.Docuware.get('deliveryNote', null, parse);
expect(result.id).toEqual(1);
expect(result.name).toEqual('myName');
expect(result.notRequiredField).not.toBeDefined();
});
});
describe('getById()', () => {
it('should return data', async() => {
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
const data = {
data: {
id: 1
}
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
const result = await models.Docuware.getById('deliveryNote', 1);
expect(result.id).toEqual(1);
});
});
});

View File

@ -39,7 +39,7 @@ describe('docuware download()', () => {
spyOn(docuwareModel, 'checkFile').and.returnValue({});
spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true}));
const result = await models.Docuware.download(ctx, ticketId, fileCabinetName);
const result = await models.Docuware.download(ticketId, fileCabinetName);
expect(result[1]).toEqual('application/pdf');
expect(result[2]).toEqual(`filename="${ticketId}.pdf"`);

View File

@ -18,6 +18,9 @@
},
"expired": {
"type": "date"
},
"supplierAccountFk": {
"type": "number"
}
},
"scope": {

View File

@ -22,6 +22,9 @@
},
"isUeeMember": {
"type": "boolean"
},
"isSocialNameUnique": {
"type": "boolean"
}
},
"relations": {

View File

@ -28,5 +28,12 @@
"findById": {
"type": "string"
}
},
"relations": {
"dmsType": {
"type": "belongsTo",
"model": "DmsType",
"foreignKey": "dmsTypeFk"
}
}
}

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES('Ticket', 'invoiceTickets', 'WRITE', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,8 @@
DELETE FROM `salix`.`ACL` WHERE model = 'MailAliasAccount';
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('MailAliasAccount', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
('MailAliasAccount', 'create', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('MailAliasAccount', 'deleteById', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('MailAliasAccount', 'canEditAlias', 'WRITE', 'ALLOW', 'ROLE', 'itManagement');

View File

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

View File

@ -0,0 +1,15 @@
CREATE TABLE `vn`.`clientSms` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`clientFk` int(11) NOT NULL,
`smsFk` mediumint(8) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `clientSms_FK` (`clientFk`),
KEY `clientSms_FK_1` (`smsFk`),
CONSTRAINT `clientSms_FK` FOREIGN KEY (`clientFk`) REFERENCES `client` (`id`) ON UPDATE CASCADE,
CONSTRAINT `clientSms_FK_1` FOREIGN KEY (`smsFk`) REFERENCES `sms` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('ClientSms', 'find', 'READ', 'ALLOW', 'ROLE', 'employee'),
('ClientSms', 'create', 'WRITE', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`company` MODIFY COLUMN sage200Company int(2) DEFAULT 10 NOT NULL;

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
VALUES
('Vehicle','sorted','WRITE','ALLOW','employee');

View File

@ -0,0 +1,2 @@
ALTER TABLE `vn`.`item` ADD recycledPlastic INT NULL;
ALTER TABLE `vn`.`item` ADD nonRecycledPlastic INT NULL;

View File

@ -0,0 +1,64 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemShelving_inventory`(vParkingFromFk VARCHAR(8), vParkingToFk VARCHAR(8))
BEGIN
/**
* Devuelve un listado de ubicaciones a revisar
*
* @param vParkingFromFk Parking de partida, identificador de parking
* @param vParkingToFk Parking de llegada, identificador de parking
*/
DECLARE vSectorFk INT;
DECLARE vPickingOrderFrom INT;
DECLARE vPickingOrderTo INT;
SELECT p.sectorFk, p.pickingOrder INTO vSectorFk, vPickingOrderFrom
FROM parking p
WHERE p.code = vParkingFromFk COLLATE 'utf8mb3_general_ci';
SELECT p.pickingOrder INTO vPickingOrderTo
FROM parking p
WHERE p.code = vParkingToFk COLLATE 'utf8mb3_general_ci';
CALL visible_getMisfit(vSectorFk);
SELECT ish.id,
p.pickingOrder,
p.code parking,
ish.shelvingFk,
ish.itemFk,
i.longName,
ish.visible,
p.sectorFk,
it.workerFk buyer,
CONCAT('http:',ic.url, '/catalog/1600x900/',i.image) urlImage,
ish.isChecked,
CASE
WHEN s.notPrepared > sm.parked THEN 0
WHEN sm.visible > sm.parked THEN 1
ELSE 2
END priority
FROM itemShelving ish
JOIN item i ON i.id = ish.itemFk
JOIN itemType it ON it.id = i.typeFk
JOIN tmp.stockMisfit sm ON sm.itemFk = ish.itemFk
JOIN shelving sh ON sh.code = ish.shelvingFk
JOIN parking p ON p.id = sh.parkingFk
JOIN (SELECT s.itemFk, sum(s.quantity) notPrepared
FROM sale s
JOIN ticket t ON t.id = s.ticketFk
JOIN warehouse w ON w.id = t.warehouseFk
JOIN config c ON c.mainWarehouseFk = w.id
WHERE t.shipped BETWEEN util.VN_CURDATE()
AND util.dayEnd(util.VN_CURDATE())
AND s.isPicked = FALSE
GROUP BY s.itemFk) s ON s.itemFk = i.id
JOIN hedera.imageConfig ic
WHERE p.pickingOrder BETWEEN vPickingOrderFrom AND vPickingOrderTo
AND p.sectorFk = vSectorFk
ORDER BY p.pickingOrder;
END$$
DELIMITER ;

View File

@ -0,0 +1,2 @@
ALTER TABLE `vn`.`country`
ADD COLUMN `isSocialNameUnique` tinyint(1) NOT NULL DEFAULT 1;

View File

@ -0,0 +1,8 @@
ALTER TABLE `vn`.`roadmap` COMMENT='Troncales diarios que se contratan';
ALTER TABLE `vn`.`roadmap` ADD price decimal(10,2) NULL;
ALTER TABLE `vn`.`roadmap` ADD driverName varchar(45) NULL;
ALTER TABLE `vn`.`roadmap` ADD name varchar(45) NOT NULL;
ALTER TABLE `vn`.`roadmap` CHANGE name name varchar(45) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL AFTER id;
ALTER TABLE `vn`.`roadmap` MODIFY COLUMN etd datetime NOT NULL;
ALTER TABLE `vn`.`expeditionTruck` COMMENT='Distintas paradas que hacen los trocales';

View File

@ -0,0 +1,6 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Roadmap', '*', '*', 'ALLOW', 'ROLE', 'palletizerBoss'),
('Roadmap', '*', '*', 'ALLOW', 'ROLE', 'productionBoss'),
('ExpeditionTruck', '*', '*', 'ALLOW', 'ROLE', 'palletizerBoss'),
('ExpeditionTruck', '*', '*', 'ALLOW', 'ROLE', 'productionBoss');

View File

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`)
VALUES ('Ticket','transferClient','WRITE','ALLOW','ROLE','administrative');

View File

@ -0,0 +1,10 @@
ALTER TABLE `vn`.`docuware` ADD dmsTypeFk INT(11) DEFAULT NULL NULL;
ALTER TABLE `vn`.`docuware` ADD CONSTRAINT docuware_FK FOREIGN KEY (dmsTypeFk) REFERENCES `vn`.`dmsType`(id) ON DELETE RESTRICT ON UPDATE CASCADE;
INSERT INTO `vn`.`docuware` (code, fileCabinetName, `action`, dialogName, findById, dmsTypeFk)
VALUES
('hr', 'RRHH', 'find', 'Búsqueda', 'N__DOCUMENTO', NULL); -- set dmsTypeFk 3 when deploy in production
INSERT INTO `salix`.`url` (appName, environment, url)
VALUES
('docuware', 'production', 'https://verdnatura.docuware.cloud/DocuWare/Platform/');

View File

@ -37,7 +37,7 @@ ALTER TABLE `vn`.`ticket` AUTO_INCREMENT = 1;
INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`)
VALUES
('DEFAULT_TOKEN', '1209600', util.VN_CURDATE(), 66);
('DEFAULT_TOKEN', '1209600', CURDATE(), 66);
INSERT INTO `salix`.`printConfig` (`id`, `itRecipient`, `incidencesEmail`)
VALUES
@ -188,13 +188,13 @@ INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAd
UPDATE `vn`.`sector` SET mainPrinterFk = 1 WHERE id = 1;
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`, `sectorFk`, `labelerFk`)
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`)
VALUES
(1106, 'LGN', 'David Charles', 'Haller', 1106, 19, 432978106, NULL, NULL),
(1107, 'ANT', 'Hank' , 'Pym' , 1107, 19, 432978107, NULL, NULL),
(1108, 'DCX', 'Charles' , 'Xavier', 1108, 19, 432978108, 1, NULL),
(1109, 'HLK', 'Bruce' , 'Banner', 1109, 19, 432978109, 1, NULL),
(1110, 'JJJ', 'Jessica' , 'Jones' , 1110, 19, 432978110, 2, NULL);
(1106, 'LGN', 'David Charles', 'Haller', 1106, 19, 432978106),
(1107, 'ANT', 'Hank' , 'Pym' , 1107, 19, 432978107),
(1108, 'DCX', 'Charles' , 'Xavier', 1108, 19, 432978108),
(1109, 'HLK', 'Bruce' , 'Banner', 1109, 19, 432978109),
(1110, 'JJJ', 'Jessica' , 'Jones' , 1110, 19, 432978110);
INSERT INTO `vn`.`parking` (`id`, `column`, `row`, `sectorFk`, `code`, `pickingOrder`)
VALUES
@ -2606,9 +2606,18 @@ INSERT INTO `vn`.`zoneAgencyMode`(`id`, `agencyModeFk`, `zoneFk`)
(3, 6, 5),
(4, 7, 1);
INSERT INTO `vn`.`expeditionTruck` (`id`, `eta`, `description`)
INSERT INTO `vn`.`roadmap` (`id`, `name`, `tractorPlate`, `trailerPlate`, `phone`, `supplierFk`, `etd`, `observations`, `userFk`, `price`, `driverName`)
VALUES
(1, CONCAT(YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL +3 YEAR))), 'Best truck in fleet');
(1, 'val-algemesi', 'RE-001', 'PO-001', '111111111', 1, util.VN_NOW(), 'this is test observation', 1, 15, 'Batman'),
(2, 'alg-valencia', 'RE-002', 'PO-002', '111111111', 1, util.VN_NOW(), 'test observation', 1, 20, 'Robin'),
(3, 'alz-algemesi', 'RE-003', 'PO-003', '222222222', 2, DATE_ADD(util.VN_NOW(), INTERVAL 2 DAY), 'observations...', 2, 25, 'Driverman');
INSERT INTO `vn`.`expeditionTruck` (`id`, `roadmapFk`, `warehouseFk`, `eta`, `description`, `userFk`)
VALUES
(1, 1, 1, DATE_ADD(util.VN_NOW(), INTERVAL 1 DAY), 'Best truck in fleet', 1),
(2, 1, 2, DATE_ADD(util.VN_NOW(), INTERVAL '1 2' DAY_HOUR), 'Second truck in fleet', 1),
(3, 1, 3, DATE_ADD(util.VN_NOW(), INTERVAL '1 4' DAY_HOUR), 'Third truck in fleet', 1),
(4, 2, 1, DATE_ADD(util.VN_NOW(), INTERVAL 3 DAY), 'Truck red', 1);
INSERT INTO `vn`.`expeditionPallet` (`id`, `truckFk`, `built`, `position`, `isPrint`)
VALUES
@ -2944,3 +2953,8 @@ INSERT INTO `vn`.`invoiceInSerial` (`code`, `description`, `cplusTerIdNifFk`, `t
('E', 'Midgard', 1, 'CEE'),
('R', 'Jotunheim', 1, 'NATIONAL'),
('W', 'Vanaheim', 1, 'WORLD');
INSERT INTO `hedera`.`imageConfig` (`id`, `maxSize`, `useXsendfile`, `url`)
VALUES
(1, 0, 0, 'marvel.com');

View File

@ -77831,7 +77831,7 @@ BEGIN
LEAVE cur1Loop;
END IF;
CALL zone_getLeaves2(vZoneFk, NULL, NULL);
CALL zone_getLeaves(vZoneFk, NULL, NULL, TRUE);
myLoop: LOOP
SET vGeoFk = NULL;
@ -77844,7 +77844,7 @@ BEGIN
LEAVE myLoop;
END IF;
CALL zone_getLeaves2(vZoneFk, vGeoFk, NULL);
CALL zone_getLeaves(vZoneFk, vGeoFk, NULL, TRUE);
UPDATE tmp.zoneNodes
SET isChecked = TRUE
WHERE geoFk = vGeoFk;
@ -78130,19 +78130,25 @@ DELIMITER ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `zone_getLeaves`(vSelf INT, vParentFk INT, vSearch VARCHAR(255))
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getLeaves`(
vSelf INT,
vParentFk INT,
vSearch VARCHAR(255),
vHasInsert BOOL
)
BEGIN
/**
* Devuelve las ubicaciones incluidas en la ruta y que sean hijos de parentFk.
* @param vSelf Id de la zona
* @param vParentFk Id del geo a calcular
* @param vSearch cadena a buscar
* @param vSearch Cadena a buscar
* @param vHasInsert Indica si inserta en tmp.zoneNodes
* Optional @table tmp.zoneNodes(geoFk, name, parentFk, sons, isChecked, zoneFk)
*/
DECLARE vIsNumber BOOL;
DECLARE vIsSearch BOOL DEFAULT vSearch IS NOT NULL AND vSearch != '';
DECLARE vIsSearch BOOL DEFAULT vSearch IS NOT NULL AND vSearch <> '';
DROP TEMPORARY TABLE IF EXISTS tNodes;
CREATE TEMPORARY TABLE tNodes
CREATE OR REPLACE TEMPORARY TABLE tNodes
(UNIQUE (id))
ENGINE = MEMORY
SELECT id
@ -78167,18 +78173,15 @@ BEGIN
END IF;
IF vParentFk IS NULL THEN
DROP TEMPORARY TABLE IF EXISTS tChilds;
CREATE TEMPORARY TABLE tChilds
CREATE OR REPLACE TEMPORARY TABLE tChilds
(INDEX(id))
ENGINE = MEMORY
SELECT id
FROM tNodes;
SELECT id FROM tNodes;
DROP TEMPORARY TABLE IF EXISTS tParents;
CREATE TEMPORARY TABLE tParents
CREATE OR REPLACE TEMPORARY TABLE tParents
(INDEX(id))
ENGINE = MEMORY
SELECT id
FROM zoneGeo
LIMIT 0;
SELECT id FROM zoneGeo LIMIT 0;
myLoop: LOOP
DELETE FROM tParents;
@ -78186,43 +78189,67 @@ BEGIN
SELECT parentFk id
FROM zoneGeo g
JOIN tChilds c ON c.id = g.id
WHERE g.parentFk IS NOT NULL;
WHERE g.parentFk IS NOT NULL;
INSERT IGNORE INTO tNodes
SELECT id
FROM tParents;
SELECT id FROM tParents;
IF ROW_COUNT() = 0 THEN
IF NOT ROW_COUNT() THEN
LEAVE myLoop;
END IF;
DELETE FROM tChilds;
INSERT INTO tChilds
SELECT id
FROM tParents;
SELECT id FROM tParents;
END LOOP;
DROP TEMPORARY TABLE tChilds, tParents;
END IF;
IF !vIsSearch THEN
IF NOT vIsSearch THEN
INSERT IGNORE INTO tNodes
SELECT id
FROM zoneGeo
WHERE parentFk <=> vParentFk;
END IF;
SELECT g.id,
g.name,
g.parentFk,
g.sons,
isIncluded selected
FROM zoneGeo g
JOIN tNodes n ON n.id = g.id
LEFT JOIN zoneIncluded i ON i.geoFk = g.id AND i.zoneFk = vSelf
ORDER BY `depth`, selected DESC, name;
CREATE OR REPLACE TEMPORARY TABLE tZones
SELECT g.id,
g.name,
g.parentFk,
g.sons,
NOT g.sons OR `type` = 'country' isChecked,
i.isIncluded selected,
g.`depth`,
vSelf
FROM zoneGeo g
JOIN tNodes n ON n.id = g.id
LEFT JOIN zoneIncluded i ON i.geoFk = g.id
AND i.zoneFk = vSelf
ORDER BY g.`depth`, selected DESC, g.name;
DROP TEMPORARY TABLE tNodes;
IF vHasInsert THEN
INSERT IGNORE INTO tmp.zoneNodes(geoFk, name, parentFk, sons, isChecked, zoneFk)
SELECT id,
name,
parentFk,
sons,
isChecked,
vSelf
FROM tZones
WHERE selected
OR (selected IS NULL AND vParentFk IS NOT NULL);
ELSE
SELECT id,
name,
parentFk,
sons,
selected
FROM tZones
ORDER BY `depth`, selected DESC, name;
END IF;
DROP TEMPORARY TABLE tNodes, tZones;
END ;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
@ -78540,7 +78567,7 @@ BEGIN
INDEX(geoFk))
ENGINE = MEMORY;
CALL zone_getLeaves2(vSelf, NULL , NULL);
CALL zone_getLeaves(vSelf, NULL , NULL, TRUE);
UPDATE tmp.zoneNodes zn
SET isChecked = 0
@ -78553,7 +78580,7 @@ BEGIN
WHERE NOT isChecked
LIMIT 1;
CALL zone_getLeaves2(vSelf, vGeoFk, NULL);
CALL zone_getLeaves(vSelf, vGeoFk, NULL, TRUE);
UPDATE tmp.zoneNodes
SET isChecked = TRUE
WHERE geoFk = vGeoFk;

View File

@ -66,7 +66,11 @@ export default class App {
return this.logger.$http.get('Urls/findOne', {filter})
.then(res => {
return res.data.url + route;
if (res && res.data)
return res.data.url + route;
})
.catch(() => {
this.showError('Direction not found');
});
}
}

View File

@ -4,3 +4,4 @@ Please enter your username: Por favor introduce tu nombre de usuario
It seems that the server has fall down: Parece que el servidor se ha caído, espera unos minutos e inténtalo de nuevo
Session has expired: Tu sesión ha expirado, por favor vuelve a iniciar sesión
Access denied: Acción no permitida
Direction not found: Dirección no encontrada

View File

@ -83,7 +83,6 @@ export default class Token {
this.renewPeriod = data.renewPeriod;
this.stopRenewer();
this.inservalId = setInterval(() => this.checkValidity(), data.renewInterval * 1000);
this.checkValidity();
});
}

View File

@ -1,402 +1,411 @@
@font-face {
font-family: 'salixfont';
src:
url('./salixfont.ttf?wtrl3') format('truetype'),
url('./salixfont.woff?wtrl3') format('woff'),
url('./salixfont.svg?wtrl3#salixfont') format('svg');
font-weight: normal;
font-style: normal;
}
font-family: 'salixfont';
src:
url('./salixfont.ttf?wtrl3') format('truetype'),
url('./salixfont.woff?wtrl3') format('woff'),
url('./salixfont.svg?wtrl3#salixfont') format('svg');
font-weight: normal;
font-style: normal;
}
[class^="icon-"], [class*=" icon-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'salixfont' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
[class^="icon-"], [class*=" icon-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'salixfont' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-agency-term:before {
content: "\e950";
}
.icon-defaulter:before {
content: "\e94b";
}
.icon-100:before {
content: "\e95a";
}
.icon-clientUnpaid:before {
content: "\e95b";
}
.icon-history:before {
content: "\e968";
}
.icon-Person:before {
content: "\e901";
}
.icon-accessory:before {
content: "\e90a";
}
.icon-account:before {
content: "\e92a";
}
.icon-actions:before {
content: "\e960";
}
.icon-addperson:before {
content: "\e90e";
}
.icon-agency:before {
content: "\e938";
}
.icon-albaran:before {
content: "\e94d";
}
.icon-anonymous:before {
content: "\e930";
}
.icon-apps:before {
content: "\e951";
}
.icon-artificial:before {
content: "\e90b";
}
.icon-attach:before {
content: "\e92e";
}
.icon-barcode:before {
content: "\e971";
}
.icon-basket:before {
content: "\e914";
}
.icon-basketadd:before {
content: "\e913";
}
.icon-bin:before {
content: "\e96f";
}
.icon-botanical:before {
content: "\e972";
}
.icon-bucket:before {
content: "\e97a";
}
.icon-buscaman:before {
content: "\e93b";
}
.icon-buyrequest:before {
content: "\e932";
}
.icon-calc_volum .path1:before {
content: "\e915";
}
.icon-calc_volum .path2:before {
content: "\e916";
margin-left: -1em;
}
.icon-calc_volum .path3:before {
content: "\e917";
margin-left: -1em;
}
.icon-calc_volum .path4:before {
content: "\e918";
margin-left: -1em;
}
.icon-calc_volum .path5:before {
content: "\e919";
margin-left: -1em;
}
.icon-calc_volum .path6:before {
content: "\e91a";
margin-left: -1em;
}
.icon-calendar:before {
content: "\e93d";
}
.icon-catalog:before {
content: "\e937";
}
.icon-claims:before {
content: "\e963";
}
.icon-client:before {
content: "\e928";
}
.icon-clone:before {
content: "\e973";
}
.icon-columnadd:before {
content: "\e954";
}
.icon-columndelete:before {
content: "\e953";
}
.icon-components:before {
content: "\e946";
}
.icon-consignatarios:before {
content: "\e93f";
}
.icon-control:before {
content: "\e949";
}
.icon-credit:before {
content: "\e927";
}
.icon-deletedTicket:before {
content: "\e935";
}
.icon-deleteline:before {
content: "\e955";
}
.icon-delivery:before {
content: "\e939";
}
.icon-deliveryprices:before {
content: "\e91c";
}
.icon-details:before {
content: "\e961";
}
.icon-dfiscales:before {
content: "\e984";
}
.icon-disabled:before {
content: "\e921";
}
.icon-doc:before {
content: "\e977";
}
.icon-entry:before {
content: "\e934";
}
.icon-exit:before {
content: "\e92f";
}
.icon-eye:before {
content: "\e976";
}
.icon-fixedPrice:before {
content: "\e90d";
}
.icon-flower:before {
content: "\e90c";
}
.icon-frozen:before {
content: "\e900";
}
.icon-fruit:before {
content: "\e903";
}
.icon-funeral:before {
content: "\e904";
}
.icon-greenery:before {
content: "\e907";
}
.icon-greuge:before {
content: "\e944";
}
.icon-grid:before {
content: "\e980";
}
.icon-handmade:before {
content: "\e909";
}
.icon-handmadeArtificial:before {
content: "\e902";
}
.icon-headercol:before {
content: "\e958";
}
.icon-info:before {
content: "\e952";
}
.icon-inventory:before {
content: "\e92b";
}
.icon-invoice:before {
content: "\e923";
}
.icon-invoice-in:before {
content: "\e911";
}
.icon-invoice-in-create:before {
content: "\e912";
}
.icon-invoice-out:before {
content: "\e910";
}
.icon-isTooLittle:before {
content: "\e91b";
}
.icon-item:before {
content: "\e956";
}
.icon-languaje:before {
content: "\e926";
}
.icon-lines:before {
content: "\e942";
}
.icon-linesprepaired:before {
content: "\e948";
}
.icon-logout:before {
content: "\e936";
}
.icon-mana:before {
content: "\e96a";
}
.icon-mandatory:before {
content: "\e97b";
}
.icon-net:before {
content: "\e931";
}
.icon-niche:before {
content: "\e96c";
}
.icon-no036:before {
content: "\e920";
}
.icon-noPayMethod:before {
content: "\e905";
}
.icon-notes:before {
content: "\e941";
}
.icon-noweb:before {
content: "\e91f";
}
.icon-onlinepayment:before {
content: "\e91d";
}
.icon-package:before {
content: "\e978";
}
.icon-payment:before {
content: "\e97e";
}
.icon-pbx:before {
content: "\e93c";
}
.icon-pets:before {
content: "\e947";
}
.icon-photo:before {
content: "\e924";
}
.icon-plant:before {
content: "\e908";
}
.icon-polizon:before {
content: "\e95e";
}
.icon-preserved:before {
content: "\e906";
}
.icon-recovery:before {
content: "\e97c";
}
.icon-regentry:before {
content: "\e964";
}
.icon-reserva:before {
content: "\e959";
}
.icon-revision:before {
content: "\e94a";
}
.icon-risk:before {
content: "\e91e";
}
.icon-services:before {
content: "\e94c";
}
.icon-settings:before {
content: "\e979";
}
.icon-shipment-01:before {
content: "\e929";
}
.icon-sign:before {
content: "\e95d";
}
.icon-sms:before {
content: "\e975";
}
.icon-solclaim:before {
content: "\e95f";
}
.icon-solunion:before {
content: "\e94e";
}
.icon-stowaway:before {
content: "\e94f";
}
.icon-splitline:before {
content: "\e93e";
}
.icon-splur:before {
content: "\e970";
}
.icon-supplier:before {
content: "\e925";
}
.icon-supplierfalse:before {
content: "\e90f";
}
.icon-tags:before {
content: "\e96d";
}
.icon-tax:before {
content: "\e940";
}
.icon-thermometer:before {
content: "\e933";
}
.icon-ticket:before {
content: "\e96b";
}
.icon-ticketAdd:before {
content: "\e945";
}
.icon-traceability:before {
content: "\e962";
}
.icon-transaction:before {
content: "\e966";
}
.icon-treatments:before {
content: "\e922";
}
.icon-unavailable:before {
content: "\e92c";
}
.icon-volume:before {
content: "\e96e";
}
.icon-wand:before {
content: "\e93a";
}
.icon-web:before {
content: "\e982";
}
.icon-wiki:before {
content: "\e92d";
}
.icon-worker:before {
content: "\e957";
}
.icon-zone:before {
content: "\e943";
}
.icon-trailer:before {
content: "\e967";
}
.icon-grafana:before {
content: "\e965";
}
.icon-trolley:before {
content: "\e95c";
}
.icon-agency-term:before {
content: "\e950";
}
.icon-defaulter:before {
content: "\e94b";
}
.icon-100:before {
content: "\e95a";
}
.icon-clientUnpaid:before {
content: "\e95b";
}
.icon-history:before {
content: "\e968";
}
.icon-Person:before {
content: "\e901";
}
.icon-accessory:before {
content: "\e90a";
}
.icon-account:before {
content: "\e92a";
}
.icon-actions:before {
content: "\e960";
}
.icon-addperson:before {
content: "\e90e";
}
.icon-agency:before {
content: "\e938";
}
.icon-albaran:before {
content: "\e94d";
}
.icon-anonymous:before {
content: "\e930";
}
.icon-apps:before {
content: "\e951";
}
.icon-artificial:before {
content: "\e90b";
}
.icon-attach:before {
content: "\e92e";
}
.icon-barcode:before {
content: "\e971";
}
.icon-basket:before {
content: "\e914";
}
.icon-basketadd:before {
content: "\e913";
}
.icon-bin:before {
content: "\e96f";
}
.icon-botanical:before {
content: "\e972";
}
.icon-bucket:before {
content: "\e97a";
}
.icon-buscaman:before {
content: "\e93b";
}
.icon-buyrequest:before {
content: "\e932";
}
.icon-calc_volum .path1:before {
content: "\e915";
}
.icon-calc_volum .path2:before {
content: "\e916";
margin-left: -1em;
}
.icon-calc_volum .path3:before {
content: "\e917";
margin-left: -1em;
}
.icon-calc_volum .path4:before {
content: "\e918";
margin-left: -1em;
}
.icon-calc_volum .path5:before {
content: "\e919";
margin-left: -1em;
}
.icon-calc_volum .path6:before {
content: "\e91a";
margin-left: -1em;
}
.icon-calendar:before {
content: "\e93d";
}
.icon-catalog:before {
content: "\e937";
}
.icon-claims:before {
content: "\e963";
}
.icon-client:before {
content: "\e928";
}
.icon-clone:before {
content: "\e973";
}
.icon-columnadd:before {
content: "\e954";
}
.icon-columndelete:before {
content: "\e953";
}
.icon-components:before {
content: "\e946";
}
.icon-consignatarios:before {
content: "\e93f";
}
.icon-control:before {
content: "\e949";
}
.icon-credit:before {
content: "\e927";
}
.icon-deletedTicket:before {
content: "\e935";
}
.icon-deleteline:before {
content: "\e955";
}
.icon-delivery:before {
content: "\e939";
}
.icon-deliveryprices:before {
content: "\e91c";
}
.icon-details:before {
content: "\e961";
}
.icon-dfiscales:before {
content: "\e984";
}
.icon-disabled:before {
content: "\e921";
}
.icon-doc:before {
content: "\e977";
}
.icon-entry:before {
content: "\e934";
}
.icon-exit:before {
content: "\e92f";
}
.icon-eye:before {
content: "\e976";
}
.icon-fixedPrice:before {
content: "\e90d";
}
.icon-flower:before {
content: "\e90c";
}
.icon-frozen:before {
content: "\e900";
}
.icon-fruit:before {
content: "\e903";
}
.icon-funeral:before {
content: "\e904";
}
.icon-greenery:before {
content: "\e907";
}
.icon-greuge:before {
content: "\e944";
}
.icon-grid:before {
content: "\e980";
}
.icon-handmade:before {
content: "\e909";
}
.icon-handmadeArtificial:before {
content: "\e902";
}
.icon-headercol:before {
content: "\e958";
}
.icon-info:before {
content: "\e952";
}
.icon-inventory:before {
content: "\e92b";
}
.icon-invoice:before {
content: "\e923";
}
.icon-invoice-in:before {
content: "\e911";
}
.icon-invoice-in-create:before {
content: "\e912";
}
.icon-invoice-out:before {
content: "\e910";
}
.icon-isTooLittle:before {
content: "\e91b";
}
.icon-item:before {
content: "\e956";
}
.icon-languaje:before {
content: "\e926";
}
.icon-lines:before {
content: "\e942";
}
.icon-linesprepaired:before {
content: "\e948";
}
.icon-logout:before {
content: "\e936";
}
.icon-mana:before {
content: "\e96a";
}
.icon-mandatory:before {
content: "\e97b";
}
.icon-net:before {
content: "\e931";
}
.icon-niche:before {
content: "\e96c";
}
.icon-no036:before {
content: "\e920";
}
.icon-noPayMethod:before {
content: "\e905";
}
.icon-notes:before {
content: "\e941";
}
.icon-noweb:before {
content: "\e91f";
}
.icon-onlinepayment:before {
content: "\e91d";
}
.icon-package:before {
content: "\e978";
}
.icon-payment:before {
content: "\e97e";
}
.icon-pbx:before {
content: "\e93c";
}
.icon-pets:before {
content: "\e947";
}
.icon-photo:before {
content: "\e924";
}
.icon-plant:before {
content: "\e908";
}
.icon-polizon:before {
content: "\e95e";
}
.icon-preserved:before {
content: "\e906";
}
.icon-recovery:before {
content: "\e97c";
}
.icon-regentry:before {
content: "\e964";
}
.icon-reserva:before {
content: "\e959";
}
.icon-revision:before {
content: "\e94a";
}
.icon-risk:before {
content: "\e91e";
}
.icon-services:before {
content: "\e94c";
}
.icon-settings:before {
content: "\e979";
}
.icon-shipment-01:before {
content: "\e929";
}
.icon-sign:before {
content: "\e95d";
}
.icon-sms:before {
content: "\e975";
}
.icon-solclaim:before {
content: "\e95f";
}
.icon-solunion:before {
content: "\e94e";
}
.icon-stowaway:before {
content: "\e94f";
}
.icon-splitline:before {
content: "\e93e";
}
.icon-splur:before {
content: "\e970";
}
.icon-supplier:before {
content: "\e925";
}
.icon-supplierfalse:before {
content: "\e90f";
}
.icon-tags:before {
content: "\e96d";
}
.icon-tax:before {
content: "\e940";
}
.icon-thermometer:before {
content: "\e933";
}
.icon-ticket:before {
content: "\e96b";
}
.icon-ticketAdd:before {
content: "\e945";
}
.icon-traceability:before {
content: "\e962";
}
.icon-transaction:before {
content: "\e966";
}
.icon-treatments:before {
content: "\e922";
}
.icon-unavailable:before {
content: "\e92c";
}
.icon-volume:before {
content: "\e96e";
}
.icon-wand:before {
content: "\e93a";
}
.icon-web:before {
content: "\e982";
}
.icon-wiki:before {
content: "\e92d";
}
.icon-worker:before {
content: "\e957";
}
.icon-zone:before {
content: "\e943";
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 173 KiB

View File

@ -177,5 +177,6 @@
"Mail not sent": "There has been an error sending the invoice to the client [{{clientId}}]({{{clientUrl}}}), please check the email address",
"The renew period has not been exceeded": "The renew period has not been exceeded",
"You can not use the same password": "You can not use the same password",
"Valid priorities": "Valid priorities: %d"
"Valid priorities": "Valid priorities: %d",
"Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}"
}

View File

@ -303,5 +303,9 @@
"Error when sending mail to client": "Error al enviar el correo al cliente",
"Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico",
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado",
"Valid priorities": "Prioridades válidas: %d"
"Valid priorities": "Prioridades válidas: %d",
"Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}",
"The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias",
"You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado",
"This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado"
}

View File

@ -0,0 +1,55 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.observe('before save', async ctx => {
const changes = ctx.currentInstance || ctx.instance;
await Self.hasGrant(ctx, changes.mailAlias);
});
Self.observe('before delete', async ctx => {
const mailAliasAccount = await Self.findById(ctx.where.id);
await Self.hasGrant(ctx, mailAliasAccount.mailAlias);
});
/**
* Checks if current user has
* grant to add/remove alias
*
* @param {Object} ctx - Request context
* @param {Interger} mailAlias - mailAlias id
* @return {Boolean} True for user with grant
*/
Self.hasGrant = async function(ctx, mailAlias) {
const models = Self.app.models;
const accessToken = {req: {accessToken: ctx.options.accessToken}};
const userId = accessToken.req.accessToken.userId;
const canEditAlias = await models.ACL.checkAccessAcl(accessToken, 'MailAliasAccount', 'canEditAlias', 'WRITE');
if (canEditAlias) return true;
const user = await models.VnUser.findById(userId, {fields: ['hasGrant']});
if (!user.hasGrant)
throw new UserError(`You don't have grant privilege`);
const account = await models.Account.findById(userId, {
fields: ['id'],
include: {
relation: 'aliases',
scope: {
fields: ['mailAlias']
}
}
});
const aliases = account.aliases().map(alias => alias.mailAlias);
const hasAlias = aliases.includes(mailAlias);
if (!hasAlias)
throw new UserError(`You cannot assign/remove an alias that you are not assigned to`);
return true;
};
};

View File

@ -17,9 +17,7 @@
<vn-icon-button
icon="delete"
translate-attr="{title: 'Unsubscribe'}"
ng-click="removeConfirm.show(row)"
vn-acl="itManagement"
vn-acl-action="remove">
ng-click="removeConfirm.show(row)">
</vn-icon-button>
</vn-item-section>
</vn-item>
@ -32,9 +30,7 @@
translate-attr="{title: 'Add'}"
vn-bind="+"
ng-click="$ctrl.onAddClick()"
fixed-bottom-right
vn-acl="itManagement"
vn-acl-action="remove">
fixed-bottom-right>
</vn-float-button>
<vn-dialog
vn-id="dialog"

View File

@ -1,3 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = function(Self) {
Self.remoteMethodCtx('canBeInvoiced', {
description: 'Change property isEqualizated in all client addresses',
@ -9,6 +11,12 @@ module.exports = function(Self) {
required: true,
description: 'Client id',
http: {source: 'path'}
},
{
arg: 'companyFk',
description: 'The company id',
type: 'number',
required: true
}
],
returns: {
@ -22,18 +30,29 @@ module.exports = function(Self) {
}
});
Self.canBeInvoiced = async(id, options) => {
Self.canBeInvoiced = async(id, companyFk, options) => {
const models = Self.app.models;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const client = await models.Client.findById(id, {
fields: ['id', 'isTaxDataChecked', 'hasToInvoice']
fields: ['id', 'isTaxDataChecked', 'hasToInvoice', 'payMethodFk'],
include:
{
relation: 'payMethod',
scope: {
fields: ['code']
}
}
}, myOptions);
const company = await models.Company.findById(companyFk, {fields: ['supplierAccountFk']}, myOptions);
if (client.payMethod().code === 'wireTransfer' && !company.supplierAccountFk)
throw new UserError('The company has not informed the supplier account for bank transfers');
if (client.isTaxDataChecked && client.hasToInvoice)
return true;

View File

@ -7,7 +7,7 @@ module.exports = Self => {
arg: 'id',
type: 'number',
required: true,
description: 'The ticket id',
description: 'The client id',
http: {source: 'path'}
},
{
@ -33,6 +33,12 @@ module.exports = Self => {
Self.sendSms = async(ctx, id, destination, message) => {
const models = Self.app.models;
const sms = await models.Sms.send(ctx, destination, message);
await models.ClientSms.create({
clientFk: id,
smsFk: sms.id
});
return sms;
};
};

View File

@ -4,6 +4,7 @@ const LoopBackContext = require('loopback-context');
describe('client canBeInvoiced()', () => {
const userId = 19;
const clientId = 1101;
const companyId = 442;
const activeCtx = {
accessToken: {userId: userId}
};
@ -23,7 +24,7 @@ describe('client canBeInvoiced()', () => {
const client = await models.Client.findById(clientId, null, options);
await client.updateAttribute('isTaxDataChecked', false, options);
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options);
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options);
expect(canBeInvoiced).toEqual(false);
@ -43,7 +44,7 @@ describe('client canBeInvoiced()', () => {
const client = await models.Client.findById(clientId, null, options);
await client.updateAttribute('hasToInvoice', false, options);
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options);
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options);
expect(canBeInvoiced).toEqual(false);
@ -60,7 +61,7 @@ describe('client canBeInvoiced()', () => {
try {
const options = {transaction: tx};
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options);
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options);
expect(canBeInvoiced).toEqual(true);

View File

@ -70,6 +70,7 @@ module.exports = Self => {
c.creditInsurance,
d.defaulterSinced,
cn.country,
c.countryFk,
pm.name payMethod
FROM vn.defaulter d
JOIN vn.client c ON c.id = d.clientFk

View File

@ -95,6 +95,9 @@
"ClientSample": {
"dataSource": "vn"
},
"ClientSms": {
"dataSource": "vn"
},
"Sms": {
"dataSource": "vn"
},

View File

@ -0,0 +1,26 @@
{
"name": "ClientSms",
"base": "VnModel",
"options": {
"mysql": {
"table": "clientSms"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"clientFk": {
"type": "number"
}
},
"relations": {
"sms": {
"type": "belongsTo",
"model": "Sms",
"foreignKey": "smsFk"
}
}
}

View File

@ -41,7 +41,18 @@ module.exports = Self => {
});
async function socialNameIsUnique(err, done) {
if (!this.countryFk)
return done();
const filter = {
include: {
relation: 'country',
scope: {
fields: {
isSocialNameUnique: true,
},
},
},
where: {
and: [
{socialName: this.socialName},
@ -50,9 +61,13 @@ module.exports = Self => {
]
}
};
const client = await Self.app.models.Client.findOne(filter);
if (client)
const client = await Self.app.models.Country.findById(this.countryFk, {fields: ['isSocialNameUnique']});
const existingClient = await Self.findOne(filter);
if (existingClient && (existingClient.country().isSocialNameUnique || client.isSocialNameUnique))
err();
done();
}
@ -89,7 +104,7 @@ module.exports = Self => {
};
const country = await Self.app.models.Country.findOne(filter);
const code = country ? country.code.toLowerCase() : null;
const countryCode = this.fi.toLowerCase().substring(0, 2);
const countryCode = this.fi?.toLowerCase().substring(0, 2);
if (!this.fi || !validateTin(this.fi, code) || (this.isVies && countryCode == code))
err();

View File

@ -181,6 +181,11 @@
"model": "Country",
"foreignKey": "countryFk"
},
"isSocialNameUnique": {
"type": "belongsTo",
"model": "Country",
"foreignKey": "countryFk"
},
"contactChannel": {
"type": "belongsTo",
"model": "ContactChannel",

View File

@ -33,7 +33,7 @@
"country": {
"type": "belongsTo",
"model": "Country",
"foreignKey": "country"
"foreignKey": "countryFk"
},
"payMethod": {
"type": "belongsTo",

View File

@ -68,7 +68,7 @@
</span>
</vn-td>
<vn-td number>{{::clientInforma.rating}}</vn-td>
<vn-td>{{::clientInforma.recommendedCredit | currency: 'EUR': 2}}</vn-td>
<vn-td number>{{::clientInforma.recommendedCredit | currency: 'EUR'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>

View File

@ -60,7 +60,7 @@
<th field="salesPersonFk">
<span translate>Comercial</span>
</th>
<th field="country">
<th field="countryFk">
<span translate>Country</span>
</th>
<th field="payMethod"

View File

@ -31,10 +31,11 @@ export default class Controller extends Section {
valueField: 'id',
}
}, {
field: 'country',
field: 'countryFk',
autocomplete: {
url: 'Countries',
showField: 'country',
valueField: 'country'
valueField: 'id'
}
}, {
field: 'payMethodFk',
@ -167,7 +168,7 @@ export default class Controller extends Section {
case 'amount':
case 'clientFk':
case 'workerFk':
case 'country':
case 'countryFk':
case 'payMethod':
case 'salesPersonFk':
return {[`d.${param}`]: value};

View File

@ -48,4 +48,5 @@ import './notification';
import './unpaid';
import './extended-list';
import './credit-management';
import './sms';

View File

@ -23,6 +23,7 @@
{"state": "client.card.recovery.index", "icon": "icon-recovery"},
{"state": "client.card.webAccess", "icon": "cloud"},
{"state": "client.card.log", "icon": "history"},
{"state": "client.card.sms", "icon": "sms"},
{
"description": "Credit management",
"icon": "monetization_on",
@ -373,6 +374,12 @@
"component": "vn-client-log",
"description": "Log"
},
{
"url" : "/sms",
"state": "client.card.sms",
"component": "vn-client-sms",
"description": "Sms"
},
{
"url": "/dms",
"state": "client.card.dms",

View File

@ -0,0 +1,40 @@
<vn-crud-model
vn-id="model"
url="ClientSms"
link="{clientFk: $ctrl.$params.id}"
filter="::$ctrl.filter"
data="clientSmsList"
limit="20"
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="model">
<vn-card class="vn-w-md">
<vn-table model="model" auto-load="false">
<vn-thead>
<vn-tr>
<vn-th field="senderFk">Sender</vn-th>
<vn-th field="destination" number>Destination</vn-th>
<vn-th field="message">Message</vn-th>
<vn-th field="status">Status</vn-th>
<vn-th field="created" expand>Created</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="clientSms in clientSmsList">
<vn-td>
<span class="link" ng-click="workerDescriptor.show($event, clientSms.sms.senderFk)">
{{::clientSms.sms.sender.name}}
</span>
</vn-td>
<vn-td number expand>{{::clientSms.sms.destination}}</vn-td>
<vn-td>{{::clientSms.sms.message}}</vn-td>
<vn-td>{{::clientSms.sms.status}}</vn-td>
<vn-td shrink-datetime>{{::clientSms.sms.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-worker-descriptor-popover
vn-id="worker-descriptor">
</vn-worker-descriptor-popover>

View File

@ -0,0 +1,39 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.filter = {
fields: ['id', 'smsFk'],
include: {
relation: 'sms',
scope: {
fields: [
'senderFk',
'sender',
'destination',
'message',
'statusCode',
'status',
'created'],
include: {
relation: 'sender',
scope: {
fields: ['name']
}
}
}
}
};
}
}
ngModule.vnComponent('vnClientSms', {
template: require('./index.html'),
controller: Controller,
bindings: {
client: '<'
}
});

View File

@ -0,0 +1,2 @@
Sender: Remitente
Number sender: Número remitente

View File

@ -270,7 +270,7 @@
info="Invoices minus payments plus orders not yet invoiced">
</vn-label-value>
<vn-label-value label="Credit"
value="{{$ctrl.summary.credit | currency: 'EUR':2 }} "
value="{{$ctrl.summary.credit | currency: 'EUR'}} "
ng-class="{alert: $ctrl.summary.credit > $ctrl.summary.creditInsurance ||
($ctrl.summary.credit && $ctrl.summary.creditInsurance == null)}"
info="Verdnatura's maximum risk">
@ -296,6 +296,9 @@
value="{{$ctrl.summary.rating}}"
info="Value from 1 to 20. The higher the better value">
</vn-label-value>
<vn-label-value label="Recommended credit"
value="{{$ctrl.summary.recommendedCredit | currency: 'EUR'}}">
</vn-label-value>
</vn-one>
</vn-horizontal>
<vn-horizontal>

View File

@ -8,7 +8,7 @@ vn-client-summary .summary {
}
vn-horizontal h4 .grafana:after {
content: 'contact_support';
font-size: 17px;
font-family: 'salixfont' !important;
content: "\e965";
}
}

View File

@ -9,8 +9,7 @@ vn-entry-buy-index vn-card {
}
thead tr {
border-left: 1px solid white;
border-right: 1px solid white;
border: 1px solid white;;
}
tbody tr:nth-child(1),

View File

@ -1,3 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
require('../methods/invoice-in/filter')(Self);
require('../methods/invoice-in/summary')(Self);
@ -7,4 +9,9 @@ module.exports = Self => {
require('../methods/invoice-in/invoiceInPdf')(Self);
require('../methods/invoice-in/invoiceInEmail')(Self);
require('../methods/invoice-in/getSerial')(Self);
Self.rewriteDbError(function(err) {
if (err.code === 'ER_ROW_IS_REFERENCED_2' && err.sqlMessage.includes('vehicleInvoiceIn'))
return new UserError(`This invoice has a linked vehicle.`);
return err;
});
};

View File

@ -1,5 +1,3 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('invoiceClient', {
description: 'Make a invoice of a client',
@ -56,7 +54,6 @@ module.exports = Self => {
const minShipped = Date.vnNew();
minShipped.setFullYear(args.maxShipped.getFullYear() - 1);
let invoiceId;
try {
const client = await models.Client.findById(args.clientId, {
fields: ['id', 'hasToInvoiceByAddress']
@ -77,56 +74,21 @@ module.exports = Self => {
], options);
}
// Check negative bases
let query =
`SELECT COUNT(*) isSpanishCompany
FROM supplier s
JOIN country c ON c.id = s.countryFk
AND c.code = 'ES'
WHERE s.id = ?`;
const [supplierCompany] = await Self.rawSql(query, [
args.companyFk
], options);
const isSpanishCompany = supplierCompany?.isSpanishCompany;
query = 'SELECT hasAnyNegativeBase() AS base';
const [result] = await Self.rawSql(query, null, options);
const hasAnyNegativeBase = result?.base;
if (hasAnyNegativeBase && isSpanishCompany)
throw new UserError('Negative basis');
// Invoicing
query = `SELECT invoiceSerial(?, ?, ?) AS serial`;
const [invoiceSerial] = await Self.rawSql(query, [
client.id,
const invoiceType = 'G';
const invoiceId = await models.Ticket.makeInvoice(
ctx,
invoiceType,
args.companyFk,
'G'
], options);
const serialLetter = invoiceSerial.serial;
query = `CALL invoiceOut_new(?, ?, NULL, @invoiceId)`;
await Self.rawSql(query, [
serialLetter,
args.invoiceDate
], options);
const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, options);
if (!newInvoice)
throw new UserError('No tickets to invoice', 'notInvoiced');
await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], options);
invoiceId = newInvoice.id;
args.invoiceDate,
options
);
if (tx) await tx.commit();
return invoiceId;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
return invoiceId;
};
};

View File

@ -14,8 +14,7 @@ module.exports = Self => {
}, {
arg: 'printerFk',
type: 'number',
description: 'The printer to print',
required: true
description: 'The printer to print'
}
],
http: {
@ -51,7 +50,7 @@ module.exports = Self => {
const ref = invoiceOut.ref;
const client = invoiceOut.client();
if (client.isToBeMailed) {
if (client.isToBeMailed || !printerFk) {
try {
ctx.args = {
reference: ref,

View File

@ -0,0 +1,37 @@
module.exports = Self => {
Self.remoteMethod('getInventory', {
description: 'Get list of itemShelving to review between two parking code',
accessType: 'WRITE',
accepts: [{
arg: 'parkingFrom',
type: 'string',
required: true,
description: 'Parking code from'
},
{
arg: 'parkingTo',
type: 'string',
required: true,
description: 'Parking code to'
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/getInventory`,
verb: 'POST'
}
});
Self.getInventory = async(parkingFrom, parkingTo, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const [result] = await Self.rawSql(`CALL vn.itemShelving_inventory(?, ?)`, [parkingFrom, parkingTo], myOptions);
return result;
};
};

View File

@ -0,0 +1,24 @@
const models = require('vn-loopback/server/server').models;
describe('itemShelving getInventory()', () => {
it('should return a list of itemShelvings', async() => {
const tx = await models.ItemShelving.beginTransaction({});
let response;
try {
const options = {transaction: tx};
await models.ItemShelving.rawSql(`
UPDATE vn.config
SET mainWarehouseFk=1
WHERE id=1
`, null, options);
response = await models.ItemShelving.getInventory('100-01', 'LR-02-3', options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(response.length).toEqual(2);
});
});

View File

@ -164,6 +164,8 @@ module.exports = Self => {
i.stemMultiplier,
i.typeFk,
i.isFloramondo,
i.recycledPlastic,
i.nonRecycledPlastic,
pr.name AS producer,
it.name AS typeName,
it.workerFk AS buyerFk,

View File

@ -1,3 +1,4 @@
module.exports = Self => {
require('../methods/item-shelving/deleteItemShelvings')(Self);
require('../methods/item-shelving/getInventory')(Self);
};

View File

@ -23,6 +23,9 @@
},
"isChecked": {
"type": "boolean"
},
"visible": {
"type": "number"
}
},
"relations": {

View File

@ -125,6 +125,12 @@
"minPrice": {
"type": "number"
},
"recycledPlastic": {
"type": "number"
},
"nonRecycledPlastic": {
"type": "number"
},
"packingOut": {
"type": "number"
},

View File

@ -82,6 +82,8 @@
vn-name="expence"
initial-data="$ctrl.item.expense">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
data="originsData"
label="Origin"
@ -91,21 +93,6 @@
vn-name="origin"
initial-data="$ctrl.item.origin">
</vn-autocomplete>
<vn-textfield
label="Reference"
ng-model="$ctrl.item.comment"
vn-name="comment"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
min="0"
label="Relevancy"
ng-model="$ctrl.item.relevancy"
vn-name="relevancy"
rule>
</vn-input-number>
<vn-input-number
min="0"
label="Size"
@ -113,6 +100,21 @@
vn-name="size"
rule>
</vn-input-number>
<vn-textfield
label="Reference"
ng-model="$ctrl.item.comment"
vn-name="comment"
rule>
</vn-textfield>
<vn-input-number
min="0"
label="Relevancy"
ng-model="$ctrl.item.relevancy"
vn-name="relevancy"
rule>
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
min="0"
label="stems"
@ -126,22 +128,6 @@
ng-model="$ctrl.item.stemMultiplier"
vn-name="stemMultiplier">
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
min="0"
label="Weight/Piece"
ng-model="$ctrl.item.weightByPiece"
vn-name="weightByPiece"
rule>
</vn-input-number>
<vn-input-number
min="0"
label="Units/Box"
ng-model="$ctrl.item.packingOut"
vn-name="packingOut"
rule>
</vn-input-number>
<vn-autocomplete
label="Generic"
url="Items/withName"
@ -167,6 +153,36 @@
</append>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
min="0"
label="Weight/Piece"
ng-model="$ctrl.item.weightByPiece"
vn-name="weightByPiece"
rule>
</vn-input-number>
<vn-input-number
min="0"
label="Units/Box"
ng-model="$ctrl.item.packingOut"
vn-name="packingOut"
rule>
</vn-input-number>
<vn-input-number
min="0"
label="Recycled Plastic"
ng-model="$ctrl.item.recycledPlastic"
vn-name="recycledPlastic"
rule>
</vn-input-number>
<vn-input-number
min="0"
label="Non recycled plastic"
ng-model="$ctrl.item.nonRecycledPlastic"
vn-name="nonRecycledPlastic"
rule>
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-textarea
label="Description"

View File

@ -14,3 +14,5 @@ Multiplier: Multiplicador
Generic: Genérico
This item does need a photo: Este artículo necesita una foto
Do photo: Hacer foto
Recycled Plastic: Plástico reciclado
Non recycled plastic: Plástico no reciclado

View File

@ -85,7 +85,7 @@
show-field="id"
value-field="id"
search-function="$ctrl.itemSearchFunc($search)"
on-change="$ctrl.upsertPrice(price, true)"
ng-change="$ctrl.upsertPrice(price, true)"
order="id DESC"
tabindex="1">
<tpl-item>

View File

@ -113,9 +113,21 @@
<vn-label-value label="Weight/Piece"
value="{{$ctrl.summary.item.weightByPiece}}">
</vn-label-value>
<vn-label-value label="Units/Box"
value="{{$ctrl.summary.item.packingOut}}">
</vn-label-value>
<vn-label-value label="Expense"
value="{{$ctrl.summary.item.expense.name}}">
</vn-label-value>
<vn-label-value label="Generic"
value="{{$ctrl.summary.item.genericFk}}">
</vn-label-value>
<vn-label-value label="Recycled Plastic"
value="{{$ctrl.summary.item.recycledPlastic}}">
</vn-label-value>
<vn-label-value label="Non recycled plastic"
value="{{$ctrl.summary.item.nonRecycledPlastic}}">
</vn-label-value>
</vn-one>
<vn-one name="tags">
<h4 ng-show="$ctrl.isBuyer || $ctrl.isReplenisher">

View File

@ -0,0 +1,81 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('clone', {
description: 'Clones the selected routes',
accessType: 'WRITE',
accepts: [
{
arg: 'ids',
type: ['number'],
required: true,
description: 'The routes ids to clone'
},
{
arg: 'etd',
type: 'date',
required: true,
description: 'The estimated time of departure for all roadmaps'
}
],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/clone`,
verb: 'POST'
}
});
Self.clone = async(ids, etd) => {
const tx = await Self.beginTransaction({});
try {
const models = Self.app.models;
const options = {transaction: tx};
const originalRoadmaps = await models.Roadmap.find({
where: {id: {inq: ids}},
fields: [
'id',
'name',
'tractorPlate',
'trailerPlate',
'phone',
'supplierFk',
'etd',
'observations',
'price'],
include: [{
relation: 'expeditionTruck',
scope: {
fields: ['roadmapFk', 'warehouseFk', 'eta', 'description']
}
}]
}, options);
if (ids.length != originalRoadmaps.length)
throw new UserError(`The amount of roadmaps found don't match`);
for (const roadmap of originalRoadmaps) {
roadmap.id = undefined;
roadmap.etd = etd;
const clone = await models.Roadmap.create(roadmap, options);
const expeditionTrucks = roadmap.expeditionTruck();
expeditionTrucks.map(expeditionTruck => {
expeditionTruck.roadmapFk = clone.id;
return expeditionTruck;
});
await models.ExpeditionTruck.create(expeditionTrucks, options);
}
await tx.commit();
return true;
} catch (e) {
await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,109 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('AgencyTerm filter()', () => {
const authUserId = 9;
const today = Date.vnNew();
today.setHours(2, 0, 0, 0);
it('should return all results matching the filter', async() => {
const tx = await models.AgencyTerm.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {};
const ctx = {req: {accessToken: {userId: authUserId}}};
const agencyTerms = await models.AgencyTerm.filter(ctx, filter, options);
const firstAgencyTerm = agencyTerms[0];
expect(firstAgencyTerm.routeFk).toEqual(1);
expect(agencyTerms.length).toEqual(5);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return results matching "search" searching by integer', async() => {
let ctx = {
args: {
search: 1,
}
};
let result = await app.models.AgencyTerm.filter(ctx);
expect(result.length).toEqual(1);
expect(result[0].routeFk).toEqual(1);
});
it('should return results matching "search" searching by string', async() => {
let ctx = {
args: {
search: 'Plants SL',
}
};
let result = await app.models.AgencyTerm.filter(ctx);
expect(result.length).toEqual(2);
});
it('should return results matching "from" and "to"', async() => {
const tx = await models.Buy.beginTransaction({});
const options = {transaction: tx};
try {
const from = Date.vnNew();
from.setHours(0, 0, 0, 0);
const to = Date.vnNew();
to.setHours(23, 59, 59, 999);
const ctx = {
args: {
from: from,
to: to
}
};
const results = await models.AgencyTerm.filter(ctx, options);
expect(results.length).toBe(5);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return results matching "agencyModeFk"', async() => {
let ctx = {
args: {
agencyModeFk: 1,
}
};
let result = await app.models.AgencyTerm.filter(ctx);
expect(result.length).toEqual(1);
expect(result[0].routeFk).toEqual(1);
});
it('should return results matching "agencyFk"', async() => {
let ctx = {
args: {
agencyFk: 2,
}
};
let result = await app.models.AgencyTerm.filter(ctx);
expect(result.length).toEqual(1);
expect(result[0].routeFk).toEqual(2);
});
});

View File

@ -0,0 +1,27 @@
module.exports = Self => {
Self.remoteMethod('sorted', {
description: 'Sort the vehicles by warehouse',
accessType: 'WRITE',
accepts: [{
arg: 'warehouseFk',
type: 'number'
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/sorted`,
verb: `POST`
}
});
Self.sorted = async warehouseFk => {
return Self.rawSql(`
SELECT v.id, v.warehouseFk, v.numberPlate, w.name
FROM vehicle v
JOIN warehouse w ON w.id = v.warehouseFk
ORDER BY v.warehouseFk = ? DESC, w.id, v.numberPlate ASC;
`, [warehouseFk]);
};
};

View File

@ -5,18 +5,22 @@
"AgencyTermConfig": {
"dataSource": "vn"
},
"Route": {
"DeliveryPoint": {
"dataSource": "vn"
},
"Vehicle": {
"ExpeditionTruck": {
"dataSource": "vn"
},
"Roadmap": {
"dataSource": "vn"
},
"Route": {
"dataSource": "vn"
},
"RouteLog": {
"dataSource": "vn"
},
"DeliveryPoint": {
"Vehicle": {
"dataSource": "vn"
}
}

View File

@ -0,0 +1,43 @@
{
"name": "ExpeditionTruck",
"base": "VnModel",
"options": {
"mysql": {
"table": "expeditionTruck"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"roadmapFk": {
"type": "number"
},
"warehouseFk": {
"type": "number"
},
"eta": {
"type": "date"
},
"description": {
"type": "string"
},
"userFk": {
"type": "number"
}
},
"relations": {
"roadmap": {
"type": "belongsTo",
"model": "Roadmap",
"foreignKey": "roadmapFk"
},
"warehouse": {
"type": "belongsTo",
"model": "Warehouse",
"foreignKey": "warehouseFk"
}
}
}

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/roadmap/clone')(Self);
};

View File

@ -0,0 +1,63 @@
{
"name": "Roadmap",
"base": "VnModel",
"options": {
"mysql": {
"table": "roadmap"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"name": {
"type": "string"
},
"tractorPlate": {
"type": "string"
},
"trailerPlate": {
"type": "string"
},
"phone": {
"type": "string"
},
"supplierFk": {
"type": "number"
},
"etd": {
"type": "date"
},
"observations": {
"type": "string"
},
"userFk": {
"type": "number"
},
"price": {
"type": "number"
},
"driverName": {
"type": "string"
}
},
"relations": {
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "userFk"
},
"supplier": {
"type": "belongsTo",
"model": "Supplier",
"foreignKey": "supplierFk"
},
"expeditionTruck": {
"type": "hasMany",
"model": "ExpeditionTruck",
"foreignKey": "roadmapFk"
}
}
}

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/vehicle/sorted')(Self);
};

View File

@ -23,12 +23,14 @@
</tpl-item>
</vn-autocomplete>
<vn-autocomplete
label="Vehicle"
ng-model="$ctrl.route.vehicleFk"
url="Vehicles"
data="$ctrl.vehicles"
show-field="numberPlate"
value-field="id"
label="Vehicle"
order="false"
vn-name="vehicle">
<tpl-item>{{::numberPlate}} - {{::name}}</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>

View File

@ -2,6 +2,13 @@ import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
$onInit() {
this.$http.post(`Vehicles/sorted`, {warehouseFk: this.vnConfig.warehouseFk})
.then(res => {
this.vehicles = res.data;
});
}
onSubmit() {
this.$.watcher.submit().then(() =>
this.card.reload()

View File

@ -16,3 +16,4 @@ import './agency-term/createInvoiceIn';
import './agency-term-search-panel';
import './ticket-popup';
import './sms';
import './roadmap';

View File

@ -0,0 +1,98 @@
<mg-ajax path="Roadmaps/{{patch.params.id}}" options="vnPatch"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.roadmap"
form="form"
save="patch">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield vn-focus
vn-one
label="Roadmap"
ng-model="$ctrl.roadmap.name"
rule>
</vn-textfield>
<vn-date-picker
vn-one
label="ETD date"
ng-model="$ctrl.roadmap.etd">
</vn-date-picker>
<vn-input-time
vn-one
label="ETD hour"
ng-model="$ctrl.roadmap.etd">
</vn-input-time>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Tractor plate"
ng-model="$ctrl.roadmap.tractorPlate"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Trailer plate"
ng-model="$ctrl.roadmap.trailerPlate"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
ng-model="$ctrl.roadmap.supplierFk"
url="Suppliers"
show-field="nickname"
search-function="{or: [{id: $search}, {nickname: {like: '%'+ $search +'%'}}]}"
value-field="id"
order="nickname"
label="Carrier">
<tpl-item>
{{::id}} - {{::nickname}}
</tpl-item>
</vn-autocomplete>
<vn-input-number
vn-one
label="Price"
ng-model="$ctrl.roadmap.price"
rule>
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Driver name"
ng-model="$ctrl.roadmap.driverName"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Phone"
ng-model="$ctrl.roadmap.phone"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textArea
vn-one
label="Observations"
ng-model="$ctrl.roadmap.observations"
rule>
</vn-textArea>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
<vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>

View File

@ -0,0 +1,16 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
onSubmit() {
this.$.watcher.submit();
}
}
ngModule.component('vnRoadmapBasicData', {
template: require('./index.html'),
controller: Controller,
bindings: {
roadmap: '<'
}
});

View File

@ -0,0 +1,5 @@
<vn-portal slot="menu">
<vn-roadmap-descriptor roadmap="$ctrl.roadmap"></vn-roadmap-descriptor>
<vn-left-menu source="roadmap"></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -0,0 +1,19 @@
import ngModule from '../../module';
import ModuleCard from 'salix/components/module-card';
class Controller extends ModuleCard {
reload() {
const filter = {
include: [
{relation: 'supplier'}
]
};
this.$http.get(`Roadmaps/${this.$params.id}`, {filter})
.then(res => this.roadmap = res.data);
}
}
ngModule.vnComponent('vnRoadmapCard', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,37 @@
<vn-watcher
vn-id="watcher"
url="Roadmaps"
data="$ctrl.roadmap"
insert-mode="true"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
label="Roadmap"
ng-model="$ctrl.roadmap.name"
rule>
</vn-textfield>
<vn-date-picker
label="ETD date"
ng-model="$ctrl.roadmap.etd">
</vn-date-picker>
<vn-input-time
label="ETD hour"
ng-model="$ctrl.roadmap.etd">
</vn-input-time>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Create">
</vn-submit>
<vn-button
class="cancel"
label="Cancel"
ui-sref="roadmap.index">
</vn-button>
</vn-button-bar>
</form>

View File

@ -0,0 +1,23 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $, $transclude, vnReport, vnEmail) {
super($element, $, $transclude);
this.roadmap = {etd: Date.vnNew()};
}
onSubmit() {
this.$.watcher.submit().then(
res => this.$state.go('route.roadmap.card.summary', {id: res.data.id})
);
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnRoadmapCreate', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,6 @@
vn-ticket-request {
.vn-textfield {
margin: 0!important;
max-width: 100px;
}
}

View File

@ -0,0 +1,39 @@
<vn-descriptor-content
module="route"
base-state="route.roadmap"
description="$ctrl.roadmap.name">
<slot-menu>
<vn-item
ng-click="deleteRoadmap.show()"
name="deleteRoadmap"
translate>
Delete roadmap
</vn-item>
</slot-menu>
<slot-body>
<div class="attributes">
<vn-label-value
label="Roadmap"
value="{{$ctrl.roadmap.name}}">
</vn-label-value>
<vn-label-value
label="ETD"
value="{{$ctrl.roadmap.etd | date:'dd/MM/yyyy HH:mm'}}">
</vn-label-value>
<vn-label-value label="Carrier">
<span ng-click="supplierDescriptor.show($event, $ctrl.roadmap.supplier.id)" class="link">
{{$ctrl.roadmap.supplier.nickname}}
</span>
</vn-label-value>
</div>
</slot-body>
</vn-descriptor-content>
<vn-confirm
vn-id="deleteRoadmap"
on-accept="$ctrl.onDelete()"
question="Are you sure you want to continue?"
message="The roadmap will be removed">
</vn-confirm>
<vn-supplier-descriptor-popover
vn-id="supplierDescriptor">
</vn-supplier-descriptor-popover>

View File

@ -0,0 +1,26 @@
import ngModule from '../../module';
import Descriptor from 'salix/components/descriptor';
class Controller extends Descriptor {
get roadmap() {
return this.entity;
}
set roadmap(value) {
this.entity = value;
}
onDelete() {
return this.$http.delete(`Roadmaps/${this.roadmap.id}`)
.then(() => this.$state.go('route.roadmap'))
.then(() => this.vnApp.showSuccess(this.$t('Roadmap removed')));
}
}
ngModule.component('vnRoadmapDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
roadmap: '<'
}
});

View File

@ -0,0 +1,3 @@
Delete roadmap: Eliminar troncal
The roadmap will be removed: La troncal será eliminada
Roadmap removed: Troncal eliminada

View File

@ -0,0 +1,9 @@
import './main';
import './index/';
import './summary';
import './card';
import './descriptor';
import './create';
import './basic-data';
import './search-panel';
import './stops';

View File

@ -0,0 +1,112 @@
<vn-auto-search
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
class="vn-w-lg">
<vn-card class="vn-pa-md vn-w-lg">
<vn-tool-bar class="vn-ma-md">
<vn-button
disabled="$ctrl.totalChecked == 0"
ng-click="$ctrl.openClonationDialog()"
icon="icon-clone"
vn-tooltip="Clone selected roadmaps">
</vn-button>
<vn-button
disabled="$ctrl.totalChecked == 0"
ng-click="deleteRoadmaps.show()"
vn-tooltip="Delete roadmap(s)"
icon="delete">
</vn-button>
</vn-tool-bar>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th shrink>
<vn-multi-check
model="model">
</vn-multi-check>
</vn-th>
<vn-th field="description">Roadmap</vn-th>
<vn-th field="etd" expand date>ETD</vn-th>
<vn-th field="supplierFk">Carrier</vn-th>
<vn-th field="plate">Plate</vn-th>
<vn-th field="price">Price</vn-th>
<vn-th field="observations" expand>Observations</vn-th>
<vn-th></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="roadmap in model.data"
class="clickable vn-tr search-result"
ui-sref="route.roadmap.card.summary({id: {{::roadmap.id}}})">
<vn-td>
<vn-check
ng-model="roadmap.checked"
vn-click-stop>
</vn-check>
</vn-td>
<vn-td>{{::roadmap.name}}</vn-td>
<vn-td expand date>{{::roadmap.etd | date:'dd/MM/yyyy HH:mm'}}</vn-td>
<vn-td expand>
<span
class="link"
vn-click-stop="supplierDescriptor.show($event, roadmap.supplierFk)">
{{::roadmap.supplier.nickname}}
</span>
</vn-td>
<vn-td>{{::roadmap.tractorPlate | dashIfEmpty}}</vn-td>
<vn-td expand>{{::roadmap.price | currency: 'EUR':2 | dashIfEmpty}}</vn-td>
<vn-td expand>{{::roadmap.observations | dashIfEmpty}}</vn-td>
<vn-td shrink>
<vn-icon-button
vn-click-stop="$ctrl.preview(roadmap)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<a
ui-sref="route.roadmap.create"
vn-tooltip="Create roadmap"
vn-bind="+"
fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>
<vn-popup vn-id="summary">
<vn-roadmap-summary
roadmap="$ctrl.roadmapSelected">
</vn-roadmap-summary>
</vn-popup>
<vn-supplier-descriptor-popover
vn-id="supplierDescriptor">
</vn-supplier-descriptor-popover>
<!-- Clonation dialog -->
<vn-dialog class="edit"
vn-id="clonationDialog"
on-accept="$ctrl.cloneSelectedRoadmaps()"
message="Select the estimated time of departure (ETD)">
<tpl-body>
<vn-horizontal>
<vn-date-picker
label="ETD"
ng-model="$ctrl.etd">
</vn-date-picker>
</vn-horizontal>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Clone</button>
</tpl-buttons>
</vn-dialog>
<vn-confirm
vn-id="deleteRoadmaps"
question="Are you sure you want to continue?"
message="Selected roadmaps will be removed"
on-accept="$ctrl.deleteRoadmaps()">
</vn-confirm>

View File

@ -0,0 +1,62 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
class Controller extends Section {
get checked() {
const roadmaps = this.$.model.data || [];
const checkedRoadmap = [];
for (let roadmap of roadmaps) {
if (roadmap.checked)
checkedRoadmap.push(roadmap);
}
return checkedRoadmap;
}
get totalChecked() {
return this.checked.length;
}
preview(roadmap) {
this.roadmapSelected = roadmap;
this.$.summary.show();
}
openClonationDialog() {
this.$.clonationDialog.show();
this.etd = Date.vnNew();
}
cloneSelectedRoadmaps() {
try {
if (!this.etd)
throw new Error(`The date can't be empty`);
const roadmapsIds = [];
for (let roadmap of this.checked)
roadmapsIds.push(roadmap.id);
return this.$http.post('Roadmaps/clone', {ids: roadmapsIds, etd: this.etd}).then(() => {
this.$.model.refresh();
this.vnApp.showSuccess(this.$t('Data saved!'));
});
} catch (e) {
this.vnApp.showError(this.$t(e.message));
}
}
deleteRoadmaps() {
console.log(this.checked);
for (const roadmap of this.checked) {
this.$http.delete(`Roadmaps/${roadmap.id}`)
.then(() => this.$.model.refresh())
.then(() => this.vnApp.showSuccess(this.$t('Roadmaps removed')));
}
}
}
ngModule.vnComponent('vnRoadmapIndex', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,3 @@
Delete roadmap(s): Eliminar troncal(es)
Selected roadmaps will be removed: Los troncales seleccionados serán eliminados
Roadmaps removed: Troncales eliminados

View File

@ -0,0 +1,14 @@
Roadmaps: Troncales
Roadmap: Troncal
Driver name: Nombre conductor
Plate: Matrícula
Price: Precio
Observations: Observaciones
Clone selected roadmaps: Clonar troncales seleccionadas
Select the estimated time of departure (ETD): Seleccione la hora estimada de salida (ETD)
Create roadmap: Crear troncal
Tractor plate: Matrícula tractor
Trailer plate: Matrícula trailer
Carrier: Transportista
ETD date: Fecha ETD
ETD hour: Hora ETD

View File

@ -0,0 +1,20 @@
<vn-crud-model
vn-id="model"
url="Roadmaps"
include="$ctrl.include"
auto-load="true"
limit="20">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
info="Search roadmap by id or trunk"
panel="vn-roadmap-search-panel"
model="model"
filter="$ctrl.filterParams"
expr-builder="$ctrl.exprBuilder(param, value)"
base-state="route.roadmap">
</vn-searchbar>
</vn-portal>
<ui-view>
<vn-roadmap-index></vn-roadmap-index>
</ui-view>

View File

@ -0,0 +1,61 @@
import ngModule from '../../module';
import ModuleMain from 'salix/components/module-main';
export default class Roadmap extends ModuleMain {
constructor($element, $) {
super($element, $);
this.include = {
relation: 'supplier',
scope: {
fields: ['nickname']
}
};
}
$postLink() {
const from = Date.vnNew();
from.setHours(0, 0, 0, 0);
const to = Date.vnNew();
to.setHours(23, 59, 59, 999);
this.filterParams = {
from: from,
to: to
};
this.$.model.addFilter({where: {
and: [
{etd: {gte: from}},
{etd: {lte: to}}
]
}});
}
exprBuilder(param, value) {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {id: value}
: {name: {like: `%${value}%`}};
case 'from':
return {etd: {gte: value}};
case 'to':
return {etd: {lte: value}};
case 'supplierFk':
case 'price':
return {[param]: value};
case 'tractorPlate':
case 'trailerPlate':
case 'phone':
case 'driverName':
return {[param]: {like: `%${value}%`}};
}
}
}
ngModule.vnComponent('vnRoadmap', {
controller: Roadmap,
template: require('./index.html')
});

View File

@ -0,0 +1 @@
Search roadmap by id or trunk: Buscar troncales por id o troncal

Some files were not shown because too many files have changed in this diff Show More