6745-2404_testToMaster #1950
|
@ -0,0 +1,68 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('addAlias', {
|
||||
description: 'Add an alias if the user has the grant',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'ctx',
|
||||
type: 'Object',
|
||||
http: {source: 'context'}
|
||||
},
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'The user id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'mailAlias',
|
||||
type: 'number',
|
||||
description: 'The new alias for user',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: `/:id/addAlias`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.addAlias = async function(ctx, id, mailAlias, options) {
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions);
|
||||
|
||||
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']
|
||||
}
|
||||
}
|
||||
}, myOptions);
|
||||
|
||||
const aliases = account.aliases().map(alias => alias.mailAlias);
|
||||
|
||||
const hasAlias = aliases.includes(mailAlias);
|
||||
if (!hasAlias)
|
||||
throw new UserError(`You cannot assign an alias that you are not assigned to`);
|
||||
|
||||
return models.MailAliasAccount.create({
|
||||
mailAlias: mailAlias,
|
||||
account: id
|
||||
}, myOptions);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,55 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('removeAlias', {
|
||||
description: 'Remove alias if the user has the grant',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'ctx',
|
||||
type: 'Object',
|
||||
http: {source: 'context'}
|
||||
},
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'The user id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'mailAlias',
|
||||
type: 'number',
|
||||
description: 'The alias to delete',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: `/:id/removeAlias`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.removeAlias = async function(ctx, id, mailAlias, options) {
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const canRemoveAlias = await models.ACL.checkAccessAcl(ctx, 'VnUser', 'canRemoveAlias', 'WRITE');
|
||||
|
||||
if (userId != id && !canRemoveAlias) throw new UserError(`You don't have grant privilege`);
|
||||
|
||||
const mailAliasAccount = await models.MailAliasAccount.findOne({
|
||||
where: {
|
||||
mailAlias: mailAlias,
|
||||
account: id
|
||||
}
|
||||
}, myOptions);
|
||||
|
||||
await mailAliasAccount.destroy(myOptions);
|
||||
};
|
||||
};
|
|
@ -12,6 +12,8 @@ module.exports = function(Self) {
|
|||
require('../methods/vn-user/privileges')(Self);
|
||||
require('../methods/vn-user/validate-auth')(Self);
|
||||
require('../methods/vn-user/renew-token')(Self);
|
||||
require('../methods/vn-user/addAlias')(Self);
|
||||
require('../methods/vn-user/removeAlias')(Self);
|
||||
|
||||
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
|
||||
VALUES
|
||||
('Vehicle','sorted','WRITE','ALLOW','employee');
|
|
@ -0,0 +1,11 @@
|
|||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('VnUser', 'addAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee');
|
||||
|
||||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('VnUser', 'removeAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee');
|
||||
|
||||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('VnUser', 'canRemoveAlias', 'WRITE', 'ALLOW', 'ROLE', 'itManagement');
|
|
@ -0,0 +1,2 @@
|
|||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES('Ticket', 'invoiceTickets', 'WRITE', 'ALLOW', 'ROLE', 'employee');
|
|
@ -0,0 +1,10 @@
|
|||
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';
|
||||
ALTER TABLE `vn`.`expeditionTruck` DROP FOREIGN KEY expeditionTruck_FK_2;
|
||||
ALTER TABLE `vn`.`expeditionTruck` ADD CONSTRAINT expeditionTruck_FK_2 FOREIGN KEY (roadmapFk) REFERENCES vn.roadmap(id) ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -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');
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE `vn`.`item` ADD recycledPlastic INT NULL;
|
||||
ALTER TABLE `vn`.`item` ADD nonRecycledPlastic INT NULL;
|
|
@ -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
|
||||
|
|
|
@ -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,55 +78130,58 @@ 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))
|
||||
BEGIN
|
||||
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
|
||||
FROM zoneGeo
|
||||
SELECT id
|
||||
FROM zoneGeo
|
||||
LIMIT 0;
|
||||
|
||||
IF vIsSearch THEN
|
||||
SET vIsNumber = vSearch REGEXP '^[0-9]+$';
|
||||
|
||||
|
||||
INSERT INTO tNodes
|
||||
SELECT id
|
||||
SELECT id
|
||||
FROM zoneGeo
|
||||
WHERE (vIsNumber AND `name` = vSearch)
|
||||
OR (!vIsNumber AND `name` LIKE CONCAT('%', vSearch, '%'))
|
||||
LIMIT 1000;
|
||||
|
||||
|
||||
ELSEIF vParentFk IS NULL THEN
|
||||
INSERT INTO tNodes
|
||||
SELECT geoFk
|
||||
SELECT geoFk
|
||||
FROM zoneIncluded
|
||||
WHERE zoneFk = vSelf;
|
||||
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;
|
||||
|
||||
IF ROW_COUNT() = 0 THEN
|
||||
SELECT id FROM tParents;
|
||||
|
||||
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
|
||||
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;
|
||||
|
|
|
@ -83,7 +83,6 @@ export default class Token {
|
|||
this.renewPeriod = data.renewPeriod;
|
||||
this.stopRenewer();
|
||||
this.inservalId = setInterval(() => this.checkValidity(), data.renewInterval * 1000);
|
||||
this.checkValidity();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 173 KiB |
Binary file not shown.
Binary file not shown.
|
@ -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}}"
|
||||
}
|
||||
|
|
|
@ -303,5 +303,7 @@
|
|||
"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}}",
|
||||
"You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -21,12 +21,11 @@ export default class Controller extends Section {
|
|||
}
|
||||
|
||||
onAddClick() {
|
||||
this.addData = {account: this.$params.id};
|
||||
this.$.dialog.show();
|
||||
}
|
||||
|
||||
onAddSave() {
|
||||
return this.$http.post(`MailAliasAccounts`, this.addData)
|
||||
return this.$http.post(`VnUsers/${this.$params.id}/addAlias`, this.addData)
|
||||
.then(() => this.refresh())
|
||||
.then(() => this.vnApp.showSuccess(
|
||||
this.$t('Subscribed to alias!'))
|
||||
|
@ -34,11 +33,12 @@ export default class Controller extends Section {
|
|||
}
|
||||
|
||||
onRemove(row) {
|
||||
return this.$http.delete(`MailAliasAccounts/${row.id}`)
|
||||
.then(() => {
|
||||
this.$.data.splice(this.$.data.indexOf(row), 1);
|
||||
this.vnApp.showSuccess(this.$t('Unsubscribed from alias!'));
|
||||
});
|
||||
const params = {
|
||||
mailAlias: row.mailAlias
|
||||
};
|
||||
return this.$http.post(`VnUsers/${this.$params.id}/removeAlias`, params)
|
||||
.then(() => this.refresh())
|
||||
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,8 +25,9 @@ describe('component vnUserAliases', () => {
|
|||
describe('onAddSave()', () => {
|
||||
it('should add the new row', () => {
|
||||
controller.addData = {account: 1};
|
||||
controller.$params = {id: 1};
|
||||
|
||||
$httpBackend.expectPOST('MailAliasAccounts').respond();
|
||||
$httpBackend.expectPOST('VnUsers/1/addAlias').respond();
|
||||
$httpBackend.expectGET('MailAliasAccounts').respond('foo');
|
||||
controller.onAddSave();
|
||||
$httpBackend.flush();
|
||||
|
@ -41,12 +42,14 @@ describe('component vnUserAliases', () => {
|
|||
{id: 1, alias: 'foo'},
|
||||
{id: 2, alias: 'bar'}
|
||||
];
|
||||
controller.$params = {id: 1};
|
||||
|
||||
$httpBackend.expectDELETE('MailAliasAccounts/1').respond();
|
||||
$httpBackend.expectPOST('VnUsers/1/removeAlias').respond();
|
||||
$httpBackend.expectGET('MailAliasAccounts').respond(controller.$.data[1]);
|
||||
controller.onRemove(controller.$.data[0]);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.$.data).toEqual([{id: 2, alias: 'bar'}]);
|
||||
expect(controller.$.data).toEqual({id: 2, alias: 'bar'});
|
||||
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -89,7 +89,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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
<vn-label-value label="Channel"
|
||||
value="{{$ctrl.summary.contactChannel.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Business type"
|
||||
<vn-label-value label="Business type"
|
||||
value="{{$ctrl.summary.businessType.description}}">
|
||||
</vn-label-value>
|
||||
</vn-one>
|
||||
|
@ -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>
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,13 @@
|
|||
|
||||
vn-entry-buy-index vn-card {
|
||||
max-width: $width-xl;
|
||||
|
||||
|
||||
.dark-row {
|
||||
background-color: lighten($color-marginal, 10%);
|
||||
}
|
||||
|
||||
thead tr {
|
||||
border-left: 1px solid white;
|
||||
border-right: 1px solid white;
|
||||
border: 1px solid white;;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(1),
|
||||
|
@ -22,7 +21,7 @@ vn-entry-buy-index vn-card {
|
|||
tbody tr:nth-child(2) {
|
||||
border-bottom: 1px solid $color-spacer;
|
||||
}
|
||||
|
||||
|
||||
tbody{
|
||||
border-bottom: 1px solid $color-spacer;
|
||||
}
|
||||
|
@ -40,4 +39,4 @@ vn-entry-buy-index vn-card {
|
|||
}
|
||||
}
|
||||
|
||||
$color-font-link-medium: lighten($color-font-link, 20%)
|
||||
$color-font-link-medium: lighten($color-font-link, 20%)
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -145,7 +145,7 @@ module.exports = Self => {
|
|||
|
||||
const stmts = [];
|
||||
const stmt = new ParameterizedSQL(
|
||||
`SELECT
|
||||
`SELECT
|
||||
i.id,
|
||||
i.image,
|
||||
i.name,
|
||||
|
@ -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,
|
||||
|
|
|
@ -125,6 +125,12 @@
|
|||
"minPrice": {
|
||||
"type": "number"
|
||||
},
|
||||
"recycledPlastic": {
|
||||
"type": "number"
|
||||
},
|
||||
"nonRecycledPlastic": {
|
||||
"type": "number"
|
||||
},
|
||||
"packingOut": {
|
||||
"type": "number"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Reference: Referencia
|
||||
Full name calculates based on tags 1-3. Is not recommended to change it manually: >-
|
||||
El nombre completo se calcula
|
||||
basado en los tags 1-3.
|
||||
Full name calculates based on tags 1-3. Is not recommended to change it manually: >-
|
||||
El nombre completo se calcula
|
||||
basado en los tags 1-3.
|
||||
No se recomienda cambiarlo manualmente
|
||||
Is active: Activo
|
||||
Expense: Gasto
|
||||
|
@ -13,4 +13,6 @@ Is shown at website, app that this item cannot travel (wreath, palms, ...): Se m
|
|||
Multiplier: Multiplicador
|
||||
Generic: Genérico
|
||||
This item does need a photo: Este artículo necesita una foto
|
||||
Do photo: Hacer foto
|
||||
Do photo: Hacer foto
|
||||
Recycled Plastic: Plástico reciclado
|
||||
Non recycled plastic: Plástico no reciclado
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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]);
|
||||
};
|
||||
};
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/roadmap/clone')(Self);
|
||||
};
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/vehicle/sorted')(Self);
|
||||
};
|
|
@ -23,19 +23,21 @@
|
|||
</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>
|
||||
<vn-date-picker
|
||||
label="Created"
|
||||
ng-model="$ctrl.route.created"
|
||||
vn-name="created">
|
||||
vn-name="created">
|
||||
</vn-date-picker>
|
||||
<vn-autocomplete
|
||||
ng-model="$ctrl.route.agencyModeFk"
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -16,3 +16,4 @@ import './agency-term/createInvoiceIn';
|
|||
import './agency-term-search-panel';
|
||||
import './ticket-popup';
|
||||
import './sms';
|
||||
import './roadmap';
|
||||
|
|
|
@ -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>
|
|
@ -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: '<'
|
||||
}
|
||||
});
|
|
@ -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>
|
|
@ -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
|
||||
});
|
|
@ -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>
|
|
@ -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
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
vn-ticket-request {
|
||||
.vn-textfield {
|
||||
margin: 0!important;
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
Delete roadmap: Eliminar troncal
|
||||
The roadmap will be removed: La troncal será eliminada
|
||||
Roadmap removed: Troncal eliminada
|
|
@ -0,0 +1,9 @@
|
|||
import './main';
|
||||
import './index/';
|
||||
import './summary';
|
||||
import './card';
|
||||
import './descriptor';
|
||||
import './create';
|
||||
import './basic-data';
|
||||
import './search-panel';
|
||||
import './stops';
|
|
@ -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>
|
|
@ -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
|
||||
});
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
|
@ -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')
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
Search roadmap by id or trunk: Buscar troncales por id o troncal
|
|
@ -0,0 +1,74 @@
|
|||
<div class="search-panel">
|
||||
<form id="manifold-form" ng-submit="$ctrl.onSearch()">
|
||||
<vn-horizontal class="vn-px-lg vn-pt-lg">
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="General search"
|
||||
ng-model="filter.search"
|
||||
info="Search routes by id"
|
||||
vn-focus>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<section class="vn-px-md">
|
||||
<vn-horizontal class="manifold-panel vn-pa-md">
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="From"
|
||||
ng-model="filter.from">
|
||||
</vn-date-picker>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="To"
|
||||
ng-model="filter.to">
|
||||
</vn-date-picker>
|
||||
</vn-horizontal>
|
||||
</section>
|
||||
<vn-horizontal class="vn-px-lg">
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Tractor plate"
|
||||
ng-model="filter.tractorPlate">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Trailer plate"
|
||||
ng-model="filter.trailerPlate">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-px-lg">
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
ng-model="filter.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="filter.price">
|
||||
</vn-input-number>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-px-lg">
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Driver name"
|
||||
ng-model="filter.driverName">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Phone"
|
||||
ng-model="filter.phone">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-px-lg vn-pb-lg vn-mt-lg">
|
||||
<vn-submit label="Search"></vn-submit>
|
||||
</vn-horizontal>
|
||||
</form>
|
||||
</div>
|
|
@ -0,0 +1,7 @@
|
|||
import ngModule from '../../module';
|
||||
import SearchPanel from 'core/components/searchbar/search-panel';
|
||||
|
||||
ngModule.component('vnRoadmapSearchPanel', {
|
||||
template: require('./index.html'),
|
||||
controller: SearchPanel
|
||||
});
|
|
@ -0,0 +1,71 @@
|
|||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="ExpeditionTrucks"
|
||||
where="{roadmapFk: $ctrl.$params.id}"
|
||||
order="eta ASC"
|
||||
data="$ctrl.expeditionTrucks"
|
||||
auto-load="true">
|
||||
</vn-crud-model>
|
||||
<vn-watcher
|
||||
vn-id="watcher"
|
||||
data="$ctrl.expeditionTrucks"
|
||||
form="form">
|
||||
</vn-watcher>
|
||||
<form class="vn-w-md" name="form" ng-submit="$ctrl.onSubmit()">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-horizontal ng-repeat="expeditionTruck in $ctrl.expeditionTrucks">
|
||||
<vn-autocomplete vn-one
|
||||
label="Warehouse"
|
||||
ng-model="expeditionTruck.warehouseFk"
|
||||
url="Warehouses"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
vn-focus
|
||||
rule>
|
||||
</vn-autocomplete>
|
||||
<vn-date-picker vn-one
|
||||
label="ETA date"
|
||||
ng-model="expeditionTruck.eta"
|
||||
rule>
|
||||
</vn-date-picker>
|
||||
<vn-input-time
|
||||
vn-one
|
||||
label="ETA hour"
|
||||
ng-model="expeditionTruck.eta">
|
||||
</vn-input-time>
|
||||
<vn-textArea
|
||||
vn-one
|
||||
label="Description"
|
||||
ng-model="expeditionTruck.description"
|
||||
rule>
|
||||
</vn-textArea>
|
||||
<vn-none>
|
||||
<vn-icon-button
|
||||
vn-tooltip="Remove stop"
|
||||
icon="delete"
|
||||
ng-click="model.remove($index)"
|
||||
tabindex="-1">
|
||||
</vn-icon-button>
|
||||
</vn-none>
|
||||
</vn-horizontal>
|
||||
<vn-one>
|
||||
<vn-icon-button
|
||||
vn-bind="+"
|
||||
vn-tooltip="Add stop"
|
||||
icon="add_circle"
|
||||
ng-click="$ctrl.add()">
|
||||
</vn-icon-button>
|
||||
</vn-one>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
<vn-submit
|
||||
disabled="!watcher.dataChanged()"
|
||||
label="Save">
|
||||
</vn-submit>
|
||||
</vn-button-bar>
|
||||
</form>
|
||||
<vn-confirm
|
||||
vn-id="confirm"
|
||||
question="Delete stop?"
|
||||
on-accept="$ctrl.removeTicketFromRoute($index)">
|
||||
</vn-confirm>
|
|
@ -0,0 +1,39 @@
|
|||
import ngModule from '../../module';
|
||||
import Section from 'salix/components/section';
|
||||
|
||||
export default class Controller extends Section {
|
||||
add() {
|
||||
const filter = {
|
||||
fields: ['etd']
|
||||
};
|
||||
this.$http.get(`Roadmaps/${this.$params.id}`, {filter})
|
||||
.then(res => {
|
||||
this.roadmap = res.data;
|
||||
|
||||
const eta = new Date(this.roadmap.etd);
|
||||
eta.setDate(eta.getDate() + 1);
|
||||
|
||||
this.$.model.insert({
|
||||
roadmapFk: this.$params.id,
|
||||
eta: eta
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
this.$.watcher.check();
|
||||
this.$.model.save().then(() => {
|
||||
this.$.watcher.notifySaved();
|
||||
this.$.watcher.updateOriginalData();
|
||||
this.$.model.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.component('vnRoadmapStops', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
roadmap: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,4 @@
|
|||
Remove stop: Eliminar parada
|
||||
Add stop: Añadir parada
|
||||
ETA date: Fecha ETA
|
||||
ETA hour: Hora ETA
|
|
@ -0,0 +1,113 @@
|
|||
<vn-card class="summary">
|
||||
<h5>
|
||||
<span>{{summary.id}} - {{summary.name}}</span>
|
||||
</h5>
|
||||
<vn-horizontal class="vn-pa-md">
|
||||
<vn-one>
|
||||
<vn-label-value label="Carrier">
|
||||
<span ng-click="supplierDescriptor.show($event, summary.supplier.id)" class="link">
|
||||
{{summary.supplier.nickname}}
|
||||
</span>
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="ETD"
|
||||
value="{{summary.etd | date:'dd/MM/yyyy HH:mm'}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Tractor plate"
|
||||
value="{{summary.tractorPlate}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Trailer plate"
|
||||
value="{{summary.trailerPlate}}">
|
||||
</vn-label-value>
|
||||
</vn-one>
|
||||
<vn-one>
|
||||
<vn-label-value
|
||||
label="Phone"
|
||||
value="{{summary.phone}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Worker"
|
||||
value="{{summary.worker.firstName}} {{summary.worker.lastName}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Observations"
|
||||
value="{{summary.observations}}">
|
||||
</vn-label-value>
|
||||
</vn-one>
|
||||
<vn-auto>
|
||||
<h4>
|
||||
<a
|
||||
ui-sref="route.roadmap.card.stops({id:summary.id})"
|
||||
target="_self">
|
||||
<span translate vn-tooltip="Go to">Stops</span>
|
||||
<vn-icon-button
|
||||
vn-bind="+"
|
||||
vn-tooltip="Add stop"
|
||||
icon="add_circle"
|
||||
vn-click-stop="addExpeditionTruck.show()">
|
||||
</vn-icon-button>
|
||||
</a>
|
||||
</h4>
|
||||
<vn-table model="model">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th>Wharehouse</vn-th>
|
||||
<vn-th>ETA</vn-th>
|
||||
</vn-tr>
|
||||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr ng-repeat="expeditionTruck in summary.expeditionTruck">
|
||||
<vn-td>{{expeditionTruck.warehouse.name}}</vn-td>
|
||||
<vn-td expand>{{expeditionTruck.eta | date:'dd/MM/yyyy HH:mm'}}</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</vn-auto>
|
||||
</vn-horizontal>
|
||||
</vn-card>
|
||||
<vn-supplier-descriptor-popover
|
||||
vn-id="supplierDescriptor">
|
||||
</vn-supplier-descriptor-popover>
|
||||
|
||||
<vn-dialog
|
||||
vn-id="addExpeditionTruck"
|
||||
on-open="$ctrl.getETD()"
|
||||
on-accept="$ctrl.onAddAccept()">
|
||||
<tpl-body>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete
|
||||
label="Warehouse"
|
||||
ng-model="$ctrl.expeditionTruck.warehouseFk"
|
||||
url="Warehouses"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
vn-focus
|
||||
rule>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-date-picker
|
||||
label="ETA date"
|
||||
ng-model="$ctrl.expeditionTruck.eta"
|
||||
rule>
|
||||
</vn-date-picker>
|
||||
<vn-input-time
|
||||
label="ETA hour"
|
||||
ng-model="$ctrl.expeditionTruck.eta">
|
||||
</vn-input-time>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textArea
|
||||
label="Description"
|
||||
ng-model="$ctrl.expeditionTruck.description"
|
||||
rule>
|
||||
</vn-textArea>
|
||||
</vn-horizontal>
|
||||
</tpl-body>
|
||||
<tpl-buttons>
|
||||
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||
<button response="accept" translate>Confirm</button>
|
||||
</tpl-buttons>
|
||||
</vn-dialog>
|
|
@ -0,0 +1,68 @@
|
|||
import ngModule from '../../module';
|
||||
import Component from 'core/lib/component';
|
||||
import './style.scss';
|
||||
|
||||
class Controller extends Component {
|
||||
set roadmap(value) {
|
||||
this._roadmap = value;
|
||||
this.$.summary = null;
|
||||
if (!value) return;
|
||||
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
get roadmap() {
|
||||
return this._roadmap;
|
||||
}
|
||||
|
||||
loadData() {
|
||||
const filter = {
|
||||
include: [
|
||||
{relation: 'supplier'},
|
||||
{relation: 'worker'},
|
||||
{relation: 'expeditionTruck',
|
||||
scope: {
|
||||
include: [
|
||||
{relation: 'warehouse'}
|
||||
]
|
||||
}}
|
||||
]
|
||||
};
|
||||
this.$http.get(`Roadmaps/${this.roadmap.id}`, {filter})
|
||||
.then(res => this.$.summary = res.data);
|
||||
}
|
||||
|
||||
getETD() {
|
||||
const eta = new Date(this.roadmap.etd);
|
||||
eta.setDate(eta.getDate() + 1);
|
||||
|
||||
this.expeditionTruck = {eta: eta};
|
||||
}
|
||||
|
||||
onAddAccept() {
|
||||
try {
|
||||
const data = {
|
||||
roadmapFk: this.roadmap.id,
|
||||
warehouseFk: this.expeditionTruck.warehouseFk,
|
||||
eta: this.expeditionTruck.eta,
|
||||
description: this.expeditionTruck.description
|
||||
};
|
||||
|
||||
this.$http.post(`ExpeditionTrucks`, data)
|
||||
.then(() => {
|
||||
this.loadData();
|
||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||
});
|
||||
} catch (e) {
|
||||
this.vnApp.showError(this.$t(e.message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.component('vnRoadmapSummary', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
roadmap: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
Stops: Paradas
|
||||
Wharehouse: Almacén
|
||||
You must fill all the fields: Debes rellenar todos los campos
|
|
@ -0,0 +1,10 @@
|
|||
@import "variables";
|
||||
|
||||
vn-roadmap-summary .summary {
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 18.328px;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,12 +7,17 @@
|
|||
"menus": {
|
||||
"main": [
|
||||
{"state": "route.index", "icon": "icon-delivery"},
|
||||
{"state": "route.agencyTerm.index", "icon": "icon-agency-term"}
|
||||
{"state": "route.agencyTerm.index", "icon": "icon-agency-term"},
|
||||
{"state": "route.roadmap", "icon": "icon-trailer"}
|
||||
],
|
||||
"card": [
|
||||
{"state": "route.card.basicData", "icon": "settings"},
|
||||
{"state": "route.card.tickets", "icon": "icon-ticket"},
|
||||
{"state": "route.card.log", "icon": "history"}
|
||||
],
|
||||
"roadmap": [
|
||||
{"state": "route.roadmap.card.basicData", "icon": "settings"},
|
||||
{"state": "route.roadmap.card.stops", "icon": "icon-lines"}
|
||||
]
|
||||
},
|
||||
"routes": [
|
||||
|
@ -90,6 +95,46 @@
|
|||
"route": "$ctrl.route"
|
||||
},
|
||||
"acl": ["delivery"]
|
||||
}, {
|
||||
"url": "/roadmap?q",
|
||||
"state": "route.roadmap",
|
||||
"component": "vn-roadmap",
|
||||
"description": "Roadmaps"
|
||||
}, {
|
||||
"url": "/create",
|
||||
"state": "route.roadmap.create",
|
||||
"component": "vn-roadmap-create",
|
||||
"description": "Create roadmap"
|
||||
},{
|
||||
"url": "/:id",
|
||||
"state": "route.roadmap.card",
|
||||
"component": "vn-roadmap-card",
|
||||
"abstract": true,
|
||||
"description": "Detail"
|
||||
},{
|
||||
"url": "/summary",
|
||||
"state": "route.roadmap.card.summary",
|
||||
"component": "vn-roadmap-summary",
|
||||
"description": "Summary",
|
||||
"params": {
|
||||
"roadmap": "$ctrl.roadmap"
|
||||
}
|
||||
},{
|
||||
"url": "/basic-data",
|
||||
"state": "route.roadmap.card.basicData",
|
||||
"component": "vn-roadmap-basic-data",
|
||||
"description": "Basic data",
|
||||
"params": {
|
||||
"roadmap": "$ctrl.roadmap"
|
||||
}
|
||||
}, {
|
||||
"url": "/stops",
|
||||
"state": "route.roadmap.card.stops",
|
||||
"component": "vn-roadmap-stops",
|
||||
"description": "Stops",
|
||||
"params": {
|
||||
"route": "$ctrl.roadmap"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
ng-click="deleteShelving.show()"
|
||||
name="deleteShelving"
|
||||
translate>
|
||||
Delete
|
||||
Delete shelving
|
||||
</vn-item>
|
||||
</slot-menu>
|
||||
<slot-body>
|
||||
|
@ -32,7 +32,7 @@
|
|||
</slot-body>
|
||||
</vn-descriptor-content>
|
||||
<vn-confirm
|
||||
vn-id="delete-shelving"
|
||||
vn-id="deleteShelving"
|
||||
on-accept="$ctrl.onDelete()"
|
||||
question="Are you sure you want to continue?"
|
||||
message="Shelving will be removed">
|
||||
|
@ -40,6 +40,6 @@
|
|||
<vn-popup vn-id="summary">
|
||||
<vn-shelving-summary shelving="$ctrl.shelving"></vn-shelving-summary>
|
||||
</vn-popup>
|
||||
<vn-worker-descriptor-popover
|
||||
<vn-worker-descriptor-popover
|
||||
vn-id="workerDescriptor">
|
||||
</vn-worker-descriptor-popover>
|
||||
</vn-worker-descriptor-popover>
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
Delete shelving: Eliminar carro
|
||||
Shelving will be removed: El carro será eliminado
|
||||
Shelving removed: Carro eliminado
|
|
@ -103,7 +103,7 @@ module.exports = Self => {
|
|||
const changes = ctx.data || ctx.instance;
|
||||
const orgData = ctx.currentInstance;
|
||||
const loopBackContext = LoopBackContext.getCurrentContext();
|
||||
const accessToken = {req: loopBackContext.active.accessToken};
|
||||
const accessToken = {req: loopBackContext.active};
|
||||
|
||||
const editPayMethodCheck =
|
||||
await Self.app.models.ACL.checkAccessAcl(accessToken, 'Supplier', 'editPayMethodCheck', 'WRITE');
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = function(Self) {
|
||||
Self.remoteMethodCtx('canBeInvoiced', {
|
||||
description: 'Whether the ticket can or not be invoiced',
|
||||
|
@ -21,8 +23,9 @@ module.exports = function(Self) {
|
|||
}
|
||||
});
|
||||
|
||||
Self.canBeInvoiced = async(ticketsIds, options) => {
|
||||
Self.canBeInvoiced = async(ctx, ticketsIds, options) => {
|
||||
const myOptions = {};
|
||||
const $t = ctx.req.__; // $translate
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
@ -31,29 +34,43 @@ module.exports = function(Self) {
|
|||
where: {
|
||||
id: {inq: ticketsIds}
|
||||
},
|
||||
fields: ['id', 'refFk', 'shipped', 'totalWithVat']
|
||||
fields: ['id', 'refFk', 'shipped', 'totalWithVat', 'companyFk']
|
||||
}, myOptions);
|
||||
const [firstTicket] = tickets;
|
||||
const companyFk = firstTicket.companyFk;
|
||||
|
||||
const query = `
|
||||
SELECT vn.hasSomeNegativeBase(t.id) AS hasSomeNegativeBase
|
||||
FROM ticket t
|
||||
WHERE id IN(?)`;
|
||||
const ticketBases = await Self.rawSql(query, [ticketsIds], myOptions);
|
||||
const hasSomeNegativeBase = ticketBases.some(
|
||||
ticketBases => ticketBases.hasSomeNegativeBase
|
||||
);
|
||||
const 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, [companyFk], options);
|
||||
|
||||
const isSpanishCompany = supplierCompany?.isSpanishCompany;
|
||||
|
||||
const [result] = await Self.rawSql('SELECT hasAnyNegativeBase() AS base', null, options);
|
||||
const hasAnyNegativeBase = result?.base && isSpanishCompany;
|
||||
if (hasAnyNegativeBase)
|
||||
throw new UserError($t('Negative basis of tickets', {ticketsIds: ticketsIds}));
|
||||
|
||||
const today = Date.vnNew();
|
||||
|
||||
const invalidTickets = tickets.some(ticket => {
|
||||
tickets.some(ticket => {
|
||||
const shipped = new Date(ticket.shipped);
|
||||
const shippingInFuture = shipped.getTime() > today.getTime();
|
||||
const isInvoiced = ticket.refFk;
|
||||
const priceZero = ticket.totalWithVat == 0;
|
||||
if (shippingInFuture)
|
||||
throw new UserError(`Can't invoice to future`);
|
||||
|
||||
return isInvoiced || priceZero || shippingInFuture;
|
||||
const isInvoiced = ticket.refFk;
|
||||
if (isInvoiced)
|
||||
throw new UserError(`This ticket is already invoiced`);
|
||||
|
||||
const priceZero = ticket.totalWithVat == 0;
|
||||
if (priceZero)
|
||||
throw new UserError(`A ticket with an amount of zero can't be invoiced`);
|
||||
});
|
||||
|
||||
return !(invalidTickets || hasSomeNegativeBase);
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = function(Self) {
|
||||
Self.remoteMethodCtx('invoiceTickets', {
|
||||
description: 'Make out an invoice from one or more tickets',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'ticketsIds',
|
||||
description: 'The tickets id',
|
||||
type: ['number'],
|
||||
required: true
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/invoiceTickets`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.invoiceTickets = async(ctx, ticketsIds, options) => {
|
||||
const models = Self.app.models;
|
||||
const date = Date.vnNew();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
|
||||
const myOptions = {userId: ctx.req.accessToken.userId};
|
||||
let tx;
|
||||
|
||||
if (typeof options === 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
let invoicesIds = [];
|
||||
try {
|
||||
const tickets = await models.Ticket.find({
|
||||
where: {
|
||||
id: {inq: ticketsIds}
|
||||
},
|
||||
fields: ['id', 'clientFk', 'companyFk']
|
||||
}, myOptions);
|
||||
|
||||
const [firstTicket] = tickets;
|
||||
const clientId = firstTicket.clientFk;
|
||||
const companyId = firstTicket.companyFk;
|
||||
|
||||
const isSameClient = tickets.every(ticket => ticket.clientFk === clientId);
|
||||
if (!isSameClient)
|
||||
throw new UserError(`You can't invoice tickets from multiple clients`);
|
||||
|
||||
const client = await models.Client.findById(clientId, {
|
||||
fields: ['id', 'hasToInvoiceByAddress']
|
||||
}, myOptions);
|
||||
|
||||
if (client.hasToInvoiceByAddress) {
|
||||
const query = `
|
||||
SELECT DISTINCT addressFk
|
||||
FROM ticket t
|
||||
WHERE id IN (?)`;
|
||||
const result = await Self.rawSql(query, [ticketsIds], myOptions);
|
||||
|
||||
const addressIds = result.map(address => address.addressFk);
|
||||
for (const address of addressIds)
|
||||
await createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, myOptions);
|
||||
} else
|
||||
await createInvoice(ctx, companyId, ticketsIds, null, invoicesIds, myOptions);
|
||||
|
||||
if (tx) await tx.commit();
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
|
||||
for (const invoiceId of invoicesIds)
|
||||
await models.InvoiceOut.makePdfAndNotify(ctx, invoiceId, null);
|
||||
|
||||
return invoicesIds;
|
||||
};
|
||||
|
||||
async function createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, myOptions) {
|
||||
const models = Self.app.models;
|
||||
|
||||
await models.Ticket.rawSql(`
|
||||
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice
|
||||
(PRIMARY KEY (id))
|
||||
ENGINE = MEMORY
|
||||
SELECT id
|
||||
FROM vn.ticket
|
||||
WHERE id IN (?)
|
||||
${address ? `AND addressFk = ${address}` : ''}
|
||||
`, [ticketsIds], myOptions);
|
||||
|
||||
const invoiceId = await models.Ticket.makeInvoice(ctx, 'R', companyId, Date.vnNew(), myOptions);
|
||||
invoicesIds.push(invoiceId);
|
||||
}
|
||||
};
|
||||
|
|
@ -6,15 +6,26 @@ module.exports = function(Self) {
|
|||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'ticketsIds',
|
||||
description: 'The tickets id',
|
||||
type: ['number'],
|
||||
arg: 'invoiceType',
|
||||
description: 'The invoice type',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'companyFk',
|
||||
description: 'The company id',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'invoiceDate',
|
||||
description: 'The invoice date',
|
||||
type: 'date',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
arg: 'data',
|
||||
type: 'boolean',
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
|
@ -23,10 +34,9 @@ module.exports = function(Self) {
|
|||
}
|
||||
});
|
||||
|
||||
Self.makeInvoice = async(ctx, ticketsIds, options) => {
|
||||
Self.makeInvoice = async(ctx, invoiceType, companyFk, invoiceDate, options) => {
|
||||
const models = Self.app.models;
|
||||
const date = Date.vnNew();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
invoiceDate.setHours(0, 0, 0, 0);
|
||||
|
||||
const myOptions = {userId: ctx.req.accessToken.userId};
|
||||
let tx;
|
||||
|
@ -40,81 +50,50 @@ module.exports = function(Self) {
|
|||
}
|
||||
|
||||
let serial;
|
||||
let invoiceId;
|
||||
let invoiceOut;
|
||||
try {
|
||||
const ticketToInvoice = await Self.rawSql(`
|
||||
SELECT id
|
||||
FROM tmp.ticketToInvoice`, null, myOptions);
|
||||
|
||||
const ticketsIds = ticketToInvoice.map(ticket => ticket.id);
|
||||
const tickets = await models.Ticket.find({
|
||||
where: {
|
||||
id: {inq: ticketsIds}
|
||||
},
|
||||
fields: ['id', 'clientFk', 'companyFk']
|
||||
fields: ['id', 'clientFk']
|
||||
}, myOptions);
|
||||
|
||||
await models.Ticket.canBeInvoiced(ctx, ticketsIds, myOptions);
|
||||
|
||||
const [firstTicket] = tickets;
|
||||
const clientId = firstTicket.clientFk;
|
||||
const companyId = firstTicket.companyFk;
|
||||
|
||||
const isSameClient = tickets.every(ticket => ticket.clientFk == clientId);
|
||||
if (!isSameClient)
|
||||
throw new UserError(`You can't invoice tickets from multiple clients`);
|
||||
|
||||
const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, myOptions);
|
||||
if (!clientCanBeInvoiced)
|
||||
throw new UserError(`This client can't be invoiced`);
|
||||
|
||||
const ticketCanBeInvoiced = await models.Ticket.canBeInvoiced(ticketsIds, myOptions);
|
||||
if (!ticketCanBeInvoiced)
|
||||
throw new UserError(`Some of the selected tickets are not billable`);
|
||||
|
||||
const query = `SELECT vn.invoiceSerial(?, ?, ?) AS serial`;
|
||||
const [result] = await Self.rawSql(query, [
|
||||
clientId,
|
||||
companyId,
|
||||
'R'
|
||||
companyFk,
|
||||
invoiceType,
|
||||
], myOptions);
|
||||
serial = result.serial;
|
||||
|
||||
await Self.rawSql(`
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice;
|
||||
CREATE TEMPORARY TABLE tmp.ticketToInvoice
|
||||
(PRIMARY KEY (id))
|
||||
ENGINE = MEMORY
|
||||
SELECT id FROM vn.ticket
|
||||
WHERE id IN(?) AND refFk IS NULL
|
||||
`, [ticketsIds], myOptions);
|
||||
|
||||
await Self.rawSql('CALL invoiceOut_new(?, ?, null, @invoiceId)', [serial, date], myOptions);
|
||||
await Self.rawSql('CALL invoiceOut_new(?, ?, null, @invoiceId)', [serial, invoiceDate], myOptions);
|
||||
|
||||
const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], myOptions);
|
||||
if (!resultInvoice)
|
||||
throw new UserError('No tickets to invoice', 'notInvoiced');
|
||||
|
||||
invoiceId = resultInvoice.id;
|
||||
if (serial != 'R' && resultInvoice.id)
|
||||
await Self.rawSql('CALL invoiceOutBooking(?)', [resultInvoice.id], myOptions);
|
||||
|
||||
if (serial != 'R' && invoiceId)
|
||||
await Self.rawSql('CALL invoiceOutBooking(?)', [invoiceId], myOptions);
|
||||
|
||||
invoiceOut = await models.InvoiceOut.findById(invoiceId, {
|
||||
include: {
|
||||
relation: 'client'
|
||||
}
|
||||
}, myOptions);
|
||||
if (tx) await tx.commit();
|
||||
|
||||
return resultInvoice.id;
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (serial != 'R' && invoiceId)
|
||||
await models.InvoiceOut.createPdf(ctx, invoiceId);
|
||||
|
||||
if (invoiceId) {
|
||||
ctx.args = {
|
||||
reference: invoiceOut.ref,
|
||||
recipientId: invoiceOut.clientFk,
|
||||
recipient: invoiceOut.client().email
|
||||
};
|
||||
await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref);
|
||||
}
|
||||
|
||||
return {invoiceFk: invoiceId, serial: serial};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,61 +1,81 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('ticket canBeInvoiced()', () => {
|
||||
const userId = 19;
|
||||
const ticketId = 11;
|
||||
const activeCtx = {
|
||||
accessToken: {userId: userId}
|
||||
const ctx = {req: {accessToken: {userId: userId}}};
|
||||
ctx.req.__ = value => {
|
||||
return value;
|
||||
};
|
||||
|
||||
beforeAll(async() => {
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should return falsy for an already invoiced ticket', async() => {
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
let error;
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ticket = await models.Ticket.findById(ticketId, null, options);
|
||||
await ticket.updateAttribute('refFk', 'T1111111', options);
|
||||
|
||||
const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options);
|
||||
await models.Ticket.rawSql(`
|
||||
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice
|
||||
(PRIMARY KEY (id))
|
||||
ENGINE = MEMORY
|
||||
SELECT id
|
||||
FROM vn.ticket
|
||||
WHERE id IN (?)
|
||||
`, [ticketId], options);
|
||||
|
||||
const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options);
|
||||
|
||||
expect(canBeInvoiced).toEqual(false);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`This ticket is already invoiced`);
|
||||
});
|
||||
|
||||
it('should return falsy for a ticket with a price of zero', async() => {
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
let error;
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ticket = await models.Ticket.findById(ticketId, null, options);
|
||||
await ticket.updateAttribute('totalWithVat', 0, options);
|
||||
|
||||
const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options);
|
||||
await models.Ticket.rawSql(`
|
||||
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice
|
||||
(PRIMARY KEY (id))
|
||||
ENGINE = MEMORY
|
||||
SELECT id
|
||||
FROM vn.ticket
|
||||
WHERE id IN (?)
|
||||
`, [ticketId], options);
|
||||
|
||||
const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options);
|
||||
|
||||
expect(canBeInvoiced).toEqual(false);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`A ticket with an amount of zero can't be invoiced`);
|
||||
});
|
||||
|
||||
it('should return falsy for a ticket shipping in future', async() => {
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
let error;
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
|
@ -66,15 +86,26 @@ describe('ticket canBeInvoiced()', () => {
|
|||
|
||||
await ticket.updateAttribute('shipped', shipped, options);
|
||||
|
||||
const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options);
|
||||
await models.Ticket.rawSql(`
|
||||
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice
|
||||
(PRIMARY KEY (id))
|
||||
ENGINE = MEMORY
|
||||
SELECT id
|
||||
FROM vn.ticket
|
||||
WHERE id IN (?)
|
||||
`, [ticketId], options);
|
||||
|
||||
const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options);
|
||||
|
||||
expect(canBeInvoiced).toEqual(false);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`Can't invoice to future`);
|
||||
});
|
||||
|
||||
it('should return truthy for an invoiceable ticket', async() => {
|
||||
|
@ -83,7 +114,16 @@ describe('ticket canBeInvoiced()', () => {
|
|||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options);
|
||||
await models.Ticket.rawSql(`
|
||||
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice
|
||||
(PRIMARY KEY (id))
|
||||
ENGINE = MEMORY
|
||||
SELECT id
|
||||
FROM vn.ticket
|
||||
WHERE id IN (?)
|
||||
`, [ticketId], options);
|
||||
|
||||
const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options);
|
||||
|
||||
expect(canBeInvoiced).toEqual(true);
|
||||
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('ticket invoiceTickets()', () => {
|
||||
const userId = 19;
|
||||
const clientId = 1102;
|
||||
const activeCtx = {
|
||||
getLocale: () => {
|
||||
return 'en';
|
||||
},
|
||||
accessToken: {userId: userId},
|
||||
headers: {origin: 'http://localhost:5000'},
|
||||
};
|
||||
const ctx = {req: activeCtx};
|
||||
|
||||
beforeAll(async() => {
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when invoicing tickets from multiple clients', async() => {
|
||||
const invoiceOutModel = models.InvoiceOut;
|
||||
spyOn(invoiceOutModel, 'makePdfAndNotify');
|
||||
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
let error;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ticketsIds = [11, 16];
|
||||
await models.Ticket.invoiceTickets(ctx, ticketsIds, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`You can't invoice tickets from multiple clients`);
|
||||
});
|
||||
|
||||
it(`should throw an error when invoicing a client without tax data checked`, async() => {
|
||||
const invoiceOutModel = models.InvoiceOut;
|
||||
spyOn(invoiceOutModel, 'makePdfAndNotify');
|
||||
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
let error;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const client = await models.Client.findById(clientId, null, options);
|
||||
await client.updateAttribute('isTaxDataChecked', false, options);
|
||||
|
||||
const ticketsIds = [11];
|
||||
await models.Ticket.invoiceTickets(ctx, ticketsIds, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`This client can't be invoiced`);
|
||||
});
|
||||
|
||||
it('should invoice a ticket, then try again to fail', async() => {
|
||||
const invoiceOutModel = models.InvoiceOut;
|
||||
spyOn(invoiceOutModel, 'makePdfAndNotify');
|
||||
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
let error;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ticketsIds = [11];
|
||||
await models.Ticket.invoiceTickets(ctx, ticketsIds, options);
|
||||
await models.Ticket.invoiceTickets(ctx, ticketsIds, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`This ticket is already invoiced`);
|
||||
});
|
||||
|
||||
it('should success to invoice a ticket', async() => {
|
||||
const invoiceOutModel = models.InvoiceOut;
|
||||
spyOn(invoiceOutModel, 'makePdfAndNotify');
|
||||
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ticketsIds = [11];
|
||||
const invoicesIds = await models.Ticket.invoiceTickets(ctx, ticketsIds, options);
|
||||
|
||||
expect(invoicesIds.length).toBeGreaterThan(0);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
|
@ -3,8 +3,9 @@ const LoopBackContext = require('loopback-context');
|
|||
|
||||
describe('ticket makeInvoice()', () => {
|
||||
const userId = 19;
|
||||
const ticketId = 11;
|
||||
const clientId = 1102;
|
||||
const invoiceType = 'R';
|
||||
const companyFk = 442;
|
||||
const invoiceDate = Date.vnNew();
|
||||
const activeCtx = {
|
||||
getLocale: () => {
|
||||
return 'en';
|
||||
|
@ -20,77 +21,6 @@ describe('ticket makeInvoice()', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should throw an error when invoicing tickets from multiple clients', async() => {
|
||||
const invoiceOutModel = models.InvoiceOut;
|
||||
spyOn(invoiceOutModel, 'createPdf');
|
||||
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
let error;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
const otherClientTicketId = 16;
|
||||
await models.Ticket.makeInvoice(ctx, [ticketId, otherClientTicketId], options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`You can't invoice tickets from multiple clients`);
|
||||
});
|
||||
|
||||
it(`should throw an error when invoicing a client without tax data checked`, async() => {
|
||||
const invoiceOutModel = models.InvoiceOut;
|
||||
spyOn(invoiceOutModel, 'createPdf');
|
||||
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
let error;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const client = await models.Client.findById(clientId, null, options);
|
||||
await client.updateAttribute('isTaxDataChecked', false, options);
|
||||
|
||||
await models.Ticket.makeInvoice(ctx, [ticketId], options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`This client can't be invoiced`);
|
||||
});
|
||||
|
||||
it('should invoice a ticket, then try again to fail', async() => {
|
||||
const invoiceOutModel = models.InvoiceOut;
|
||||
spyOn(invoiceOutModel, 'createPdf');
|
||||
spyOn(invoiceOutModel, 'invoiceEmail');
|
||||
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
let error;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await models.Ticket.makeInvoice(ctx, [ticketId], options);
|
||||
await models.Ticket.makeInvoice(ctx, [ticketId], options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`Some of the selected tickets are not billable`);
|
||||
});
|
||||
|
||||
it('should success to invoice a ticket', async() => {
|
||||
const invoiceOutModel = models.InvoiceOut;
|
||||
spyOn(invoiceOutModel, 'createPdf');
|
||||
|
@ -101,10 +31,20 @@ describe('ticket makeInvoice()', () => {
|
|||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const invoice = await models.Ticket.makeInvoice(ctx, [ticketId], options);
|
||||
const ticketsIds = [11, 16];
|
||||
await models.Ticket.rawSql(`
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice;
|
||||
CREATE TEMPORARY TABLE tmp.ticketToInvoice
|
||||
(PRIMARY KEY (id))
|
||||
ENGINE = MEMORY
|
||||
SELECT id
|
||||
FROM vn.ticket
|
||||
WHERE id IN (?)
|
||||
`, [ticketsIds], options);
|
||||
|
||||
expect(invoice.invoiceFk).toBeDefined();
|
||||
expect(invoice.serial).toEqual('T');
|
||||
const invoiceId = await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, options);
|
||||
|
||||
expect(invoiceId).toBeDefined();
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
|
|
@ -39,4 +39,5 @@ module.exports = function(Self) {
|
|||
require('../methods/ticket/collectionLabel')(Self);
|
||||
require('../methods/ticket/expeditionPalletLabel')(Self);
|
||||
require('../methods/ticket/saveSign')(Self);
|
||||
require('../methods/ticket/invoiceTickets')(Self);
|
||||
};
|
||||
|
|
|
@ -270,8 +270,7 @@ class Controller extends Section {
|
|||
});
|
||||
}
|
||||
|
||||
return this.$http.post(`Tickets/makeInvoice`, {ticketsIds: [this.id]})
|
||||
.then(() => this.reload())
|
||||
return this.$http.post(`Tickets/invoiceTickets`, {ticketsIds: [this.id]})
|
||||
.then(() => this.reload())
|
||||
.then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced')));
|
||||
}
|
||||
|
|
|
@ -191,7 +191,7 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
|
|||
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||
|
||||
const expectedParams = {ticketsIds: [ticket.id]};
|
||||
$httpBackend.expectPOST(`Tickets/makeInvoice`, expectedParams).respond();
|
||||
$httpBackend.expectPOST(`Tickets/invoiceTickets`, expectedParams).respond();
|
||||
controller.makeInvoice();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
|
|
@ -163,7 +163,7 @@ export default class Controller extends Section {
|
|||
|
||||
makeInvoice() {
|
||||
const ticketsIds = this.checked.map(ticket => ticket.id);
|
||||
return this.$http.post(`Tickets/makeInvoice`, {ticketsIds})
|
||||
return this.$http.post(`Tickets/invoiceTickets`, {ticketsIds})
|
||||
.then(() => this.$.model.refresh())
|
||||
.then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced')));
|
||||
}
|
||||
|
|
|
@ -163,7 +163,7 @@
|
|||
</td>
|
||||
<td number>{{::entry.invoiceAmount | currency: 'EUR': 2}}</td>
|
||||
<td></td>
|
||||
<td class="td-editable">{{::entry.invoiceNumber}}</td>
|
||||
<td class="td-editable">{{::entry.reference}}</td>
|
||||
<td number>{{::entry.stickers}}</td>
|
||||
<td number></td>
|
||||
<td number>{{::entry.loadedkg}}</td>
|
||||
|
|
|
@ -43,9 +43,6 @@
|
|||
"SSN": {
|
||||
"type" : "string"
|
||||
},
|
||||
"labelerFk": {
|
||||
"type" : "number"
|
||||
},
|
||||
"mobileExtension": {
|
||||
"type" : "number"
|
||||
},
|
||||
|
@ -86,11 +83,6 @@
|
|||
"type": "hasMany",
|
||||
"model": "WorkerTeamCollegues",
|
||||
"foreignKey": "workerFk"
|
||||
},
|
||||
"sector": {
|
||||
"type": "belongsTo",
|
||||
"model": "Sector",
|
||||
"foreignKey": "sectorFk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<vn-side-menu side="right">
|
||||
<div class="vn-pa-md">
|
||||
<div class="totalBox vn-mb-sm" style="text-align: center;">
|
||||
<h6>{{'Contract' | translate}} #{{$ctrl.card.worker.hasWorkCenter}}</h6>
|
||||
<h6>{{'Contract' | translate}} #{{$ctrl.businessId}}</h6>
|
||||
<div>
|
||||
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed || 0}}
|
||||
{{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}}
|
||||
|
|
|
@ -33,11 +33,12 @@ class Controller extends ModuleCard {
|
|||
};
|
||||
|
||||
this.$http.get(`Workers/${this.$params.id}`, {filter})
|
||||
.then(res => this.worker = res.data);
|
||||
this.$http.get(`Workers/${this.$params.id}/activeContract`)
|
||||
.then(res => {
|
||||
if (res.data) this.worker.hasWorkCenter = res.data.workCenterFk;
|
||||
});
|
||||
.then(res => this.worker = res.data)
|
||||
.then(() =>
|
||||
this.$http.get(`Workers/${this.$params.id}/activeContract`)
|
||||
.then(res => {
|
||||
if (res.data) this.worker.hasWorkCenter = res.data.workCenterFk;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
</vn-textfield>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Code"
|
||||
label="Worker code"
|
||||
ng-model="$ctrl.worker.code"
|
||||
maxLength="3"
|
||||
on-change="$ctrl.worker.code = $ctrl.worker.code.toUpperCase()"
|
||||
|
|
|
@ -2,7 +2,7 @@ Firstname: Nombre
|
|||
Lastname: Apellidos
|
||||
Fi: DNI/NIF/NIE
|
||||
Birth: Fecha de nacimiento
|
||||
Code: Código de trabajador
|
||||
Worker code: Código de trabajador
|
||||
Province: Provincia
|
||||
City: Población
|
||||
ProfileType: Tipo de perfil
|
||||
|
|
|
@ -38,7 +38,7 @@ module.exports = Self => {
|
|||
Object.assign(myOptions, options);
|
||||
|
||||
const [res] = await Self.rawSql(
|
||||
`CALL zone_getLeaves(?, ?, ?)`,
|
||||
`CALL zone_getLeaves(?, ?, ?, FALSE)`,
|
||||
[id, parentId, search],
|
||||
myOptions
|
||||
);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
.code {
|
||||
border: 2px dashed #8dba25;
|
||||
border-radius: 3px;
|
||||
text-align: center
|
||||
}
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
|
|
@ -1,23 +1,45 @@
|
|||
<email-body v-bind="$props">
|
||||
<div class="grid-row">
|
||||
<div class="grid-block vn-pa-ml">
|
||||
<h1>{{ $t('title') }}</h1>
|
||||
<p>{{ $t('description') }}</p>
|
||||
<p>
|
||||
{{ $t('device') }}: <strong>{{ device }}</strong>
|
||||
</p>
|
||||
<p>
|
||||
{{$t('ip')}}: <strong>{{ ip }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="grid-block vn-pa-ml">
|
||||
<p>{{ $t('Enter the following code to continue to your account') }}</p>
|
||||
<div class="code vn-pa-sm vn-m-md">
|
||||
{{ code }}
|
||||
</div>
|
||||
<p>{{ $t('It expires in 5 minutes') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</email-body>
|
||||
<!DOCTYPE html>
|
||||
<html v-bind:lang="$i18n.locale">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
</head>
|
||||
<body>
|
||||
<table class="grid">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="grid-row">
|
||||
<div class="grid-block empty"></div>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="grid-block">
|
||||
<email-header v-bind="$props"></email-header>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="grid-block vn-pa-md">
|
||||
<h1>{{ $t('title') }}</h1>
|
||||
<p>{{ $t('description') }}</p>
|
||||
<p>
|
||||
{{ $t('device') }}: <strong>{{ device }}</strong>
|
||||
</p>
|
||||
<p>
|
||||
{{$t('ip')}}: <strong>{{ ip }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="grid-block vn-pa-sm">
|
||||
<p>{{ $t('Enter the following code to continue to your account. It expires in 5 minutes.') }}</p>
|
||||
<div class="code vn-pa-sm vn-m-md">
|
||||
{{ code }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
const Component = require(`vn-print/core/component`);
|
||||
const emailBody = new Component('email-body');
|
||||
const emailHeader = new Component('email-header');
|
||||
|
||||
module.exports = {
|
||||
name: 'auth-code',
|
||||
components: {
|
||||
'email-body': emailBody.build(),
|
||||
'email-header': emailHeader.build(),
|
||||
},
|
||||
props: {
|
||||
code: {
|
||||
|
|
|
@ -3,5 +3,4 @@ title: Verification code
|
|||
description: Somebody did request a verification code for login. If you didn't request it, please ignore this email.
|
||||
device: 'Device'
|
||||
ip: 'IP'
|
||||
Enter the following code to continue to your account: Enter the following code to continue to your account
|
||||
It expires in 5 minutes: It expires in 5 minutes
|
||||
Enter the following code to continue to your account. It expires in 5 minutes.: Enter the following code to continue to your account. It expires in 5 minutes.
|
||||
|
|
|
@ -3,5 +3,4 @@ title: Código de verificación
|
|||
description: Alguien ha solicitado un código de verificación para poder iniciar sesión. Si no lo has solicitado tu, ignora este email.
|
||||
device: 'Dispositivo'
|
||||
ip: 'IP'
|
||||
Enter the following code to continue to your account: Introduce el siguiente código para poder continuar con tu cuenta
|
||||
It expires in 5 minutes: Expira en 5 minutos
|
||||
Enter the following code to continue to your account. It expires in 5 minutes.: Introduce el siguiente código para poder continuar con tu cuenta. Expira en 5 minutos.
|
||||
|
|
|
@ -3,5 +3,4 @@ title: Code de vérification
|
|||
description: Quelqu'un a demandé un code de vérification pour se connecter. Si ce n'était pas toi, ignore cet email.
|
||||
device: 'Appareil'
|
||||
ip: 'IP'
|
||||
Enter the following code to continue to your account: Entrez le code suivant pour continuer avec votre compte
|
||||
It expires in 5 minutes: Il expire dans 5 minutes.
|
||||
Enter the following code to continue to your account. It expires in 5 minutes.: Entrez le code suivant pour continuer avec votre compte. Il expire dans 5 minutes.
|
||||
|
|
|
@ -3,5 +3,4 @@ title: Código de verificação
|
|||
description: Alguém solicitou um código de verificação para entrar. Se você não fez essa solicitação, ignore este e-mail.
|
||||
device: 'Dispositivo'
|
||||
ip: 'IP'
|
||||
Enter the following code to continue to your account: Insira o seguinte código para continuar com sua conta.
|
||||
It expires in 5 minutes: Expira em 5 minutos.
|
||||
Enter the following code to continue to your account. It expires in 5 minutes.: Insira o seguinte código para continuar com sua conta. Expira em 5 minutos.
|
||||
|
|
Loading…
Reference in New Issue