Merge branch 'dev' into 4059-revocar-permisos-client-account
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Javi Gallego 2023-07-04 11:47:39 +02:00
commit e4e7a9444a
202 changed files with 4067 additions and 4156 deletions

View File

@ -17,7 +17,7 @@ rules:
camelcase: 0 camelcase: 0
default-case: 0 default-case: 0
no-eq-null: 0 no-eq-null: 0
no-console: ["error"] no-console: ["warn"]
no-warning-comments: 0 no-warning-comments: 0
no-empty: [error, allowEmptyCatch: true] no-empty: [error, allowEmptyCatch: true]
complexity: 0 complexity: 0

View File

@ -8,13 +8,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [2326.01] - 2023-06-29 ## [2326.01] - 2023-06-29
### Added ### Added
- (Entradas -> Correo) Al cambiar el tipo de cambio enviará un correo a las personas designadas
- (General -> Históricos) Botón para ver el estado del registro en cada punto
- (General -> Históricos) Al filtar por registro se muestra todo el histórial desde que fue creado
- (Tickets -> Índice) Permite enviar varios albaranes a Docuware
### Changed ### Changed
- (General -> Históricos) Los registros se muestran agrupados por usuario y entidad
- (Facturas -> Facturación global) Optimizada, generación de PDFs y notificaciones en paralelo
### Fixed ### Fixed
- - (General -> Históricos) Duplicidades eliminadas
- (Facturas -> Facturación global) Solucionados fallos que paran el proceso
## [2324.01] - 2023-06-08 ## [2324.01] - 2023-06-15
### Added ### Added
- (Tickets -> Abono) Al abonar permite crear el ticket abono con almacén o sin almmacén - (Tickets -> Abono) Al abonar permite crear el ticket abono con almacén o sin almmacén

View File

@ -8,7 +8,7 @@ Salix is also the scientific name of a beautifull tree! :)
Required applications. Required applications.
* Node.js = 14.x LTS * Node.js >= 16.x LTS
* Docker * Docker
* Git * Git
@ -71,7 +71,7 @@ $ npm run test:e2e
Open Visual Studio Code, press Ctrl+P and paste the following commands. Open Visual Studio Code, press Ctrl+P and paste the following commands.
In Visual Studio Code we use the ESLint extension. In Visual Studio Code we use the ESLint extension.
``` ```
ext install dbaeumer.vscode-eslint ext install dbaeumer.vscode-eslint
``` ```

View File

@ -0,0 +1,38 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('renewToken', {
description: 'Checks if the token has more than renewPeriod seconds to live and if so, renews it',
accessType: 'WRITE',
accepts: [],
returns: {
type: 'Object',
root: true
},
http: {
path: `/renewToken`,
verb: 'POST'
}
});
Self.renewToken = async function(ctx) {
const models = Self.app.models;
const token = ctx.req.accessToken;
const now = new Date();
const differenceMilliseconds = now - token.created;
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
const fields = ['renewPeriod', 'courtesyTime'];
const accessTokenConfig = await models.AccessTokenConfig.findOne({fields});
if (differenceSeconds < accessTokenConfig.renewPeriod - accessTokenConfig.courtesyTime)
throw new UserError(`The renew period has not been exceeded`, 'periodNotExceeded');
await Self.logout(token.id);
const user = await Self.findById(token.userId);
const accessToken = await user.createAccessToken();
return {id: accessToken.id, ttl: accessToken.ttl};
};
};

View File

@ -62,10 +62,9 @@ module.exports = Self => {
scopes: ['change-password'], scopes: ['change-password'],
userId: vnUser.id userId: vnUser.id
}); });
throw new UserError('Pass expired', 'passExpired', { const err = new UserError('Pass expired', 'passExpired');
id: vnUser.id, err.details = {token: changePasswordToken};
token: changePasswordToken.id throw err;
});
} }
try { try {
@ -77,6 +76,6 @@ module.exports = Self => {
let loginInfo = Object.assign({password}, userInfo); let loginInfo = Object.assign({password}, userInfo);
token = await Self.login(loginInfo, 'user'); token = await Self.login(loginInfo, 'user');
return {token: token.id}; return {token: token.id, ttl: token.ttl};
}; };
}; };

View File

@ -9,7 +9,7 @@ describe('VnUser signIn()', () => {
expect(login.token).toBeDefined(); expect(login.token).toBeDefined();
await models.VnUser.signOut(ctx); await models.VnUser.logout(ctx.req.accessToken.id);
}); });
it('should return the token if the user doesnt exist but the client does', async() => { it('should return the token if the user doesnt exist but the client does', async() => {
@ -19,7 +19,7 @@ describe('VnUser signIn()', () => {
expect(login.token).toBeDefined(); expect(login.token).toBeDefined();
await models.VnUser.signOut(ctx); await models.VnUser.logout(ctx.req.accessToken.id);
}); });
}); });

View File

@ -1,42 +0,0 @@
const {models} = require('vn-loopback/server/server');
describe('VnUser signOut()', () => {
it('should logout and remove token after valid login', async() => {
let loginResponse = await models.VnUser.signOut('buyer', 'nightmare');
let accessToken = await models.AccessToken.findById(loginResponse.token);
let ctx = {req: {accessToken: accessToken}};
let logoutResponse = await models.VnUser.signOut(ctx);
let tokenAfterLogout = await models.AccessToken.findById(loginResponse.token);
expect(logoutResponse).toBeTrue();
expect(tokenAfterLogout).toBeNull();
});
it('should throw a 401 error when token is invalid', async() => {
let error;
let ctx = {req: {accessToken: {id: 'invalidToken'}}};
try {
response = await models.VnUser.signOut(ctx);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.statusCode).toBe(401);
});
it('should throw an error when no token is passed', async() => {
let error;
let ctx = {req: {accessToken: null}};
try {
response = await models.VnUser.signOut(ctx);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
});
});

View File

@ -2,6 +2,14 @@
"AccountingType": { "AccountingType": {
"dataSource": "vn" "dataSource": "vn"
}, },
"AccessTokenConfig": {
"dataSource": "vn",
"options": {
"mysql": {
"table": "salix.accessTokenConfig"
}
}
},
"Bank": { "Bank": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,34 @@
{
"name": "AccessTokenConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "accessTokenConfig"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"renewPeriod": {
"type": "number",
"required": true
},
"courtesyTime": {
"type": "number",
"required": true
},
"renewInterval": {
"type": "number",
"required": true
}
},
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
}

View File

@ -10,6 +10,9 @@ module.exports = function(Self) {
require('../methods/vn-user/recover-password')(Self); require('../methods/vn-user/recover-password')(Self);
require('../methods/vn-user/validate-token')(Self); require('../methods/vn-user/validate-token')(Self);
require('../methods/vn-user/privileges')(Self); require('../methods/vn-user/privileges')(Self);
require('../methods/vn-user/renew-token')(Self);
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
// Validations // Validations

View File

@ -118,5 +118,24 @@
"principalId": "$authenticated", "principalId": "$authenticated",
"permission": "ALLOW" "permission": "ALLOW"
} }
] ],
"scopes": {
"preview": {
"fields": [
"id",
"name",
"username",
"roleFk",
"nickname",
"lang",
"active",
"created",
"updated",
"image",
"hasGrant",
"realm",
"email"
]
}
}
} }

View File

@ -4,4 +4,4 @@ apps:
instances: 1 instances: 1
max_restarts: 3 max_restarts: 3
restart_delay: 15000 restart_delay: 15000
node_args: --tls-min-v1.0 node_args: --tls-min-v1.0 --openssl-legacy-provider

View File

@ -1,6 +1,5 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES VALUES
('VnUser', '*', '*', 'ALLOW', 'ROLE', 'employee'),
('VnUser','acl','READ','ALLOW','ROLE','account'), ('VnUser','acl','READ','ALLOW','ROLE','account'),
('VnUser','getCurrentUserData','READ','ALLOW','ROLE','account'), ('VnUser','getCurrentUserData','READ','ALLOW','ROLE','account'),
('VnUser','changePassword', 'WRITE', 'ALLOW', 'ROLE', 'account'), ('VnUser','changePassword', 'WRITE', 'ALLOW', 'ROLE', 'account'),

View File

@ -0,0 +1,22 @@
CREATE TABLE `vn`.`travelConfig` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`warehouseInFk` smallint(6) unsigned NOT NULL DEFAULT 8 COMMENT 'Warehouse de origen',
`warehouseOutFk` smallint(6) unsigned NOT NULL DEFAULT 60 COMMENT 'Warehouse destino',
`agencyFk` int(11) NOT NULL DEFAULT 1378 COMMENT 'Agencia por defecto',
`companyFk` int(10) unsigned NOT NULL DEFAULT 442 COMMENT 'Compañía por defecto',
PRIMARY KEY (`id`),
KEY `travelConfig_FK` (`warehouseInFk`),
KEY `travelConfig_FK_1` (`warehouseOutFk`),
KEY `travelConfig_FK_2` (`agencyFk`),
KEY `travelConfig_FK_3` (`companyFk`),
CONSTRAINT `travelConfig_FK` FOREIGN KEY (`warehouseInFk`) REFERENCES `warehouse` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `travelConfig_FK_1` FOREIGN KEY (`warehouseOutFk`) REFERENCES `warehouse` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `travelConfig_FK_2` FOREIGN KEY (`agencyFk`) REFERENCES `agencyMode` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `travelConfig_FK_3` FOREIGN KEY (`companyFk`) REFERENCES `company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Entry', 'addFromPackaging', 'WRITE', 'ALLOW', 'ROLE', 'production'),
('Entry', 'addFromBuy', 'WRITE', 'ALLOW', 'ROLE', 'production'),
('Supplier', 'getItemsPackaging', 'READ', 'ALLOW', 'ROLE', 'production');

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
DELETE FROM `salix`.`ACL` WHERE model = 'Role';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Role', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Role', '*', 'WRITE', 'ALLOW', 'ROLE', 'it');

View File

@ -0,0 +1,10 @@
DELETE
FROM `salix`.`ACL`
WHERE model = 'VnUser' AND property = '*' AND principalId = 'employee';
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('VnUser', '*', '*', 'ALLOW', 'ROLE', 'itManagement'),
('VnUser', '__get__preview', 'READ', 'ALLOW', 'ROLE', 'employee'),
('VnUser', 'preview', '*', 'ALLOW', 'ROLE', 'employee'),
('VnUser', 'create', '*', 'ALLOW', 'ROLE', 'itManagement');

View File

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

View File

@ -0,0 +1,40 @@
DELIMITER $$
$$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`entry_updateComission`(vCurrency INT)
BEGIN
/**
* Actualiza la comision de las entradas de hoy a futuro y las recalcula
*
* @param vCurrency id del tipo de moneda(SAR,EUR,USD,GBP,JPY)
*/
DECLARE vCurrencyName VARCHAR(25);
DECLARE vComission INT;
CREATE OR REPLACE TEMPORARY TABLE tmp.recalcEntryCommision
SELECT e.id
FROM vn.entry e
JOIN vn.travel t ON t.id = e.travelFk
JOIN vn.warehouse w ON w.id = t.warehouseInFk
WHERE t.shipped >= util.VN_CURDATE()
AND e.currencyFk = vCurrency;
SET vComission = currency_getCommission(vCurrency);
UPDATE vn.entry e
JOIN tmp.recalcEntryCommision tmp ON tmp.id = e.id
SET e.commission = vComission;
SELECT `name` INTO vCurrencyName
FROM currency
WHERE id = vCurrency;
CALL entry_recalc();
SELECT util.notification_send(
'entry-update-comission',
JSON_OBJECT('currencyName', vCurrencyName, 'referenceCurrent', vComission),
account.myUser_getId()
);
DROP TEMPORARY TABLE tmp.recalcEntryCommision;
END$$
DELIMITER ;

View File

@ -0,0 +1,71 @@
CREATE TABLE `vn`.`packingSiteAdvanced` (
`ticketFk` int(11),
`workerFk` int(10) unsigned,
PRIMARY KEY (`ticketFk`),
KEY `packingSiteAdvanced_FK_1` (`workerFk`),
CONSTRAINT `packingSiteAdvanced_FK` FOREIGN KEY (`ticketFk`) REFERENCES `ticket` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `packingSiteAdvanced_FK_1` FOREIGN KEY (`workerFk`) REFERENCES `worker` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('PackingSiteAdvanced', '*', '*', 'ALLOW', 'ROLE', 'production');
DROP PROCEDURE IF EXISTS `vn`.`packingSite_startCollection`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`packingSite_startCollection`(vSelf INT, vTicketFk INT)
proc: BEGIN
/**
* @param vSelf packingSite id
* @param vTicketFk A ticket id from the collection to start
*/
DECLARE vExists BOOL;
DECLARE vIsAdvanced BOOL;
DECLARE vNewCollectionFk INT;
DECLARE vOldCollectionFk INT;
DECLARE vIsPackingByOther BOOL;
SELECT id, collectionFk
INTO vExists, vOldCollectionFk
FROM packingSite
WHERE id = vSelf;
IF NOT vExists THEN
CALL util.throw('packingSiteNotExists');
END IF;
SELECT COUNT(*) > 0
INTO vIsAdvanced
FROM packingSiteAdvanced
WHERE ticketFk = vTicketFk;
IF vIsAdvanced THEN
LEAVE proc;
END IF;
SELECT collectionFk INTO vNewCollectionFk
FROM ticketCollection WHERE ticketFk = vTicketFk;
IF vOldCollectionFk IS NOT NULL
AND vOldCollectionFk <> vNewCollectionFk THEN
SELECT COUNT(*) > 0
INTO vIsPackingByOther
FROM packingSite
WHERE id <> vSelf
AND collectionFk = vOldCollectionFk;
IF NOT vIsPackingByOther AND NOT collection_isPacked(vOldCollectionFk) THEN
CALL util.throw('cannotChangeCollection');
END IF;
END IF;
UPDATE packingSite SET collectionFk = vNewCollectionFk
WHERE id = vSelf;
END$$
DELIMITER ;

View File

@ -0,0 +1,11 @@
CREATE TABLE `salix`.`accessTokenConfig` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`renewPeriod` int(10) unsigned DEFAULT NULL,
`courtesyTime` int(10) unsigned DEFAULT NULL,
`renewInterval` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT IGNORE INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `courtesyTime`, `renewInterval`)
VALUES
(1, 21600, 5, 300);

View File

@ -0,0 +1,13 @@
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
VALUES
('InvoiceOut','makePdfAndNotify','WRITE','ALLOW','ROLE','invoicing'),
('InvoiceOutConfig','*','READ','ALLOW','ROLE','invoicing');
CREATE OR REPLACE TABLE `vn`.`invoiceOutConfig` (
id INT UNSIGNED auto_increment NOT NULL,
parallelism int UNSIGNED DEFAULT 1 NOT NULL,
PRIMARY KEY (id)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8mb3
COLLATE=utf8mb3_unicode_ci;

View File

@ -0,0 +1,13 @@
UPDATE `salix`.`ACL`
SET principalId='financialBoss'
WHERE
model = 'Client'
AND property = 'editCredit';
UPDATE `salix`.`ACL`
SET property='zeroCreditEditor'
WHERE
model = 'Client'
AND property = 'isNotEditableCredit';

File diff suppressed because one or more lines are too long

View File

@ -144,17 +144,17 @@ INSERT INTO `vn`.`currency`(`id`, `code`, `name`, `ratio`)
(3, 'GBP', 'Libra', 1), (3, 'GBP', 'Libra', 1),
(4, 'JPY', 'Yen Japones', 1); (4, 'JPY', 'Yen Japones', 1);
INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`, `continentFk`, `hasDailyInvoice`, `CEE`, `politicalCountryFk`) INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`, `continentFk`, `hasDailyInvoice`, `CEE`)
VALUES VALUES
(1, 'España', 1, 'ES', 1, 24, 4, 0, 1, 1), (1, 'España', 1, 'ES', 1, 24, 4, 0, 1),
(2, 'Italia', 1, 'IT', 1, 27, 4, 0, 1, 2), (2, 'Italia', 1, 'IT', 1, 27, 4, 0, 1),
(3, 'Alemania', 1, 'DE', 1, 22, 4, 0, 1, 3), (3, 'Alemania', 1, 'DE', 1, 22, 4, 0, 1),
(4, 'Rumania', 1, 'RO', 1, 24, 4, 0, 1, 4), (4, 'Rumania', 1, 'RO', 1, 24, 4, 0, 1),
(5, 'Holanda', 1, 'NL', 1, 18, 4, 0, 1, 5), (5, 'Holanda', 1, 'NL', 1, 18, 4, 0, 1),
(8, 'Portugal', 1, 'PT', 1, 27, 4, 0, 1, 8), (8, 'Portugal', 1, 'PT', 1, 27, 4, 0, 1),
(13,'Ecuador', 0, 'EC', 1, 24, 2, 1, 2, 13), (13,'Ecuador', 0, 'EC', 1, 24, 2, 1, 2),
(19,'Francia', 1, 'FR', 1, 27, 4, 0, 1, 19), (19,'Francia', 1, 'FR', 1, 27, 4, 0, 1),
(30,'Canarias', 1, 'IC', 1, 24, 4, 1, 2, 30); (30,'Canarias', 1, 'IC', 1, 24, 4, 1, 2);
INSERT INTO `vn`.`warehouseAlias`(`id`, `name`) INSERT INTO `vn`.`warehouseAlias`(`id`, `name`)
VALUES VALUES
@ -550,10 +550,13 @@ INSERT INTO `vn`.`supplierAddress`(`id`, `supplierFk`, `nickname`, `street`, `pr
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`, `commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`, `isPayMethodChecked`, `healthRegister`) INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`, `commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`, `isPayMethodChecked`, `healthRegister`)
VALUES VALUES
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, util.VN_CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1, '400664487V'), (1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, util.VN_CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1, '400664487V'),
(2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 0, util.VN_CURDATE(), 1, 'supplier address 2', 'GOTHAM', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals', 1, '400664487V'), (2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 0, util.VN_CURDATE(), 1, 'supplier address 2', 'GOTHAM', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals', 1, '400664487V'),
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, util.VN_CURDATE(), 1, 'supplier address 3', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'), (69, 'Packaging', 'Packaging nick', 4100000069, 1, '94935005K', 0, util.VN_CURDATE(), 1, 'supplier address 5', 'ASGARD', 3, 46600, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1, '400664487V'),
(1381, 'Ornamentales', 'Ornamentales', 7185000440, 1, '03815934E', 0, util.VN_CURDATE(), 1, 'supplier address 4', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'); (442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, util.VN_CURDATE(), 1, 'supplier address 3', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'),
(567, 'Holland', 'Holland nick', 4000020567, 1, '14364089Z', 0, util.VN_CURDATE(), 1, 'supplier address 6', 'ASGARD', 3, 46600, 1, 2, 10, 93, 2, 8, 18, 'animals', 1, '400664487V'),
(791, 'Bros SL', 'Bros nick', 5115000791, 1, '37718083S', 0, util.VN_CURDATE(), 1, 'supplier address 7', 'ASGARD', 3, 46600, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'),
(1381, 'Ornamentales', 'Ornamentales', 7185001381, 1, '07972486L', 0, util.VN_CURDATE(), 1, 'supplier address 4', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V');
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`) INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
VALUES VALUES
@ -603,6 +606,9 @@ UPDATE `vn`.`invoiceOut` SET ref = 'T3333333' WHERE id = 3;
UPDATE `vn`.`invoiceOut` SET ref = 'T4444444' WHERE id = 4; UPDATE `vn`.`invoiceOut` SET ref = 'T4444444' WHERE id = 4;
UPDATE `vn`.`invoiceOut` SET ref = 'A1111111' WHERE id = 5; UPDATE `vn`.`invoiceOut` SET ref = 'A1111111' WHERE id = 5;
INSERT INTO vn.invoiceOutConfig
SET parallelism = 8;
INSERT INTO `vn`.`invoiceOutTax` (`invoiceOutFk`, `taxableBase`, `vat`, `pgcFk`) INSERT INTO `vn`.`invoiceOutTax` (`invoiceOutFk`, `taxableBase`, `vat`, `pgcFk`)
VALUES VALUES
(1, 895.76, 89.58, 4722000010), (1, 895.76, 89.58, 4722000010),
@ -696,12 +702,12 @@ INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agen
INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `zonePrice`, `zoneBonus`, `created`) INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `zonePrice`, `zoneBonus`, `created`)
VALUES VALUES
(1 , 3, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Bat cave', 121, 'T1111111', 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)), (1 , 3, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Bat cave', 121, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
(2 , 1, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, 'T1111111', 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)), (2 , 1, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
(3 , 1, 7, 1, 6, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -2 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, 'T2222222', 0, 3, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH)), (3 , 1, 7, 1, 6, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -2 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH)),
(4 , 3, 2, 1, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -3 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, 'T3333333', 0, 9, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH)), (4 , 3, 2, 1, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -3 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 9, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH)),
(5 , 3, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -4 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, 'T4444444', 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH)), (5 , 3, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -4 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH)),
(6 , 1, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Mountain Drive Gotham', 1, 'A1111111', 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)), (6 , 1, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Mountain Drive Gotham', 1, NULL, 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
(7 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'Mountain Drive Gotham', 1, NULL, 0, 3, 5, 1, util.VN_CURDATE()), (7 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'Mountain Drive Gotham', 1, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
(8 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'Bat cave', 121, NULL, 0, 3, 5, 1, util.VN_CURDATE()), (8 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'Bat cave', 121, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
(9 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, util.VN_CURDATE()), (9 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
@ -2569,6 +2575,26 @@ INSERT INTO `vn`.`ticketRecalc`(`ticketFk`)
CALL `vn`.`ticket_doRecalc`(); CALL `vn`.`ticket_doRecalc`();
UPDATE `vn`.`ticket`
SET refFk = 'T1111111'
WHERE id IN (1,2);
UPDATE `vn`.`ticket`
SET refFk = 'T2222222'
WHERE id = 3;
UPDATE `vn`.`ticket`
SET refFk = 'T3333333'
WHERE id = 4;
UPDATE `vn`.`ticket`
SET refFk = 'T4444444'
WHERE id = 5;
UPDATE `vn`.`ticket`
SET refFk = 'A1111111'
WHERE id = 6;
INSERT INTO `vn`.`zoneAgencyMode`(`id`, `agencyModeFk`, `zoneFk`) INSERT INTO `vn`.`zoneAgencyMode`(`id`, `agencyModeFk`, `zoneFk`)
VALUES VALUES
(1, 1, 1), (1, 1, 1),
@ -2576,7 +2602,7 @@ INSERT INTO `vn`.`zoneAgencyMode`(`id`, `agencyModeFk`, `zoneFk`)
(3, 6, 5), (3, 6, 5),
(4, 7, 1); (4, 7, 1);
INSERT INTO `vn`.`expeditionTruck` (`id`, `ETD`, `description`) INSERT INTO `vn`.`expeditionTruck` (`id`, `eta`, `description`)
VALUES VALUES
(1, CONCAT(YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL +3 YEAR))), 'Best truck in fleet'); (1, CONCAT(YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL +3 YEAR))), 'Best truck in fleet');
@ -2736,7 +2762,8 @@ INSERT INTO `util`.`notification` (`id`, `name`, `description`)
(1, 'print-email', 'notification fixture one'), (1, 'print-email', 'notification fixture one'),
(2, 'invoice-electronic', 'A electronic invoice has been generated'), (2, 'invoice-electronic', 'A electronic invoice has been generated'),
(3, 'not-main-printer-configured', 'A printer distinct than main has been configured'), (3, 'not-main-printer-configured', 'A printer distinct than main has been configured'),
(4, 'supplier-pay-method-update', 'A supplier pay method has been updated'); (4, 'supplier-pay-method-update', 'A supplier pay method has been updated'),
(5, 'modified-entry', 'An entry has been modified');
INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`) INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)
VALUES VALUES
@ -2838,7 +2865,8 @@ INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`, `payMethodFk
INSERT INTO `vn`.`ticketRefund`(`refundTicketFk`, `originalTicketFk`) INSERT INTO `vn`.`ticketRefund`(`refundTicketFk`, `originalTicketFk`)
VALUES VALUES
(1, 12); (1, 12),
(8, 10);
INSERT INTO `vn`.`deviceProductionModels` (`code`) INSERT INTO `vn`.`deviceProductionModels` (`code`)
VALUES VALUES
@ -2894,6 +2922,10 @@ INSERT INTO `vn`.`wagonTypeTray` (`id`, `typeFk`, `height`, `colorFk`)
(2, 1, 50, 2), (2, 1, 50, 2),
(3, 1, 0, 3); (3, 1, 0, 3);
INSERT INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `renewInterval`)
VALUES
(1, 21600, 300);
INSERT INTO `vn`.`travelConfig` (`id`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `companyFk`) INSERT INTO `vn`.`travelConfig` (`id`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `companyFk`)
VALUES VALUES
(1, 1, 1, 1, 442); (1, 1, 1, 1, 442);

File diff suppressed because it is too large Load Diff

View File

@ -311,9 +311,9 @@ export default {
}, },
clientDefaulter: { clientDefaulter: {
anyClient: 'vn-client-defaulter tbody > tr', anyClient: 'vn-client-defaulter tbody > tr',
firstClientName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(2) > span', firstClientName: 'vn-client-defaulter tbody > tr:nth-child(2) > td:nth-child(2) > span',
firstSalesPersonName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(3) > span', firstSalesPersonName: 'vn-client-defaulter tbody > tr:nth-child(2) > td:nth-child(4) > span',
firstObservation: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(8) > vn-textarea[ng-model="defaulter.observation"]', firstObservation: 'vn-client-defaulter tbody > tr:nth-child(2) > td:nth-child(9) > vn-textarea[ng-model="defaulter.observation"]',
allDefaulterCheckbox: 'vn-client-defaulter thead vn-multi-check', allDefaulterCheckbox: 'vn-client-defaulter thead vn-multi-check',
addObservationButton: 'vn-client-defaulter vn-button[icon="icon-notes"]', addObservationButton: 'vn-client-defaulter vn-button[icon="icon-notes"]',
observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]', observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]',
@ -334,15 +334,15 @@ export default {
}, },
itemsIndex: { itemsIndex: {
createItemButton: `vn-float-button`, createItemButton: `vn-float-button`,
firstSearchResult: 'vn-item-index tbody tr:nth-child(1)', firstSearchResult: 'vn-item-index tbody tr:nth-child(2)',
searchResult: 'vn-item-index tbody tr:not(.empty-rows)', searchResult: 'vn-item-index tbody tr:not(.empty-rows)',
firstResultPreviewButton: 'vn-item-index tbody > :nth-child(1) .buttons > [icon="preview"]', firstResultPreviewButton: 'vn-item-index tbody > :nth-child(2) .buttons > [icon="preview"]',
searchResultCloneButton: 'vn-item-index .buttons > [icon="icon-clone"]', searchResultCloneButton: 'vn-item-index .buttons > [icon="icon-clone"]',
acceptClonationAlertButton: '.vn-confirm.shown [response="accept"]', acceptClonationAlertButton: '.vn-confirm.shown [response="accept"]',
closeItemSummaryPreview: '.vn-popup.shown', closeItemSummaryPreview: '.vn-popup.shown',
shownColumns: 'vn-item-index vn-button[id="shownColumns"]', shownColumns: 'vn-item-index vn-button[id="shownColumns"]',
shownColumnsList: '.vn-popover.shown .content', shownColumnsList: '.vn-popover.shown .content',
firstItemImage: 'vn-item-index tbody > tr:nth-child(1) > td:nth-child(1) > img', firstItemImage: 'vn-item-index tbody > tr:nth-child(2) > td:nth-child(1) > img',
firstItemImageTd: 'vn-item-index smart-table tr:nth-child(1) td:nth-child(1)', firstItemImageTd: 'vn-item-index smart-table tr:nth-child(1) td:nth-child(1)',
firstItemId: 'vn-item-index tbody > tr:nth-child(1) > td:nth-child(2)', firstItemId: 'vn-item-index tbody > tr:nth-child(1) > td:nth-child(2)',
idCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Identifier"]', idCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Identifier"]',
@ -479,9 +479,6 @@ export default {
fourthBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(4) > vn-td.balance > span', fourthBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(4) > vn-td.balance > span',
firstBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(1) > vn-td.balance' firstBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(1) > vn-td.balance'
}, },
itemLog: {
anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr',
},
ticketSummary: { ticketSummary: {
header: 'vn-ticket-summary > vn-card > h5', header: 'vn-ticket-summary > vn-card > h5',
state: 'vn-ticket-summary vn-label-value[label="State"] > section > span', state: 'vn-ticket-summary vn-label-value[label="State"] > section > span',
@ -523,11 +520,11 @@ export default {
searchResultDate: 'vn-ticket-summary [label=Landed] span', searchResultDate: 'vn-ticket-summary [label=Landed] span',
topbarSearch: 'vn-searchbar', topbarSearch: 'vn-searchbar',
moreMenu: 'vn-ticket-index vn-icon-button[icon=more_vert]', moreMenu: 'vn-ticket-index vn-icon-button[icon=more_vert]',
fourthWeeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(4)', fourthWeeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(5)',
fiveWeeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(5)', fiveWeeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(6)',
weeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table table tbody tr', weeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table table tbody tr',
firstWeeklyTicketDeleteIcon: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(1) vn-icon-button[icon="delete"]', firstWeeklyTicketDeleteIcon: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(2) vn-icon-button[icon="delete"]',
firstWeeklyTicketAgency: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(1) [ng-model="weekly.agencyModeFk"]', firstWeeklyTicketAgency: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(2) [ng-model="weekly.agencyModeFk"]',
acceptDeleteTurn: '.vn-confirm.shown button[response="accept"]' acceptDeleteTurn: '.vn-confirm.shown button[response="accept"]'
}, },
createTicketView: { createTicketView: {
@ -572,8 +569,8 @@ export default {
submitNotesButton: 'button[type=submit]' submitNotesButton: 'button[type=submit]'
}, },
ticketExpedition: { ticketExpedition: {
firstSaleCheckbox: 'vn-ticket-expedition tr:nth-child(1) vn-check[ng-model="expedition.checked"]', firstSaleCheckbox: 'vn-ticket-expedition tr:nth-child(2) vn-check[ng-model="expedition.checked"]',
thirdSaleCheckbox: 'vn-ticket-expedition tr:nth-child(3) vn-check[ng-model="expedition.checked"]', thirdSaleCheckbox: 'vn-ticket-expedition tr:nth-child(4) vn-check[ng-model="expedition.checked"]',
deleteExpeditionButton: 'vn-ticket-expedition slot-actions > vn-button[icon="delete"]', deleteExpeditionButton: 'vn-ticket-expedition slot-actions > vn-button[icon="delete"]',
moveExpeditionButton: 'vn-ticket-expedition slot-actions > vn-button[icon="keyboard_arrow_down"]', moveExpeditionButton: 'vn-ticket-expedition slot-actions > vn-button[icon="keyboard_arrow_down"]',
moreMenuWithoutRoute: 'vn-item[name="withoutRoute"]', moreMenuWithoutRoute: 'vn-item[name="withoutRoute"]',
@ -667,15 +664,6 @@ export default {
thirdRemoveRequestButton: 'vn-ticket-request-index vn-tr:nth-child(3) vn-icon[icon="delete"]', thirdRemoveRequestButton: 'vn-ticket-request-index vn-tr:nth-child(3) vn-icon[icon="delete"]',
thirdRequestQuantity: 'vn-ticket-request-index vn-table vn-tr:nth-child(3) > vn-td:nth-child(6) vn-input-number', thirdRequestQuantity: 'vn-ticket-request-index vn-table vn-tr:nth-child(3) > vn-td:nth-child(6) vn-input-number',
saveButton: 'vn-ticket-request-create button[type=submit]', saveButton: 'vn-ticket-request-create button[type=submit]',
},
ticketLog: {
firstTD: 'vn-ticket-log vn-table vn-td:nth-child(1)',
logButton: 'vn-left-menu a[ui-sref="ticket.card.log"]',
user: 'vn-ticket-log vn-tbody vn-tr vn-td:nth-child(2)',
action: 'vn-ticket-log vn-tbody vn-tr vn-td:nth-child(4)',
changes: 'vn-ticket-log vn-data-viewer vn-tbody vn-tr table tr:nth-child(2) td.after',
id: 'vn-ticket-log vn-tr:nth-child(1) table tr:nth-child(1) td.before'
}, },
ticketService: { ticketService: {
addServiceButton: 'vn-ticket-service vn-icon-button[vn-tooltip="Add service"] > button', addServiceButton: 'vn-ticket-service vn-icon-button[vn-tooltip="Add service"] > button',
@ -712,7 +700,7 @@ export default {
problems: 'vn-check[label="With problems"]', problems: 'vn-check[label="With problems"]',
tableButtonSearch: 'vn-button[vn-tooltip="Search"]', tableButtonSearch: 'vn-button[vn-tooltip="Search"]',
moveButton: 'vn-button[vn-tooltip="Future tickets"]', moveButton: 'vn-button[vn-tooltip="Future tickets"]',
firstCheck: 'tbody > tr:nth-child(1) > td > vn-check', firstCheck: 'tbody > tr:nth-child(2) > td > vn-check',
multiCheck: 'vn-multi-check', multiCheck: 'vn-multi-check',
tableId: 'vn-textfield[name="id"]', tableId: 'vn-textfield[name="id"]',
tableFutureId: 'vn-textfield[name="futureId"]', tableFutureId: 'vn-textfield[name="futureId"]',
@ -736,7 +724,7 @@ export default {
tableButtonSearch: 'vn-button[vn-tooltip="Search"]', tableButtonSearch: 'vn-button[vn-tooltip="Search"]',
moveButton: 'vn-button[vn-tooltip="Advance tickets"]', moveButton: 'vn-button[vn-tooltip="Advance tickets"]',
acceptButton: '.vn-confirm.shown button[response="accept"]', acceptButton: '.vn-confirm.shown button[response="accept"]',
firstCheck: 'tbody > tr:nth-child(1) > td > vn-check', firstCheck: 'tbody > tr:nth-child(2) > td > vn-check',
tableId: 'vn-textfield[name="id"]', tableId: 'vn-textfield[name="id"]',
tableFutureId: 'vn-textfield[name="futureId"]', tableFutureId: 'vn-textfield[name="futureId"]',
tableLiters: 'vn-textfield[name="liters"]', tableLiters: 'vn-textfield[name="liters"]',
@ -810,7 +798,7 @@ export default {
claimAction: { claimAction: {
importClaimButton: 'vn-claim-action vn-button[label="Import claim"]', importClaimButton: 'vn-claim-action vn-button[label="Import claim"]',
anyLine: 'vn-claim-action vn-tbody > vn-tr', anyLine: 'vn-claim-action vn-tbody > vn-tr',
firstDeleteLine: 'vn-claim-action tr:nth-child(1) vn-icon-button[icon="delete"]', firstDeleteLine: 'vn-claim-action tr:nth-child(2) vn-icon-button[icon="delete"]',
isPaidWithManaCheckbox: 'vn-claim-action vn-check[ng-model="$ctrl.claim.isChargedToMana"]' isPaidWithManaCheckbox: 'vn-claim-action vn-check[ng-model="$ctrl.claim.isChargedToMana"]'
}, },
ordersIndex: { ordersIndex: {
@ -1179,8 +1167,6 @@ export default {
allBuyCheckbox: 'vn-entry-buy-index thead vn-check', allBuyCheckbox: 'vn-entry-buy-index thead vn-check',
firstBuyCheckbox: 'vn-entry-buy-index tbody:nth-child(2) vn-check', firstBuyCheckbox: 'vn-entry-buy-index tbody:nth-child(2) vn-check',
deleteBuysButton: 'vn-entry-buy-index vn-button[icon="delete"]', deleteBuysButton: 'vn-entry-buy-index vn-button[icon="delete"]',
addBuyButton: 'vn-entry-buy-index vn-icon[icon="add"]',
secondBuyPackingPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.price3"]',
secondBuyGroupingPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.price2"]', secondBuyGroupingPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.price2"]',
secondBuyPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.buyingValue"]', secondBuyPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.buyingValue"]',
secondBuyGrouping: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.grouping"]', secondBuyGrouping: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.grouping"]',
@ -1216,7 +1202,7 @@ export default {
addTagButton: 'vn-icon-button[vn-tooltip="Add tag"]', addTagButton: 'vn-icon-button[vn-tooltip="Add tag"]',
itemTagInput: 'vn-autocomplete[ng-model="itemTag.tagFk"]', itemTagInput: 'vn-autocomplete[ng-model="itemTag.tagFk"]',
itemTagValueInput: 'vn-autocomplete[ng-model="itemTag.value"]', itemTagValueInput: 'vn-autocomplete[ng-model="itemTag.value"]',
firstBuy: 'vn-entry-latest-buys tbody > tr:nth-child(1)', firstBuy: 'vn-entry-latest-buys tbody > tr:nth-child(2)',
allBuysCheckBox: 'vn-entry-latest-buys thead vn-check', allBuysCheckBox: 'vn-entry-latest-buys thead vn-check',
secondBuyCheckBox: 'vn-entry-latest-buys tbody tr:nth-child(2) vn-check[ng-model="buy.checked"]', secondBuyCheckBox: 'vn-entry-latest-buys tbody tr:nth-child(2) vn-check[ng-model="buy.checked"]',
editBuysButton: 'vn-entry-latest-buys vn-button[icon="edit"]', editBuysButton: 'vn-entry-latest-buys vn-button[icon="edit"]',

View File

@ -19,15 +19,14 @@ describe('SmartTable SearchBar integration', () => {
await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton); await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium'); await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium');
await page.waitToClick(selectors.itemsIndex.advancedSearchButton); await page.waitToClick(selectors.itemsIndex.advancedSearchButton);
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 3); await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 4);
await page.reload({ await page.reload({
waitUntil: 'networkidle2' waitUntil: 'networkidle2'
}); });
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 3); await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 4);
await page.waitToClick(selectors.itemsIndex.advancedSmartTableButton);
await page.write(selectors.itemsIndex.advancedSmartTableGrouping, '1'); await page.write(selectors.itemsIndex.advancedSmartTableGrouping, '1');
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2); await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2);
@ -36,7 +35,7 @@ describe('SmartTable SearchBar integration', () => {
waitUntil: 'networkidle2' waitUntil: 'networkidle2'
}); });
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2); await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1);
}); });
it('should filter in section without smart-table and search in searchBar go to zone section', async() => { it('should filter in section without smart-table and search in searchBar go to zone section', async() => {

View File

@ -5,8 +5,8 @@ const $ = {
userName: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.name"]', userName: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.name"]',
email: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.email"]', email: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.email"]',
saveButton: 'vn-client-web-access button[type=submit]', saveButton: 'vn-client-web-access button[type=submit]',
nameValue: 'vn-client-log .change:nth-child(1) .basic-json:nth-child(2) vn-json-value', nameValue: 'vn-client-log .changes-log:nth-child(2) .basic-json:nth-child(2) vn-json-value',
activeValue: 'vn-client-log .change:nth-child(2) .basic-json:nth-child(1) vn-json-value' activeValue: 'vn-client-log .changes-log:nth-child(3) .basic-json:nth-child(1) vn-json-value'
}; };
describe('Client web access path', () => { describe('Client web access path', () => {
@ -39,9 +39,9 @@ describe('Client web access path', () => {
const userName = await page.getValue($.userName); const userName = await page.getValue($.userName);
const email = await page.getValue($.email); const email = await page.getValue($.email);
await page.accessToSection('client.card.log'); // await page.accessToSection('client.card.log');
const logName = await page.innerText($.nameValue); // const logName = await page.innerText($.nameValue);
const logActive = await page.innerText($.activeValue); // const logActive = await page.innerText($.activeValue);
expect(enableMessage.type).toBe('success'); expect(enableMessage.type).toBe('success');
expect(modifyMessage.type).toBe('success'); expect(modifyMessage.type).toBe('success');
@ -50,7 +50,7 @@ describe('Client web access path', () => {
expect(userName).toEqual('Legion'); expect(userName).toEqual('Legion');
expect(email).toEqual('legion@marvel.com'); expect(email).toEqual('legion@marvel.com');
expect(logName).toEqual('Legion'); // expect(logName).toEqual('Legion');
expect(logActive).toEqual('✗'); // expect(logActive).toEqual('✗');
}); });
}); });

View File

@ -19,7 +19,7 @@ describe('Client defaulter path', () => {
it('should count the amount of clients in the turns section', async() => { it('should count the amount of clients in the turns section', async() => {
const result = await page.countElement(selectors.clientDefaulter.anyClient); const result = await page.countElement(selectors.clientDefaulter.anyClient);
expect(result).toEqual(5); expect(result).toEqual(6);
}); });
it('should check contain expected client', async() => { it('should check contain expected client', async() => {

View File

@ -53,7 +53,7 @@ describe('Worker create path', () => {
expect(message.text).toContain('Data saved!'); expect(message.text).toContain('Data saved!');
// 'rollback' // 'rollback'
await page.loginAndModule('sysadmin', 'account'); await page.loginAndModule('itManagement', 'account');
await page.accessToSearchResult(newWorker); await page.accessToSearchResult(newWorker);
await page.waitToClick(selectors.accountDescriptor.menuButton); await page.waitToClick(selectors.accountDescriptor.menuButton);

View File

@ -18,11 +18,11 @@ describe('Item summary path', () => {
await page.doSearch('Ranged weapon'); await page.doSearch('Ranged weapon');
const resultsCount = await page.countElement(selectors.itemsIndex.searchResult); const resultsCount = await page.countElement(selectors.itemsIndex.searchResult);
await page.waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon'); await page.waitForTextInElement(selectors.itemsIndex.firstSearchResult, 'Ranged weapon');
await page.waitToClick(selectors.itemsIndex.firstResultPreviewButton); await page.waitToClick(selectors.itemsIndex.firstResultPreviewButton);
const isVisible = await page.isVisible(selectors.itemSummary.basicData); const isVisible = await page.isVisible(selectors.itemSummary.basicData);
expect(resultsCount).toBe(3); expect(resultsCount).toBe(4);
expect(isVisible).toBeTruthy(); expect(isVisible).toBeTruthy();
}); });
@ -66,7 +66,7 @@ describe('Item summary path', () => {
await page.waitToClick(selectors.itemsIndex.firstResultPreviewButton); await page.waitToClick(selectors.itemsIndex.firstResultPreviewButton);
await page.waitForSelector(selectors.itemSummary.basicData, {visible: true}); await page.waitForSelector(selectors.itemSummary.basicData, {visible: true});
expect(resultsCount).toBe(2); expect(resultsCount).toBe(3);
}); });
it(`should now check the item summary preview shows fields from basic data`, async() => { it(`should now check the item summary preview shows fields from basic data`, async() => {

View File

@ -19,7 +19,6 @@ describe('Item edit tax path', () => {
it(`should add the item tax to all countries`, async() => { it(`should add the item tax to all countries`, async() => {
await page.autocompleteSearch(selectors.itemTax.firstClass, 'General VAT'); await page.autocompleteSearch(selectors.itemTax.firstClass, 'General VAT');
await page.autocompleteSearch(selectors.itemTax.secondClass, 'General VAT'); await page.autocompleteSearch(selectors.itemTax.secondClass, 'General VAT');
await page.autocompleteSearch(selectors.itemTax.thirdClass, 'General VAT');
await page.waitToClick(selectors.itemTax.submitTaxButton); await page.waitToClick(selectors.itemTax.submitTaxButton);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
@ -40,13 +39,6 @@ describe('Item edit tax path', () => {
expect(secondVatType).toEqual('General VAT'); expect(secondVatType).toEqual('General VAT');
}); });
it(`should confirm the third item tax class was edited`, async() => {
const thirdVatType = await page
.waitToGetProperty(selectors.itemTax.thirdClass, 'value');
expect(thirdVatType).toEqual('General VAT');
});
it(`should edit the first class without saving the form`, async() => { it(`should edit the first class without saving the form`, async() => {
await page.autocompleteSearch(selectors.itemTax.firstClass, 'Reduced VAT'); await page.autocompleteSearch(selectors.itemTax.firstClass, 'Reduced VAT');
const firstVatType = await page.waitToGetProperty(selectors.itemTax.firstClass, 'value'); const firstVatType = await page.waitToGetProperty(selectors.itemTax.firstClass, 'value');

View File

@ -18,7 +18,7 @@ describe('Item log path', () => {
await page.doSearch('Knowledge artifact'); await page.doSearch('Knowledge artifact');
const nResults = await page.countElement(selectors.itemsIndex.searchResult); const nResults = await page.countElement(selectors.itemsIndex.searchResult);
expect(nResults).toEqual(0); expect(nResults).toEqual(1);
}); });
it('should access to the create item view by clicking the create floating button', async() => { it('should access to the create item view by clicking the create floating button', async() => {

View File

@ -27,6 +27,6 @@ describe('Ticket expeditions and log path', () => {
const result = await page const result = await page
.countElement(selectors.ticketExpedition.expeditionRow); .countElement(selectors.ticketExpedition.expeditionRow);
expect(result).toEqual(3); expect(result).toEqual(4);
}); });
}); });

View File

@ -19,7 +19,7 @@ describe('Ticket descriptor path', () => {
it('should count the amount of tickets in the turns section', async() => { it('should count the amount of tickets in the turns section', async() => {
const result = await page.countElement(selectors.ticketsIndex.weeklyTicket); const result = await page.countElement(selectors.ticketsIndex.weeklyTicket);
expect(result).toEqual(6); expect(result).toEqual(7);
}); });
it('should go back to the ticket index then search and access a ticket summary', async() => { it('should go back to the ticket index then search and access a ticket summary', async() => {
@ -89,7 +89,7 @@ describe('Ticket descriptor path', () => {
await page.doSearch('11'); await page.doSearch('11');
const nResults = await page.countElement(selectors.ticketsIndex.searchWeeklyResult); const nResults = await page.countElement(selectors.ticketsIndex.searchWeeklyResult);
expect(nResults).toEqual(1); expect(nResults).toEqual(2);
}); });
it('should delete the weekly ticket 11', async() => { it('should delete the weekly ticket 11', async() => {
@ -104,7 +104,7 @@ describe('Ticket descriptor path', () => {
await page.doSearch(); await page.doSearch();
const nResults = await page.countElement(selectors.ticketsIndex.searchWeeklyResult); const nResults = await page.countElement(selectors.ticketsIndex.searchWeeklyResult);
expect(nResults).toEqual(6); expect(nResults).toEqual(7);
}); });
it('should update the agency then remove it afterwards', async() => { it('should update the agency then remove it afterwards', async() => {

View File

@ -29,7 +29,7 @@ describe('Ticket expeditions', () => {
const result = await page const result = await page
.countElement(selectors.ticketExpedition.expeditionRow); .countElement(selectors.ticketExpedition.expeditionRow);
expect(result).toEqual(1); expect(result).toEqual(2);
}); });
it(`should move one expedition to new ticket with route`, async() => { it(`should move one expedition to new ticket with route`, async() => {
@ -45,6 +45,6 @@ describe('Ticket expeditions', () => {
const result = await page const result = await page
.countElement(selectors.ticketExpedition.expeditionRow); .countElement(selectors.ticketExpedition.expeditionRow);
expect(result).toEqual(1); expect(result).toEqual(2);
}); });
}); });

View File

@ -87,7 +87,7 @@ describe('Ticket Future path', () => {
await page.clearInput(selectors.ticketFuture.futureState); await page.clearInput(selectors.ticketFuture.futureState);
await page.waitToClick(selectors.ticketFuture.submit); await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.searchResult, 4); await page.waitForNumberOfElements(selectors.ticketFuture.searchResult, 5);
await page.waitToClick(selectors.ticketFuture.multiCheck); await page.waitToClick(selectors.ticketFuture.multiCheck);
await page.waitToClick(selectors.ticketFuture.firstCheck); await page.waitToClick(selectors.ticketFuture.firstCheck);
await page.waitToClick(selectors.ticketFuture.moveButton); await page.waitToClick(selectors.ticketFuture.moveButton);

View File

@ -9,7 +9,7 @@ describe('Travel descriptor path', () => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('buyer', 'travel'); await page.loginAndModule('buyer', 'travel');
await page.write(selectors.travelIndex.generalSearchFilter, '1'); await page.write(selectors.travelIndex.generalSearchFilter, '3');
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');
await page.waitForState('travel.card.summary'); await page.waitForState('travel.card.summary');
}); });
@ -23,7 +23,7 @@ describe('Travel descriptor path', () => {
await page.waitForState('travel.index'); await page.waitForState('travel.index');
const result = await page.countElement(selectors.travelIndex.anySearchResult); const result = await page.countElement(selectors.travelIndex.anySearchResult);
expect(result).toBeGreaterThanOrEqual(7); expect(result).toBeGreaterThanOrEqual(1);
}); });
it('should navigate to the first search result', async() => { it('should navigate to the first search result', async() => {

View File

@ -66,97 +66,4 @@ describe('Entry import, create and edit buys path', () => {
await page.waitToClick(selectors.globalItems.acceptButton); await page.waitToClick(selectors.globalItems.acceptButton);
await page.waitForNumberOfElements(selectors.entryBuys.anyBuyLine, 1); await page.waitForNumberOfElements(selectors.entryBuys.anyBuyLine, 1);
}); });
it('should add a new buy', async() => {
await page.waitToClick(selectors.entryBuys.addBuyButton);
await page.write(selectors.entryBuys.secondBuyPackingPrice, '999');
await page.write(selectors.entryBuys.secondBuyGroupingPrice, '999');
await page.write(selectors.entryBuys.secondBuyPrice, '999');
await page.write(selectors.entryBuys.secondBuyGrouping, '999');
await page.write(selectors.entryBuys.secondBuyPacking, '999');
await page.write(selectors.entryBuys.secondBuyWeight, '999');
await page.write(selectors.entryBuys.secondBuyStickers, '999');
await page.autocompleteSearch(selectors.entryBuys.secondBuyPackage, '1');
await page.write(selectors.entryBuys.secondBuyQuantity, '999');
await page.autocompleteSearch(selectors.entryBuys.secondBuyItem, '1');
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
await page.waitForNumberOfElements(selectors.entryBuys.anyBuyLine, 2);
});
it('should edit the newest buy and check data', async() => {
await page.clearInput(selectors.entryBuys.secondBuyPackingPrice);
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyPackingPrice, '100');
await page.keyboard.press('Enter');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyGroupingPrice);
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyGroupingPrice, '200');
await page.keyboard.press('Enter');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyPrice);
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyPrice, '300');
await page.keyboard.press('Enter');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyGrouping);
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyGrouping, '400');
await page.keyboard.press('Enter');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyPacking);
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyPacking, '500');
await page.keyboard.press('Enter');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyWeight);
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyWeight, '600');
await page.keyboard.press('Enter');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyStickers);
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyStickers, '700');
await page.keyboard.press('Enter');
await page.waitForSnackbar();
await page.autocompleteSearch(selectors.entryBuys.secondBuyPackage, '94');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyQuantity);
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyQuantity, '800');
await page.keyboard.press('Enter');
await page.reloadSection('entry.card.buy.index');
const secondBuyPackingPrice = await page.getValue(selectors.entryBuys.secondBuyPackingPrice);
const secondBuyGroupingPrice = await page.getValue(selectors.entryBuys.secondBuyGroupingPrice);
const secondBuyPrice = await page.getValue(selectors.entryBuys.secondBuyPrice);
const secondBuyGrouping = await page.getValue(selectors.entryBuys.secondBuyGrouping);
const secondBuyPacking = await page.getValue(selectors.entryBuys.secondBuyPacking);
const secondBuyWeight = await page.getValue(selectors.entryBuys.secondBuyWeight);
const secondBuyStickers = await page.getValue(selectors.entryBuys.secondBuyStickers);
const secondBuyPackage = await page.getValue(selectors.entryBuys.secondBuyPackage);
const secondBuyQuantity = await page.getValue(selectors.entryBuys.secondBuyQuantity);
expect(secondBuyPackingPrice).toEqual('100');
expect(secondBuyGroupingPrice).toEqual('200');
expect(secondBuyPrice).toEqual('300');
expect(secondBuyGrouping).toEqual('400');
expect(secondBuyPacking).toEqual('500');
expect(secondBuyWeight).toEqual('600');
expect(secondBuyStickers).toEqual('700');
expect(secondBuyPackage).toEqual('94');
expect(secondBuyQuantity).toEqual('800');
});
}); });

View File

@ -8,7 +8,7 @@ describe('Account create and basic data path', () => {
beforeAll(async() => { beforeAll(async() => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('developer', 'account'); await page.loginAndModule('itManagement', 'account');
}); });
afterAll(async() => { afterAll(async() => {

View File

@ -81,6 +81,6 @@ describe('Account Role create and basic data path', () => {
await page.accessToSection('account.role.card.inherited'); await page.accessToSection('account.role.card.inherited');
const rolesCount = await page.countElement(selectors.accountRoleInheritance.anyResult); const rolesCount = await page.countElement(selectors.accountRoleInheritance.anyResult);
expect(rolesCount).toEqual(6); expect(rolesCount).toEqual(7);
}); });
}); });

View File

@ -4,8 +4,8 @@ vn-avatar {
display: block; display: block;
border-radius: 50%; border-radius: 50%;
overflow: hidden; overflow: hidden;
height: 36px; height: 38px;
width: 36px; width: 38px;
font-size: 22px; font-size: 22px;
background-color: $color-main; background-color: $color-main;
position: relative; position: relative;

View File

@ -40,6 +40,8 @@ export default class SmartTable extends Component {
this._options = options; this._options = options;
if (!options) return; if (!options) return;
options.defaultSearch = true;
if (options.defaultSearch) if (options.defaultSearch)
this.displaySearch(); this.displaySearch();

View File

@ -59,12 +59,13 @@ export default class Auth {
password: password || undefined password: password || undefined
}; };
const now = new Date();
return this.$http.post('VnUsers/signIn', params) return this.$http.post('VnUsers/signIn', params)
.then(json => this.onLoginOk(json, remember)); .then(json => this.onLoginOk(json, now, remember));
} }
onLoginOk(json, remember) { onLoginOk(json, now, remember) {
this.vnToken.set(json.data.token, remember); this.vnToken.set(json.data.token, now, json.data.ttl, remember);
return this.loadAcls().then(() => { return this.loadAcls().then(() => {
let continueHash = this.$state.params.continue; let continueHash = this.$state.params.continue;

View File

@ -11,3 +11,4 @@ import './report';
import './email'; import './email';
import './file'; import './file';
import './date'; import './date';

View File

@ -1,11 +1,16 @@
import ngModule from '../module'; import ngModule from '../module';
import HttpError from 'core/lib/http-error'; import HttpError from 'core/lib/http-error';
interceptor.$inject = ['$q', 'vnApp', 'vnToken', '$translate']; interceptor.$inject = ['$q', 'vnApp', '$translate'];
function interceptor($q, vnApp, vnToken, $translate) { function interceptor($q, vnApp, $translate) {
let apiPath = 'api/'; let apiPath = 'api/';
let token = sessionStorage.getItem('vnToken')
?? localStorage.getItem('vnToken');
return { return {
setToken(newToken) {
token = newToken;
},
setApiPath(path) { setApiPath(path) {
apiPath = path; apiPath = path;
}, },
@ -14,8 +19,8 @@ function interceptor($q, vnApp, vnToken, $translate) {
if (config.url.charAt(0) !== '/' && apiPath) if (config.url.charAt(0) !== '/' && apiPath)
config.url = `${apiPath}${config.url}`; config.url = `${apiPath}${config.url}`;
if (vnToken.token) if (token)
config.headers.Authorization = vnToken.token; config.headers.Authorization = token;
if ($translate.use()) if ($translate.use())
config.headers['Accept-Language'] = $translate.use(); config.headers['Accept-Language'] = $translate.use();
if (config.filter) { if (config.filter) {

View File

@ -6,29 +6,118 @@ import ngModule from '../module';
* @property {String} token The current login token or %null * @property {String} token The current login token or %null
*/ */
export default class Token { export default class Token {
constructor() { constructor(vnInterceptor, $http, $rootScope) {
Object.assign(this, {
vnInterceptor,
$http,
$rootScope
});
try { try {
this.token = sessionStorage.getItem('vnToken'); this.getStorage(sessionStorage);
if (!this.token) this.remember = true;
this.token = localStorage.getItem('vnToken');
if (!this.token) {
this.getStorage(localStorage);
this.remember = false;
}
} catch (e) {} } catch (e) {}
} }
set(value, remember) {
set(token, created, ttl, remember) {
this.unset(); this.unset();
Object.assign(this, {
token,
created,
ttl,
remember
});
this.vnInterceptor.setToken(token);
try { try {
if (remember) if (remember)
localStorage.setItem('vnToken', value); this.setStorage(localStorage, token, created, ttl);
else else
sessionStorage.setItem('vnToken', value); this.setStorage(sessionStorage, token, created, ttl);
} catch (e) {} } catch (err) {
console.error(err);
this.token = value; }
} }
unset() { unset() {
localStorage.removeItem('vnToken');
sessionStorage.removeItem('vnToken');
this.token = null; this.token = null;
this.created = null;
this.ttl = null;
this.remember = null;
this.vnInterceptor.setToken(null);
this.removeStorage(localStorage);
this.removeStorage(sessionStorage);
}
getStorage(storage) {
this.token = storage.getItem('vnToken');
if (!this.token) return;
const created = storage.getItem('vnTokenCreated');
this.created = created && new Date(created);
this.renewPeriod = storage.getItem('vnTokenRenewPeriod');
}
setStorage(storage, token, created, ttl) {
storage.setItem('vnToken', token);
storage.setItem('vnTokenCreated', created.toJSON());
storage.setItem('vnTokenTtl', ttl);
}
removeStorage(storage) {
storage.removeItem('vnToken');
storage.removeItem('vnTokenCreated');
storage.removeItem('vnTokenTtl');
}
fetchConfig() {
const filter = {fields: ['renewInterval', 'renewPeriod']};
this.$http.get('AccessTokenConfigs/findOne', {filter}).then(res => {
const data = res.data;
if (!data) return;
this.renewPeriod = data.renewPeriod;
this.stopRenewer();
this.inservalId = setInterval(() => this.checkValidity(), data.renewInterval * 1000);
this.checkValidity();
});
}
checkValidity() {
if (this.checking || !this.created) return;
this.checking = true;
const renewPeriod = Math.min(this.ttl, this.renewPeriod) * 1000;
const maxDate = this.created.getTime() + renewPeriod;
const now = new Date();
if (now.getTime() <= maxDate) {
this.checking = false;
return;
}
this.$http.post('VnUsers/renewToken')
.then(res => {
const token = res.data;
this.set(token.id, now, token.ttl, this.remember);
})
.catch(res => {
if (res.data?.error?.code !== 'periodNotExceeded')
throw res;
})
.finally(() => {
this.checking = false;
});
}
stopRenewer() {
clearInterval(this.inservalId);
} }
} }
Token.$inject = ['vnInterceptor', '$http', '$rootScope'];
ngModule.service('vnToken', Token); ngModule.service('vnToken', Token);

View File

@ -15,7 +15,7 @@ export default class Controller {
} }
$onInit() { $onInit() {
if (!this.$state.params || !this.$state.params.id || !this.$state.params.token) if (!this.$state.params.id)
this.$state.go('login'); this.$state.go('login');
this.$http.get('UserPasswords/findOne') this.$http.get('UserPasswords/findOne')
@ -25,7 +25,7 @@ export default class Controller {
} }
submit() { submit() {
const id = this.$state.params.id; const userId = this.$state.params.userId;
const newPassword = this.newPassword; const newPassword = this.newPassword;
const oldPassword = this.oldPassword; const oldPassword = this.oldPassword;
@ -35,12 +35,12 @@ export default class Controller {
throw new UserError(`Passwords don't match`); throw new UserError(`Passwords don't match`);
const headers = { const headers = {
Authorization: this.$state.params.token Authorization: this.$state.params.id
}; };
this.$http.post('VnUsers/change-password', this.$http.post('VnUsers/change-password',
{ {
id, id: userId,
oldPassword, oldPassword,
newPassword newPassword
}, },

View File

@ -42,7 +42,7 @@
<button class="buttonAccount"> <button class="buttonAccount">
<img <img
id="user" id="user"
ng-src="{{$ctrl.getImageUrl()}}" ng-src="{{::$ctrl.getImageUrl()}}"
ng-click="userPopover.show($event)" ng-click="userPopover.show($event)"
translate-attr="{title: 'Account'}" translate-attr="{title: 'Account'}"
on-error-src/> on-error-src/>
@ -93,4 +93,4 @@
</vn-list> </vn-list>
</vn-portal> </vn-portal>
<ui-view class="main-view"></ui-view> <ui-view class="main-view"></ui-view>
<vn-scroll-up></vn-scroll-up> <vn-scroll-up></vn-scroll-up>

View File

@ -30,6 +30,10 @@ export class Layout extends Component {
refresh() { refresh() {
window.location.reload(); window.location.reload();
} }
$onDestroy() {
this.vnToken.stopRenewer();
}
} }
Layout.$inject = ['$element', '$scope', 'vnModules']; Layout.$inject = ['$element', '$scope', 'vnModules'];

View File

@ -2,8 +2,6 @@
vn-id="model" vn-id="model"
url="{{$ctrl.url}}" url="{{$ctrl.url}}"
filter="$ctrl.filter" filter="$ctrl.filter"
link="{originFk: $ctrl.originId}"
where="{changedModel: $ctrl.changedModel, changedModelId: $ctrl.changedModelId}"
data="$ctrl.logs" data="$ctrl.logs"
order="creationDate DESC, id DESC" order="creationDate DESC, id DESC"
limit="20"> limit="20">
@ -17,90 +15,110 @@
<vn-data-viewer <vn-data-viewer
model="model" model="model"
class="vn-w-sm vn-px-sm vn-pb-xl"> class="vn-w-sm vn-px-sm vn-pb-xl">
<div class="change vn-mb-sm" ng-repeat="log in $ctrl.logs"> <div class="origin-log" ng-repeat="originLog in $ctrl.logTree">
<div class="left"> <div class="origin-info vn-mb-md" ng-if="::$ctrl.logTree.length > 1">
<vn-avatar class="vn-mt-xs" <h6 class="origin-id">
ng-class="::{system: !log.user}" {{::$ctrl.modelI18n}} #{{::originLog.originFk}}
val="{{::log.user ? log.user.nickname : $ctrl.$t('System')}}" </h6>
ng-click="$ctrl.showWorkerDescriptor($event, log)">
<img
ng-if="::log.user.image"
ng-src="/api/Images/user/160x160/{{::log.userFk}}/download?access_token={{::$ctrl.vnToken.token}}">
</img>
</vn-avatar>
<div class="arrow bg-panel"></div>
<div class="line"></div> <div class="line"></div>
</div> </div>
<vn-card class="detail"> <div class="user-log vn-mb-sm" ng-repeat="userLog in ::originLog.logs">
<div class="header vn-pa-sm"> <div class="timeline">
<div class="action-model"> <div class="user-avatar">
<span class="model-name" <vn-avatar
ng-if="::$ctrl.showModelName && log.changedModel" ng-class="::{system: !userLog.user}"
ng-style="::{backgroundColor: $ctrl.hashToColor(log.changedModel)}" val="{{::userLog.user ? userLog.user.nickname : $ctrl.$t('System')}}"
title="{{::log.changedModel}}"> ng-click="$ctrl.showWorkerDescriptor($event, userLog)">
{{::log.changedModelI18n}} <img
</span> ng-if="::userLog.user.image"
</div> ng-src="/api/Images/user/160x160/{{::userLog.userFk}}/download?access_token={{::$ctrl.vnToken.token}}">
<div </img>
class="action-date text-secondary text-caption vn-ml-sm" </vn-avatar>
title="{{::log.creationDate | date:'dd/MM/yyyy HH:mm:ss'}}">
{{::$ctrl.relativeDate(log.creationDate)}}
<vn-icon
class="action vn-ml-xs"
ng-class="::$ctrl.actionsClass[log.action]"
icon="{{::$ctrl.actionsIcon[log.action]}}"
translate-attr="::{title: $ctrl.actionsText[log.action]}">
</vn-icon>
</div> </div>
<div class="arrow bg-panel" ng-if="::$ctrl.byRecord"></div>
<div class="line"></div>
</div> </div>
<div class="model vn-pb-sm vn-px-sm" <div class="user-changes">
ng-if="::$ctrl.showModelName"> <div class="model-log" ng-repeat="modelLog in ::userLog.logs">
<span class="model-id" ng-if="::log.changedModelId">#{{::log.changedModelId}}</span> <div class="model-info vn-my-sm" ng-if="::!$ctrl.byRecord">
<vn-icon <vn-icon
icon="filter_alt" icon="filter_alt"
translate-attr="{title: 'Show all record changes'}" translate-attr="{title: 'Show all record changes'}"
ng-click="$ctrl.filterByEntity(log)"> ng-click="$ctrl.filterByRecord(modelLog)">
</vn-icon> </vn-icon>
<span class="model-value" title="{{::log.changedModelValue}}">{{::log.changedModelValue}}</span> <span class="model-name"
</div> ng-if="::$ctrl.showModelName && modelLog.model"
<div class="changes vn-pa-sm" ng-style="::{backgroundColor: $ctrl.hashToColor(modelLog.model)}"
ng-class="{expanded: log.expand}" title="{{::modelLog.model}}">
ng-if="::log.props.length || log.description"> {{::modelLog.modelI18n}}
<vn-icon
icon="expand_more"
translate-attr="{title: 'Details'}"
ng-click="log.expand = !log.expand">
</vn-icon>
<span ng-if="::log.props.length"
class="attributes">
<span ng-if="!log.expand" ng-repeat="prop in ::log.props"
class="basic-json">
<span class="json-field" title="{{::prop.name}}">
{{::prop.nameI18n}}:
</span> </span>
<vn-json-value value="::prop.val.val"></vn-json-value><span ng-if="::!$last">,</span> <span class="model-id" ng-if="::modelLog.id">#{{::modelLog.id}}</span>
</span> <span class="model-value" title="{{::modelLog.showValue}}">{{::modelLog.showValue}}</span>
<div ng-if="log.expand" class="expanded-json">
<div ng-repeat="prop in ::log.props">
<span class="json-field" title="{{::prop.name}}">
{{::prop.nameI18n}}:
</span>
<vn-log-value val="::prop.val"></vn-log-value>
<span ng-if="::log.action == 'update'">
<vn-log-value val="::prop.old"></vn-log-value>
</span>
</div>
</div> </div>
</span> <vn-card class="changes-log vn-mb-xs" ng-repeat="log in ::modelLog.logs">
<span ng-if="::!log.props.length" class="description"> <div class="change-info vn-pa-sm">
{{::log.description}} <div
</span> class="date text-secondary text-caption vn-mr-sm"
</vn-card> title="{{::log.creationDate | date:'dd/MM/yyyy HH:mm:ss'}}">
{{::$ctrl.relativeDate(log.creationDate)}}
</div>
<div>
<vn-icon
class="pit vn-ml-xs"
icon="preview"
translate-attr="::{title: 'View record at this point in time'}"
ng-show="::log.action != 'insert'"
ng-click="$ctrl.viewPitInstance($event, log.id, modelLog)">
</vn-icon>
<vn-icon
class="action vn-ml-xs"
ng-class="::$ctrl.actionsClass[log.action]"
icon="{{::$ctrl.actionsIcon[log.action]}}"
translate-attr="::{title: $ctrl.actionsText[log.action]}">
</vn-icon>
</div>
</div>
<div class="change-detail vn-pa-sm"
ng-class="{expanded: log.expand}"
ng-if="::log.props.length || log.description">
<vn-icon
icon="expand_more"
translate-attr="{title: 'Details'}"
ng-click="log.expand = !log.expand">
</vn-icon>
<span ng-if="::log.props.length"
class="attributes">
<span ng-if="!log.expand" ng-repeat="prop in ::log.props"
class="basic-json">
<span class="json-field" title="{{::prop.name}}">
{{::prop.nameI18n}}:
</span>
<vn-json-value value="::prop.val.val"></vn-json-value><span ng-if="::!$last">,</span>
</span>
<div ng-if="log.expand" class="expanded-json">
<div ng-repeat="prop in ::log.props">
<span class="json-field" title="{{::prop.name}}">
{{::prop.nameI18n}}:
</span>
<vn-log-value val="::prop.val"></vn-log-value>
<span ng-if="::log.action == 'update'">
<vn-log-value val="::prop.old"></vn-log-value>
</span>
</div>
</div>
</span>
<span ng-if="::!log.props.length" class="description">
{{::log.description}}
</span>
</vn-card>
</div>
</div>
</div>
</div> </div>
</div> </div>
</vn-data-viewer> </vn-data-viewer>
<vn-float-button <vn-float-button
ng-if="model.userFilter" ng-if="$ctrl.hasFilter"
icon="filter_alt_off" icon="filter_alt_off"
translate-attr="{title: 'Quit filter'}" translate-attr="{title: 'Quit filter'}"
ng-click="$ctrl.resetFilter()" ng-click="$ctrl.resetFilter()"
@ -212,5 +230,33 @@
</vn-date-picker> </vn-date-picker>
</form> </form>
</vn-side-menu> </vn-side-menu>
<vn-worker-descriptor-popover vn-id="workerDescriptor"> <vn-popover vn-id="instance-popover">
<tpl-body class="vn-log-instance">
<vn-spinner
ng-if="$ctrl.instance.canceler"
class="loading vn-pa-sm"
enable="true">
</vn-spinner>
<div
ng-if="!$ctrl.instance.canceler" class="instance">
<h6 class="header vn-pa-sm">
{{$ctrl.instance.modelLog.modelI18n}} #{{$ctrl.instance.modelLog.id}}
</h6>
<div class="change-detail vn-pa-sm">
<div ng-if="$ctrl.instance.props"
ng-repeat="prop in $ctrl.instance.props">
<span class="json-field" title="{{::prop.name}}">
{{::prop.nameI18n}}:
</span>
<vn-log-value val="::prop.val"></vn-log-value>
</div>
<div ng-if="!$ctrl.instance.props" translate>
No data
</div>
</div>
</div>
</tpl-body>
</vn-popover>
<vn-worker-descriptor-popover
vn-id="worker-descriptor">
</vn-worker-descriptor-popover> </vn-worker-descriptor-popover>

View File

@ -3,7 +3,10 @@ import Section from '../section';
import {hashToColor} from 'core/lib/string'; import {hashToColor} from 'core/lib/string';
import './style.scss'; import './style.scss';
const validDate = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/; const validDate = new RegExp(
/^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])/.source
+ /T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/.source
);
export default class Controller extends Section { export default class Controller extends Section {
constructor($element, $) { constructor($element, $) {
@ -28,6 +31,20 @@ export default class Controller extends Section {
select: 'visibility' select: 'visibility'
}; };
this.filter = { this.filter = {
fields: [
'id',
'originFk',
'userFk',
'action',
'changedModel',
'oldInstance',
'newInstance',
'creationDate',
'changedModel',
'changedModelId',
'changedModelValue',
'description'
],
include: [{ include: [{
relation: 'user', relation: 'user',
scope: { scope: {
@ -48,6 +65,11 @@ export default class Controller extends Section {
this.today.setHours(0, 0, 0, 0); this.today.setHours(0, 0, 0, 0);
} }
$onInit() {
const match = this.url?.match(/(.*)Logs$/);
this.modelI18n = match && this.translateModel(match[1]);
}
$postLink() { $postLink() {
this.resetFilter(); this.resetFilter();
this.$.$watch( this.$.$watch(
@ -63,47 +85,75 @@ export default class Controller extends Section {
set logs(value) { set logs(value) {
this._logs = value; this._logs = value;
this.logTree = [];
if (!value) return; if (!value) return;
const empty = {}; const empty = {};
const validations = window.validations; const validations = window.validations;
const castJsonValue = this.castJsonValue;
for (const log of value) { let originLog;
let userLog;
let modelLog;
let nLogs;
for (let i = 0; i < value.length; i++) {
const log = value[i];
const prevLog = i > 0 ? value[i - 1] : null;
const locale = validations[log.changedModel]?.locale || empty;
// Origin
const originChanged = !prevLog
|| log.originFk != prevLog.originFk;
if (originChanged) {
this.logTree.push(originLog = {
originFk: log.originFk,
logs: []
});
}
// User
const userChanged = originChanged
|| log.userFk != prevLog.userFk;
if (userChanged) {
originLog.logs.push(userLog = {
user: log.user,
userFk: log.userFk,
logs: []
});
}
// Model
const modelChanged = userChanged
|| log.changedModel != prevLog.changedModel
|| log.changedModelId != prevLog.changedModelId
|| nLogs >= 6;
if (modelChanged) {
userLog.logs.push(modelLog = {
model: log.changedModel,
modelI18n: firstUpper(locale.name) || log.changedModel,
id: log.changedModelId,
showValue: log.changedModelValue,
logs: []
});
nLogs = 0;
}
nLogs++;
modelLog.logs.push(log);
// Changes
const notDelete = log.action != 'delete'; const notDelete = log.action != 'delete';
const olds = (notDelete ? log.oldInstance : null) || empty; const olds = (notDelete ? log.oldInstance : null) || empty;
const vals = (notDelete ? log.newInstance : log.oldInstance) || empty; const vals = (notDelete ? log.newInstance : log.oldInstance) || empty;
const locale = validations[log.changedModel]?.locale || empty;
log.changedModelI18n = firstUpper(locale.name) || log.changedModel;
let props = Object.keys(olds).concat(Object.keys(vals)); let propNames = Object.keys(olds).concat(Object.keys(vals));
props = [...new Set(props)]; propNames = [...new Set(propNames)];
log.props = []; log.props = this.parseProps(propNames, locale, vals, olds);
for (const prop of props) {
if (prop.endsWith('$')) continue;
log.props.push({
name: prop,
nameI18n: firstUpper(locale.columns?.[prop]) || prop,
old: getVal(olds, prop),
val: getVal(vals, prop)
});
}
log.props.sort(
(a, b) => a.nameI18n.localeCompare(b.nameI18n));
}
function getVal(vals, prop) {
let val, id;
const showProp = `${prop}$`;
if (vals[showProp] != null) {
val = vals[showProp];
id = vals[prop];
} else
val = vals[prop];
return {val: castJsonValue(val), id};
} }
} }
@ -114,17 +164,76 @@ export default class Controller extends Section {
set models(value) { set models(value) {
this._models = value; this._models = value;
if (!value) return; if (!value) return;
for (const model of value) { for (const model of value)
const name = model.changedModel; model.changedModelI18n = this.translateModel(model.changedModel);
model.changedModelI18n =
firstUpper(window.validations[name]?.locale?.name) || name;
}
} }
get showModelName() { get showModelName() {
return !(this.changedModel && this.changedModelId); return !(this.changedModel && this.changedModelId);
} }
parseProps(propNames, locale, vals, olds) {
const castJsonValue = this.castJsonValue;
const props = [];
for (const prop of propNames) {
if (prop.endsWith('$')) continue;
props.push({
name: prop,
nameI18n: firstUpper(locale.columns?.[prop]) || prop,
val: getVal(vals, prop),
old: olds && getVal(olds, prop)
});
}
props.sort(
(a, b) => a.nameI18n.localeCompare(b.nameI18n));
function getVal(vals, prop) {
let val; let id;
const showProp = `${prop}$`;
if (vals[showProp] != null) {
val = vals[showProp];
id = vals[prop];
} else
val = vals[prop];
return {val: castJsonValue(val), id};
}
return props;
}
viewPitInstance(event, id, modelLog) {
if (this.instance?.canceler)
this.instance.canceler.resolve();
const canceler = this.$q.defer();
this.instance = {
modelLog,
canceler
};
const options = {timeout: canceler.promise};
this.$http.get(`${this.url}/${id}/pitInstance`, options)
.then(res => {
const instance = res.data;
const propNames = Object.keys(instance);
const locale = window.validations[modelLog.model]?.locale || {};
this.instance.props = this.parseProps(propNames, locale, instance);
})
.finally(() => {
this.instance.canceler = null;
this.$.$applyAsync(() => this.$.instancePopover.relocate());
});
this.$.instancePopover.show(event);
}
translateModel(name) {
return firstUpper(window.validations[name]?.locale?.name) || name;
}
castJsonValue(value) { castJsonValue(value) {
return typeof value === 'string' && validDate.test(value) return typeof value === 'string' && validDate.test(value)
? new Date(value) ? new Date(value)
@ -160,12 +269,11 @@ export default class Controller extends Section {
applyFilter() { applyFilter() {
const filter = this.$.filter; const filter = this.$.filter;
function getParam(prop, value) { const getParam = (prop, value) => {
if (value == null || value == '') return null; if (value == null || value == '') return null;
switch (prop) { switch (prop) {
case 'search': case 'search':
const or = []; if (/^\s*[0-9]+\s*$/.test(value) || this.byRecord)
if (/^\s*[0-9]+\s*$/.test(value))
return {changedModelId: value.trim()}; return {changedModelId: value.trim()};
else else
return {changedModelValue: {like: `%${value}%`}}; return {changedModelValue: {like: `%${value}%`}};
@ -177,72 +285,86 @@ export default class Controller extends Section {
]}; ]};
case 'who': case 'who':
switch (value) { switch (value) {
case 'all':
return null;
case 'user': case 'user':
return {userFk: {neq: null}}; return {userFk: {neq: null}};
case 'system': case 'system':
return {userFk: null}; return {userFk: null};
case 'all':
default:
return null;
} }
case 'actions': case 'actions': {
const inq = []; const inq = [];
for (const action in value) { for (const action in value) {
if (value[action]) if (value[action])
inq.push(action); inq.push(action);
} }
return inq.length ? {action: {inq}} : null; return inq.length ? {action: {inq}} : null;
}
case 'from': case 'from':
if (filter.to) { if (filter.to)
return {creationDate: {gte: value}}; return {creationDate: {gte: value}};
} else { else {
const to = new Date(value); const to = new Date(value);
to.setHours(23, 59, 59, 999); to.setHours(23, 59, 59, 999);
return {creationDate: {between: [value, to]}}; return {creationDate: {between: [value, to]}};
} }
case 'to': case 'to': {
const to = new Date(value); const to = new Date(value);
to.setHours(23, 59, 59, 999); to.setHours(23, 59, 59, 999);
return {creationDate: {lte: to}}; return {creationDate: {lte: to}};
}
case 'userFk': case 'userFk':
return filter.who != 'system' return filter.who != 'system'
? {[prop]: value} : null; ? {[prop]: value} : null;
default: default:
return {[prop]: value}; return {[prop]: value};
} }
} };
this.hasFilter = false;
const and = []; const and = [];
if (!filter.search || !filter.changedModel)
this.byRecord = false;
if (!this.byRecord)
and.push({originFk: this.originId});
for (const prop in filter) { for (const prop in filter) {
const param = getParam(prop, filter[prop]); const param = getParam(prop, filter[prop]);
if (param) and.push(param); if (param) {
and.push(param);
this.hasFilter = true;
}
} }
const lbFilter = and.length ? {where: {and}} : null; const lbFilter = and.length ? {where: {and}} : null;
return this.$.model.applyFilter(lbFilter); return this.$.model.applyFilter(lbFilter);
} }
filterByEntity(log) { filterByRecord(modelLog) {
this.byRecord = true;
this.$.filter = { this.$.filter = {
who: 'all', who: 'all',
search: log.changedModelId, search: modelLog.id,
changedModel: log.changedModel changedModel: modelLog.model
}; };
} }
searchUser(search) { searchUser(search) {
if (/^[0-9]+$/.test(search)) { if (/^[0-9]+$/.test(search))
return {id: search}; return {id: search};
} else { else {
return {or: [ return {or: [
{name: search}, {name: search},
{nickname: {like: `%${search}%`}} {nickname: {like: `%${search}%`}}
]} ]};
} }
} }
showWorkerDescriptor(event, log) { showWorkerDescriptor(event, userLog) {
if (log.user?.worker) if (userLog.user?.worker)
this.$.workerDescriptor.show(event.target, log.userFk); this.$.workerDescriptor.show(event.target, userLog.userFk);
} }
} }

View File

@ -24,4 +24,5 @@ Changes: Cambios
today: hoy today: hoy
yesterday: ayer yesterday: ayer
Show all record changes: Mostrar todos los cambios realizados en el registro Show all record changes: Mostrar todos los cambios realizados en el registro
View record at this point in time: Ver el registro en este punto
Quit filter: Quitar filtro Quit filter: Quitar filtro

View File

@ -1,167 +1,246 @@
@import "util"; @import "util";
vn-log { vn-log {
.change { .origin-log {
&:first-child > .origin-info {
margin-top: 0;
}
& > .origin-info {
display: flex;
align-items: center;
margin-top: 28px;
gap: 6px;
& > .origin-id {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: $color-font-secondary;
margin: 0;
}
& > .line {
flex-grow: 1;
background-color: $color-font-secondary;
height: 2px;
}
}
}
.user-log {
display: flex; display: flex;
& > .left { & > .timeline {
position: relative; position: relative;
padding-right: 10px; padding-right: 10px;
width: 38px;
min-width: 38px;
flex-grow: auto;
& > vn-avatar {
cursor: pointer;
&.system {
background-color: $color-main !important;
}
}
& > .arrow { & > .arrow {
height: 8px; height: 8px;
width: 8px; width: 8px;
position: absolute; position: absolute;
transform: rotateY(0deg) rotate(45deg); transform: rotateY(0deg) rotate(45deg);
top: 18px; top: 15px;
right: -4px; right: -4px;
z-index: 1; z-index: 1;
} }
& > .user-avatar {
background-color: $color-bg;
padding: $spacing-sm 0;
margin-top: -$spacing-sm;
position: sticky;
top: 64px;
& > vn-avatar {
cursor: pointer;
display: block;
&.system {
background-color: $color-main !important;
}
}
}
& > .line { & > .line {
position: absolute; position: absolute;
background-color: $color-main; background-color: $color-main;
width: 2px; width: 2px;
left: 17px; left: 18px;
z-index: -1; z-index: -1;
top: 44px; top: 0;
bottom: -8px; bottom: -$spacing-sm;
} }
} }
&:last-child > .left > .line { &:last-child > .timeline > .line {
display: none; display: none;
} }
.detail { & > .user-changes {
position: relative;
flex-grow: 1; flex-grow: 1;
width: 100%;
border-radius: 2px;
overflow: hidden; overflow: hidden;
}
}
.model-log {
& > .model-info {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
min-height: 22px;
& > .header { & > .model-name {
display: flex; display: inline-block;
justify-content: space-between; padding: 2px 5px;
align-items: center; color: $color-font-dark;
overflow: hidden; border-radius: 8px;
vertical-align: middle;
}
& > .model-value {
font-style: italic;
}
& > .model-id {
color: $color-font-secondary;
font-size: .9rem;
}
& > vn-icon[icon="filter_alt"] {
@extend %clickable-light;
vertical-align: middle;
font-size: 18px;
color: $color-font-secondary;
float: right;
display: none;
& > .action-model { @include mobile {
display: inline-flex; display: initial;
overflow: hidden;
& > .model-name {
display: inline-block;
padding: 2px 5px;
color: $color-font-dark;
border-radius: 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
& > .action-date {
white-space: nowrap;
& > .action {
display: inline-flex;
align-items: center;
justify-content: center;
color: $color-font-bg;
vertical-align: middle;
border-radius: 50%;
width: 24px;
height: 24px;
font-size: 18px;
&.notice {
background-color: $color-notice-medium
}
&.success {
background-color: $color-success-medium;
}
&.warning {
background-color: $color-main-medium;
}
&.alert {
background-color: lighten($color-alert, 5%);
}
}
} }
} }
& > .model { }
overflow: hidden; &:hover > .model-info > vn-icon[icon="filter_alt"] {
text-overflow: ellipsis; display: initial;
white-space: nowrap; }
max-height: 18px; }
.changes-log {
position: relative;
max-width: 100%;
width: 100%;
border-radius: 2px;
overflow: hidden;
& > vn-icon { &:last-child {
margin-bottom: 0;
}
& > .change-info {
display: flex;
justify-content: space-between;
align-items: center;
overflow: hidden;
& > .date {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
& > div {
white-space: nowrap;
& > vn-icon.pit {
@extend %clickable-light; @extend %clickable-light;
vertical-align: middle; vertical-align: middle;
padding: 2px; font-size: 20px;
margin: -2px;
font-size: 18px;
color: $color-font-secondary; color: $color-font-secondary;
float: right;
display: none; display: none;
@include mobile { @include mobile {
display: initial; display: inline-block;
} }
} }
& > .model-value { & > .action {
font-style: italic; display: inline-flex;
} align-items: center;
& > .model-id { justify-content: center;
color: $color-font-secondary; color: $color-font-bg;
font-size: .9rem; vertical-align: middle;
} border-radius: 50%;
} width: 24px;
&:hover > .model > vn-icon { height: 24px;
display: initial; font-size: 18px;
}
}
}
.changes {
overflow: hidden;
background-color: rgba(255, 255, 255, .05);
color: $color-font-light;
position: relative;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-height: 34px;
box-sizing: border-box;
& > vn-icon { &.notice {
@extend %clickable; background-color: $color-notice-medium
float: right; }
position: relative; &.success {
transition-property: transform, background-color; background-color: $color-success-medium;
transition-duration: 150ms; }
margin: -5px; &.warning {
margin-left: 4px; background-color: $color-main-medium;
padding: 1px; }
border-radius: 50%; &.alert {
background-color: lighten($color-alert, 5%);
}
}
}
&:hover vn-icon.pit {
display: inline-block;
}
} }
&.expanded { & > .change-detail {
text-overflow: initial; overflow: hidden;
white-space: initial; background-color: rgba(255, 255, 255, .05);
color: $color-font-light;
position: relative;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-height: 34px;
box-sizing: border-box;
& > vn-icon { & > vn-icon {
transform: rotate(180deg); @extend %clickable;
float: right;
position: relative;
transition-property: transform, background-color;
transition-duration: 150ms;
margin: -5px;
margin-left: 4px;
padding: 1px;
border-radius: 50%;
}
&.expanded {
text-overflow: initial;
white-space: initial;
& > vn-icon {
transform: rotate(180deg);
}
}
& > .no-changes {
font-style: italic;
} }
} }
& > .no-changes { }
font-style: italic; .id-value {
font-size: .9rem;
color: $color-font-secondary;
}
}
.vn-log-instance {
display: block;
& > .loading {
display: flex;
justify-content: center;
}
& > .instance {
min-width: 180px;
max-width: 400px;
& > .header {
background-color: $color-main;
color: $color-font-dark;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin: 0;
}
& > .change-detail {
color: $color-font-light;
} }
} }
} }
vn-log-value > .id-value {
font-size: .9rem;
color: $color-font-secondary;
}

View File

@ -27,10 +27,9 @@ export default class Controller {
this.loading = false; this.loading = false;
this.password = ''; this.password = '';
this.focusUser(); this.focusUser();
if (req?.data?.error?.code == 'passExpired') { const err = req.data?.error;
const [args] = req.data.error.translateArgs; if (err?.code == 'passExpired')
this.$state.go('change-password', args); this.$state.go('change-password', err.details.token);
}
throw req; throw req;
}); });

View File

@ -11,7 +11,8 @@ function config($stateProvider, $urlRouterProvider) {
abstract: true, abstract: true,
template: '<vn-layout></vn-layout>', template: '<vn-layout></vn-layout>',
resolve: { resolve: {
config: ['vnConfig', vnConfig => vnConfig.initialize()] config: ['vnConfig', vnConfig => vnConfig.initialize()],
token: ['vnToken', vnToken => vnToken.fetchConfig()]
} }
}) })
.state('outLayout', { .state('outLayout', {
@ -38,7 +39,7 @@ function config($stateProvider, $urlRouterProvider) {
}) })
.state('change-password', { .state('change-password', {
parent: 'outLayout', parent: 'outLayout',
url: '/change-password?id&token', url: '/change-password?id&userId',
description: 'Change password', description: 'Change password',
template: '<vn-change-password></vn-change-password>' template: '<vn-change-password></vn-change-password>'
}) })

View File

@ -0,0 +1,91 @@
const NotFoundError = require('vn-loopback/util/not-found-error');
module.exports = Self => {
Self.remoteMethod('pitInstance', {
description: 'Gets the status of instance at specific point in time',
accepts: [
{
arg: 'id',
type: 'integer',
description: 'The log id',
required: true
}
],
returns: {
type: [Self],
root: true
},
http: {
path: `/:id/pitInstance`,
verb: 'GET'
}
});
Self.pitInstance = async function(id) {
const log = await Self.findById(id, {
fields: [
'changedModel',
'changedModelId',
'creationDate'
]
});
if (!log)
throw new NotFoundError();
const where = {
changedModel: log.changedModel,
changedModelId: log.changedModelId
};
// Fetch creation and all update logs for record up to requested log
const createdWhere = {
action: 'insert',
creationDate: {lte: log.creationDate}
};
const createdLog = await Self.findOne({
fields: ['id', 'creationDate', 'newInstance'],
where: Object.assign(createdWhere, where),
order: 'creationDate DESC, id DESC'
});
const instance = {};
let logsWhere = {
action: 'update'
};
if (createdLog) {
Object.assign(instance, createdLog.newInstance);
Object.assign(logsWhere, {
creationDate: {between: [
createdLog.creationDate,
log.creationDate
]},
id: {between: [
Math.min(id, createdLog.id),
Math.max(id, createdLog.id)
]}
});
} else {
Object.assign(logsWhere, {
creationDate: {lte: log.creationDate},
id: {lte: id}
});
}
const logs = await Self.find({
fields: ['newInstance'],
where: Object.assign(logsWhere, where),
order: 'creationDate, id'
});
if (!logs.length && !createdLog)
throw new NotFoundError('No logs found for record');
// Merge all logs in order into one instance
for (const log of logs)
Object.assign(instance, log.newInstance);
return instance;
};
};

View File

@ -5,6 +5,7 @@ module.exports = function(Self) {
Self.super_.setup.call(this); Self.super_.setup.call(this);
require('../methods/log/editors')(this); require('../methods/log/editors')(this);
require('../methods/log/models')(this); require('../methods/log/models')(this);
require('../methods/log/pitInstance')(this);
} }
}); });
}; };

View File

@ -200,22 +200,20 @@ module.exports = function(Self) {
const connector = this.dataSource.connector; const connector = this.dataSource.connector;
let conn; let conn;
let res; let res;
const opts = Object.assign({}, options);
try { try {
if (userId) { if (userId) {
conn = await new Promise((resolve, reject) => { if (!options.transaction) {
connector.client.getConnection(function(err, conn) { options = Object.assign({}, options);
if (err) conn = await new Promise((resolve, reject) => {
reject(err); connector.client.getConnection(function(err, conn) {
else if (err)
resolve(conn); reject(err);
else
resolve(conn);
});
}); });
}); options.transaction = {
const opts = Object.assign({}, options);
if (!opts.transaction) {
opts.transaction = {
connection: conn, connection: conn,
connector connector
}; };
@ -223,15 +221,14 @@ module.exports = function(Self) {
await connector.executeP( await connector.executeP(
'CALL account.myUser_loginWithName((SELECT name FROM account.user WHERE id = ?))', 'CALL account.myUser_loginWithName((SELECT name FROM account.user WHERE id = ?))',
[userId], opts [userId], options
); );
} }
res = await connector.executeP(query, params, opts); res = await connector.executeP(query, params, options);
if (userId) { if (userId)
await connector.executeP('CALL account.myUser_logout()', null, opts); await connector.executeP('CALL account.myUser_logout()', null, options);
}
} finally { } finally {
if (conn) conn.release(); if (conn) conn.release();
} }

View File

@ -115,7 +115,7 @@
"This client is not invoiceable": "This client is not invoiceable", "This client is not invoiceable": "This client is not invoiceable",
"INACTIVE_PROVIDER": "Inactive provider", "INACTIVE_PROVIDER": "Inactive provider",
"reference duplicated": "reference duplicated", "reference duplicated": "reference duplicated",
"The PDF document does not exists": "The PDF document does not exists. Try regenerating it from 'Regenerate invoice PDF' option", "The PDF document does not exist": "The PDF document does not exists. Try regenerating it from 'Regenerate invoice PDF' option",
"This item is not available": "This item is not available", "This item is not available": "This item is not available",
"Deny buy request": "Purchase request for ticket id [{{ticketId}}]({{{url}}}) has been rejected. Reason: {{observation}}", "Deny buy request": "Purchase request for ticket id [{{ticketId}}]({{{url}}}) has been rejected. Reason: {{observation}}",
"The type of business must be filled in basic data": "The type of business must be filled in basic data", "The type of business must be filled in basic data": "The type of business must be filled in basic data",
@ -175,5 +175,7 @@
"Pass expired": "The password has expired, change it from Salix", "Pass expired": "The password has expired, change it from Salix",
"Can't transfer claimed sales": "Can't transfer claimed sales", "Can't transfer claimed sales": "Can't transfer claimed sales",
"Invalid quantity": "Invalid quantity", "Invalid quantity": "Invalid quantity",
"Failed to upload delivery note": "Error to upload delivery note {{id}}" "Failed to upload delivery note": "Error to upload delivery note {{id}}",
"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"
} }

View File

@ -177,7 +177,6 @@
"You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria", "You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria",
"Action not allowed on the test environment": "Esta acción no está permitida en el entorno de pruebas", "Action not allowed on the test environment": "Esta acción no está permitida en el entorno de pruebas",
"The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta", "The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta",
"Sorts whole route": "Reordena ruta entera",
"New ticket request has been created with price": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}* y un precio de *{{price}} €*", "New ticket request has been created with price": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}* y un precio de *{{price}} €*",
"New ticket request has been created": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}*", "New ticket request has been created": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}*",
"Swift / BIC cannot be empty": "Swift / BIC no puede estar vacío", "Swift / BIC cannot be empty": "Swift / BIC no puede estar vacío",
@ -211,7 +210,7 @@
"You don't have enough privileges to set this credit amount": "No tienes suficientes privilegios para establecer esta cantidad de crédito", "You don't have enough privileges to set this credit amount": "No tienes suficientes privilegios para establecer esta cantidad de crédito",
"You can't change the credit set to zero from a financialBoss": "No puedes cambiar el cŕedito establecido a cero por un jefe de finanzas", "You can't change the credit set to zero from a financialBoss": "No puedes cambiar el cŕedito establecido a cero por un jefe de finanzas",
"Amounts do not match": "Las cantidades no coinciden", "Amounts do not match": "Las cantidades no coinciden",
"The PDF document does not exists": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'", "The PDF document does not exist": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'",
"The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos", "The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos",
"You can't create a claim from a ticket delivered more than seven days ago": "No puedes crear una reclamación de un ticket entregado hace más de siete días", "You can't create a claim from a ticket delivered more than seven days ago": "No puedes crear una reclamación de un ticket entregado hace más de siete días",
"The worker has hours recorded that day": "El trabajador tiene horas fichadas ese día", "The worker has hours recorded that day": "El trabajador tiene horas fichadas ese día",
@ -265,7 +264,7 @@
"It is not possible to modify cloned sales": "No es posible modificar líneas de pedido clonadas", "It is not possible to modify cloned sales": "No es posible modificar líneas de pedido clonadas",
"A supplier with the same name already exists. Change the country.": "Un proveedor con el mismo nombre ya existe. Cambie el país.", "A supplier with the same name already exists. Change the country.": "Un proveedor con el mismo nombre ya existe. Cambie el país.",
"There is no assigned email for this client": "No hay correo asignado para este cliente", "There is no assigned email for this client": "No hay correo asignado para este cliente",
"Exists an invoice with a previous date": "Existe una factura con fecha anterior", "Exists an invoice with a future date": "Existe una factura con fecha posterior",
"Invoice date can't be less than max date": "La fecha de factura no puede ser inferior a la fecha límite", "Invoice date can't be less than max date": "La fecha de factura no puede ser inferior a la fecha límite",
"Warehouse inventory not set": "El almacén inventario no está establecido", "Warehouse inventory not set": "El almacén inventario no está establecido",
"This locker has already been assigned": "Esta taquilla ya ha sido asignada", "This locker has already been assigned": "Esta taquilla ya ha sido asignada",
@ -293,5 +292,11 @@
"Pass expired": "La contraseña ha caducado, cambiela desde Salix", "Pass expired": "La contraseña ha caducado, cambiela desde Salix",
"Invalid NIF for VIES": "Invalid NIF for VIES", "Invalid NIF for VIES": "Invalid NIF for VIES",
"Ticket does not exist": "Este ticket no existe", "Ticket does not exist": "Este ticket no existe",
"Ticket is already signed": "Este ticket ya ha sido firmado" "Ticket is already signed": "Este ticket ya ha sido firmado",
"You can only add negative amounts in refund tickets": "Solo se puede añadir cantidades negativas en tickets abono",
"Fecha fuera de rango": "Fecha fuera de rango",
"Error while generating PDF": "Error al generar PDF",
"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"
} }

View File

@ -0,0 +1,3 @@
name: account
columns:
id: id

View File

@ -0,0 +1,3 @@
name: cuenta
columns:
id: id

View File

@ -0,0 +1,5 @@
name: mail alias
columns:
id: id
mailAlias: alias
account: account

View File

@ -0,0 +1,5 @@
name: alias de correo
columns:
id: id
mailAlias: alias
account: cuenta

View File

@ -5,8 +5,7 @@ import UserError from 'core/lib/user-error';
export default class Controller extends Section { export default class Controller extends Section {
onSynchronizeAll() { onSynchronizeAll() {
this.vnApp.showSuccess(this.$t('Synchronizing in the background')); this.vnApp.showSuccess(this.$t('Synchronizing in the background'));
this.$http.patch(`Accounts/syncAll`) this.$http.patch(`Accounts/syncAll`);
.then(() => this.vnApp.showSuccess(this.$t('Users synchronized!')));
} }
onUserSync() { onUserSync() {

View File

@ -17,7 +17,9 @@
<vn-icon-button <vn-icon-button
icon="delete" icon="delete"
translate-attr="{title: 'Unsubscribe'}" translate-attr="{title: 'Unsubscribe'}"
ng-click="removeConfirm.show(row)"> ng-click="removeConfirm.show(row)"
vn-acl="itManagement"
vn-acl-action="remove">
</vn-icon-button> </vn-icon-button>
</vn-item-section> </vn-item-section>
</vn-item> </vn-item>
@ -30,9 +32,11 @@
translate-attr="{title: 'Add'}" translate-attr="{title: 'Add'}"
vn-bind="+" vn-bind="+"
ng-click="$ctrl.onAddClick()" ng-click="$ctrl.onAddClick()"
fixed-bottom-right> fixed-bottom-right
vn-acl="itManagement"
vn-acl-action="remove">
</vn-float-button> </vn-float-button>
<vn-dialog <vn-dialog
vn-id="dialog" vn-id="dialog"
on-accept="$ctrl.onAddSave()"> on-accept="$ctrl.onAddSave()">
<tpl-body> <tpl-body>
@ -49,7 +53,7 @@
<button response="accept" translate>Save</button> <button response="accept" translate>Save</button>
</tpl-buttons> </tpl-buttons>
</vn-dialog> </vn-dialog>
<vn-confirm <vn-confirm
vn-id="removeConfirm" vn-id="removeConfirm"
message="User will be removed from alias" message="User will be removed from alias"
question="Are you sure you want to continue?" question="Are you sure you want to continue?"

View File

@ -5,6 +5,7 @@ import './style.scss';
class Controller extends ModuleCard { class Controller extends ModuleCard {
reload() { reload() {
const filter = { const filter = {
where: {id: this.$params.id},
include: { include: {
relation: 'role', relation: 'role',
scope: { scope: {
@ -14,8 +15,11 @@ class Controller extends ModuleCard {
}; };
return Promise.all([ return Promise.all([
this.$http.get(`VnUsers/${this.$params.id}`, {filter}) this.$http.get(`VnUsers/preview`, {filter})
.then(res => this.user = res.data), .then(res => {
const [user] = res.data;
this.user = user;
}),
this.$http.get(`Accounts/${this.$params.id}/exists`) this.$http.get(`Accounts/${this.$params.id}/exists`)
.then(res => this.hasAccount = res.data.exists) .then(res => this.hasAccount = res.data.exists)
]); ]);

View File

@ -15,12 +15,12 @@ describe('component vnUserCard', () => {
it('should reload the controller data', () => { it('should reload the controller data', () => {
controller.$params.id = 1; controller.$params.id = 1;
$httpBackend.expectGET('VnUsers/1').respond('foo'); $httpBackend.expectGET('VnUsers/preview').respond('foo');
$httpBackend.expectGET('Accounts/1/exists').respond({exists: true}); $httpBackend.expectGET('Accounts/1/exists').respond({exists: true});
controller.reload(); controller.reload();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.user).toBe('foo'); expect(controller.user).toBe('f');
expect(controller.hasAccount).toBeTruthy(); expect(controller.hasAccount).toBeTruthy();
}); });
}); });

View File

@ -12,18 +12,18 @@
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-vertical> <vn-vertical>
<vn-textfield <vn-textfield
label="Name" label="Name"
ng-model="$ctrl.user.name" ng-model="$ctrl.user.name"
rule="VnUser" rule="VnUser"
vn-focus> vn-focus>
</vn-textfield> </vn-textfield>
<vn-textfield <vn-textfield
label="Nickname" label="Nickname"
ng-model="$ctrl.user.nickname" ng-model="$ctrl.user.nickname"
rule="VnUser"> rule="VnUser">
</vn-textfield> </vn-textfield>
<vn-textfield <vn-textfield
label="Email" label="Email"
ng-model="$ctrl.user.email" ng-model="$ctrl.user.email"
rule="VnUser"> rule="VnUser">
</vn-textfield> </vn-textfield>
@ -39,7 +39,7 @@
type="password"> type="password">
</vn-textfield> </vn-textfield>
<vn-check <vn-check
label="Active" label="Active"
ng-model="$ctrl.user.active"> ng-model="$ctrl.user.active">
</vn-check> </vn-check>
</vn-vertical> </vn-vertical>

View File

@ -2,6 +2,11 @@ import ngModule from '../module';
import Section from 'salix/components/section'; import Section from 'salix/components/section';
export default class Controller extends Section { export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.user = {active: true};
}
onSubmit() { onSubmit() {
return this.$.watcher.submit().then(res => { return this.$.watcher.submit().then(res => {
this.$state.go('account.card.basicData', {id: res.data.id}); this.$state.go('account.card.basicData', {id: res.data.id});

View File

@ -6,7 +6,7 @@
<vn-item <vn-item
ng-click="deleteUser.show()" ng-click="deleteUser.show()"
name="deleteUser" name="deleteUser"
vn-acl="it" vn-acl="itManagement"
vn-acl-action="remove" vn-acl-action="remove"
translate> translate>
Delete Delete
@ -15,7 +15,7 @@
ng-if="::$root.user.id == $ctrl.id" ng-if="::$root.user.id == $ctrl.id"
ng-click="$ctrl.onChangePassClick(true)" ng-click="$ctrl.onChangePassClick(true)"
name="changePassword" name="changePassword"
vn-acl="hr" vn-acl="sysadmin"
vn-acl-action="remove" vn-acl-action="remove"
translate> translate>
Change password Change password
@ -23,7 +23,7 @@
<vn-item <vn-item
ng-click="$ctrl.onChangePassClick(false)" ng-click="$ctrl.onChangePassClick(false)"
name="setPassword" name="setPassword"
vn-acl="hr" vn-acl="sysadmin"
vn-acl-action="remove" vn-acl-action="remove"
translate> translate>
Set password Set password
@ -32,7 +32,7 @@
ng-if="!$ctrl.hasAccount" ng-if="!$ctrl.hasAccount"
ng-click="enableAccount.show()" ng-click="enableAccount.show()"
name="enableAccount" name="enableAccount"
vn-acl="it" vn-acl="sysadmin"
vn-acl-action="remove" vn-acl-action="remove"
translate> translate>
Enable account Enable account
@ -41,7 +41,7 @@
ng-if="$ctrl.hasAccount" ng-if="$ctrl.hasAccount"
ng-click="disableAccount.show()" ng-click="disableAccount.show()"
name="disableAccount" name="disableAccount"
vn-acl="it" vn-acl="sysadmin"
vn-acl-action="remove" vn-acl-action="remove"
translate> translate>
Disable account Disable account
@ -50,7 +50,7 @@
ng-if="!$ctrl.user.active" ng-if="!$ctrl.user.active"
ng-click="activateUser.show()" ng-click="activateUser.show()"
name="activateUser" name="activateUser"
vn-acl="hr" vn-acl="itManagement"
vn-acl-action="remove" vn-acl-action="remove"
translate> translate>
Activate user Activate user
@ -59,7 +59,7 @@
ng-if="$ctrl.user.active" ng-if="$ctrl.user.active"
ng-click="deactivateUser.show()" ng-click="deactivateUser.show()"
name="deactivateUser" name="deactivateUser"
vn-acl="hr" vn-acl="itManagement"
vn-acl-action="remove" vn-acl-action="remove"
translate> translate>
Deactivate user Deactivate user

View File

@ -14,11 +14,11 @@
<vn-item-section> <vn-item-section>
<h6>{{::user.nickname}}</h6> <h6>{{::user.nickname}}</h6>
<vn-label-value <vn-label-value
label="Id" label="Id"
value="{{::user.id}}"> value="{{::user.id}}">
</vn-label-value> </vn-label-value>
<vn-label-value <vn-label-value
label="User" label="User"
value="{{::user.name}}"> value="{{::user.name}}">
</vn-label-value> </vn-label-value>
</vn-item-section> </vn-item-section>
@ -36,12 +36,12 @@
<vn-popup vn-id="summary"> <vn-popup vn-id="summary">
<vn-user-summary user="$ctrl.selectedUser"></vn-user-summary> <vn-user-summary user="$ctrl.selectedUser"></vn-user-summary>
</vn-popup> </vn-popup>
<a <a
fixed-bottom-right fixed-bottom-right
ui-sref="account.create" ui-sref="account.create"
vn-tooltip="New user" vn-tooltip="New user"
vn-bind="+" vn-bind="+"
vn-acl="it" vn-acl="itManagement"
vn-acl-action="remove"> vn-acl-action="remove">
<vn-float-button icon="add"></vn-float-button> <vn-float-button icon="add"></vn-float-button>
</a> </a>

View File

@ -14,12 +14,12 @@
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-vertical> <vn-vertical>
<vn-check <vn-check
label="Enable mail forwarding" label="Enable mail forwarding"
ng-model="watcher.hasData"> ng-model="watcher.hasData">
</vn-check> </vn-check>
<vn-textfield <vn-textfield
ng-if="watcher.hasData" ng-if="watcher.hasData"
label="Forward email" label="Forward email"
ng-model="data.forwardTo" ng-model="data.forwardTo"
info="All emails will be forwarded to the specified address." info="All emails will be forwarded to the specified address."
rule="MailForward" rule="MailForward"

View File

@ -4,3 +4,4 @@ Enable mail forwarding: Habilitar redirección de correo
All emails will be forwarded to the specified address.: > All emails will be forwarded to the specified address.: >
Todos los correos serán reenviados a la dirección especificada, no se Todos los correos serán reenviados a la dirección especificada, no se
mantendrá copia de los mismos en el buzón del usuario. mantendrá copia de los mismos en el buzón del usuario.
You don't have enough privileges: No tienes suficientes permisos

View File

@ -1,6 +1,6 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="VnUsers" url="VnUsers/preview"
filter="::$ctrl.filter" filter="::$ctrl.filter"
limit="20"> limit="20">
</vn-crud-model> </vn-crud-model>

View File

@ -1,9 +1,7 @@
<mg-ajax path="VnUsers/{{post.params.id}}/privileges" options="vnPost"></mg-ajax> <mg-ajax path="VnUsers/{{post.params.id}}/privileges" options="vnPost"></mg-ajax>
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
url="VnUsers"
data="$ctrl.user" data="$ctrl.user"
id-value="$ctrl.$params.id"
form="form" form="form"
save="post"> save="post">
</vn-watcher> </vn-watcher>
@ -11,15 +9,16 @@
name="form" name="form"
ng-submit="watcher.submit()" ng-submit="watcher.submit()"
class="vn-w-md"> class="vn-w-md">
<vn-card class="vn-pa-lg" vn-focus> <vn-card class="vn-pa-lg">
<vn-vertical> <vn-vertical>
<vn-check <vn-check
label="Has grant" label="Has grant"
ng-model="$ctrl.user.hasGrant"> ng-model="$ctrl.user.hasGrant">
</vn-check> </vn-check>
</vn-vertical> </vn-vertical>
<vn-vertical </vn-card>
class="vn-mt-md"> <vn-card class="vn-pa-lg vn-mt-md">
<vn-vertical>
<vn-autocomplete <vn-autocomplete
label="Role" label="Role"
ng-model="$ctrl.user.roleFk" ng-model="$ctrl.user.roleFk"

View File

@ -1,9 +1,21 @@
import ngModule from '../module'; import ngModule from '../module';
import Section from 'salix/components/section'; import Section from 'salix/components/section';
export default class Controller extends Section {} export default class Controller extends Section {
get user() {
return this._user;
}
set user(value) {
this._user = value;
if (!value) return;
}
}
ngModule.component('vnUserPrivileges', { ngModule.component('vnUserPrivileges', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller controller: Controller,
bindings: {
user: '<'
}
}); });

View File

@ -49,15 +49,13 @@
"url": "/index?q", "url": "/index?q",
"state": "account.index", "state": "account.index",
"component": "vn-user-index", "component": "vn-user-index",
"description": "Users", "description": "Users"
"acl": ["marketing", "hr"]
}, },
{ {
"url": "/create", "url": "/create",
"state": "account.create", "state": "account.create",
"component": "vn-user-create", "component": "vn-user-create",
"description": "New user", "description": "New user"
"acl": ["it"]
}, },
{ {
"url": "/:id", "url": "/:id",
@ -80,7 +78,7 @@
"state": "account.card.basicData", "state": "account.card.basicData",
"component": "vn-user-basic-data", "component": "vn-user-basic-data",
"description": "Basic data", "description": "Basic data",
"acl": ["hr"] "acl": ["itManagement"]
}, },
{ {
"url" : "/log", "url" : "/log",
@ -98,8 +96,7 @@
"url": "/roles", "url": "/roles",
"state": "account.card.roles", "state": "account.card.roles",
"component": "vn-user-roles", "component": "vn-user-roles",
"description": "Inherited roles", "description": "Inherited roles"
"acl": ["it"]
}, },
{ {
"url": "/mail-forwarding", "url": "/mail-forwarding",
@ -111,15 +108,16 @@
"url": "/aliases", "url": "/aliases",
"state": "account.card.aliases", "state": "account.card.aliases",
"component": "vn-user-aliases", "component": "vn-user-aliases",
"description": "Mail aliases", "description": "Mail aliases"
"acl": ["marketing", "hr"]
}, },
{ {
"url": "/privileges", "url": "/privileges",
"state": "account.card.privileges", "state": "account.card.privileges",
"component": "vn-user-privileges", "component": "vn-user-privileges",
"description": "Privileges", "description": "Privileges",
"acl": ["hr"] "params": {
"user": "$ctrl.user"
}
}, },
{ {
"url": "/role?q", "url": "/role?q",
@ -180,8 +178,7 @@
"url": "/alias?q", "url": "/alias?q",
"state": "account.alias", "state": "account.alias",
"component": "vn-alias", "component": "vn-alias",
"description": "Mail aliases", "description": "Mail aliases"
"acl": ["marketing"]
}, },
{ {
"url": "/create", "url": "/create",

View File

@ -8,6 +8,7 @@ class Controller extends Summary {
if (!value) return; if (!value) return;
const filter = { const filter = {
where: {id: value.id},
include: { include: {
relation: 'role', relation: 'role',
scope: { scope: {
@ -15,8 +16,11 @@ class Controller extends Summary {
} }
} }
}; };
this.$http.get(`VnUsers/${value.id}`, {filter}) this.$http.get(`VnUsers/preview`, {filter})
.then(res => this.$.summary = res.data); .then(res => {
const [summary] = res.data;
this.$.summary = summary;
});
} }
get isHr() { get isHr() {
return this.aclService.hasAny(['hr']); return this.aclService.hasAny(['hr']);

View File

@ -77,14 +77,6 @@ module.exports = Self => {
if (salesPersonId) if (salesPersonId)
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);
await models.ClaimLog.create({
originFk: args.id,
userFk: userId,
action: 'insert',
description: 'Claim-pickup-order sent',
changedModel: 'Mail'
});
const email = new Email('claim-pickup-order', params); const email = new Email('claim-pickup-order', params);
return email.send(); return email.send();

View File

@ -52,4 +52,5 @@ columns:
hasInvoiceSimplified: simplified invoice hasInvoiceSimplified: simplified invoice
typeFk: type typeFk: type
lastSalesPersonFk: last salesperson lastSalesPersonFk: last salesperson
rating: rating
recommendedCredit: recommended credit

View File

@ -52,4 +52,5 @@ columns:
hasInvoiceSimplified: factura simple hasInvoiceSimplified: factura simple
typeFk: tipo typeFk: tipo
lastSalesPersonFk: último comercial lastSalesPersonFk: último comercial
rating: clasificación
recommendedCredit: crédito recomendado

View File

@ -19,9 +19,6 @@ module.exports = Self => {
}); });
Self.confirmTransaction = async(ctx, id, options) => { Self.confirmTransaction = async(ctx, id, options) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
let tx; let tx;
const myOptions = {userId: ctx.req.accessToken.userId}; const myOptions = {userId: ctx.req.accessToken.userId};
@ -34,29 +31,8 @@ module.exports = Self => {
} }
try { try {
const oldTpvTransaction = await models.TpvTransaction.findById(id, null, myOptions);
const confirm = await Self.rawSql('CALL hedera.tpvTransaction_confirmById(?)', [id], myOptions); const confirm = await Self.rawSql('CALL hedera.tpvTransaction_confirmById(?)', [id], myOptions);
const tpvTransaction = await models.TpvTransaction.findById(id, null, myOptions);
const oldInstance = {status: oldTpvTransaction.status};
const newInstance = {status: tpvTransaction.status};
const logRecord = {
originFk: tpvTransaction.clientFk,
userFk: userId,
action: 'update',
changedModel: 'TpvTransaction',
changedModelId: id,
oldInstance: oldInstance,
newInstance: newInstance
};
await models.ClientLog.create(logRecord, myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();
return confirm; return confirm;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();

View File

@ -30,34 +30,9 @@ module.exports = Self => {
} }
}); });
Self.sendSms = async(ctx, id, destination, message, options) => { Self.sendSms = async(ctx, id, destination, message) => {
const models = Self.app.models; const models = Self.app.models;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const userId = ctx.req.accessToken.userId;
const sms = await models.Sms.send(ctx, destination, message); const sms = await models.Sms.send(ctx, destination, message);
const logRecord = {
originFk: id,
userFk: userId,
action: 'insert',
changedModel: 'sms',
newInstance: {
destinationFk: id,
destination: destination,
message: message,
statusCode: sms.statusCode,
status: sms.status
}
};
const clientLog = await models.ClientLog.create(logRecord, myOptions);
sms.logId = clientLog.id;
return sms; return sms;
}; };
}; };

View File

@ -13,10 +13,7 @@ describe('client sendSms()', () => {
const sms = await models.Client.sendSms(ctx, id, destination, message, options); const sms = await models.Client.sendSms(ctx, id, destination, message, options);
const createdLog = await models.ClientLog.findById(sms.logId, null, options); expect(sms).toBeDefined();
const json = JSON.parse(JSON.stringify(createdLog.newInstance));
expect(json.message).toEqual(message);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -144,20 +144,7 @@ module.exports = Self => {
if (taxDataChecked && !hasSageData) if (taxDataChecked && !hasSageData)
throw new UserError(`You need to fill sage information before you check verified data`); throw new UserError(`You need to fill sage information before you check verified data`);
if (args.despiteOfClient) {
const logRecord = {
originFk: clientId,
userFk: userId,
action: 'update',
changedModel: 'Client',
changedModelId: clientId,
description: $t(`Client checked as validated despite of duplication`, {
clientId: args.despiteOfClient
})
};
await models.ClientLog.create(logRecord, myOptions);
}
// Remove unwanted properties // Remove unwanted properties
delete args.ctx; delete args.ctx;
delete args.id; delete args.id;

View File

@ -60,6 +60,7 @@ module.exports = Self => {
DISTINCT c.id clientFk, DISTINCT c.id clientFk,
c.name clientName, c.name clientName,
c.salesPersonFk, c.salesPersonFk,
c.businessTypeFk,
u.name salesPersonName, u.name salesPersonName,
d.amount, d.amount,
co.created, co.created,

View File

@ -2,7 +2,7 @@ const UserError = require('vn-loopback/util/user-error');
const base64url = require('base64url'); const base64url = require('base64url');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('confirm', { Self.remoteMethod('confirm', {
description: 'Confirms electronic payment transaction', description: 'Confirms electronic payment transaction',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [ accepts: [
@ -30,7 +30,7 @@ module.exports = Self => {
} }
}); });
Self.confirm = async(ctx, signatureVersion, merchantParameters, signature) => { Self.confirm = async(signatureVersion, merchantParameters, signature) => {
const $ = Self.app.models; const $ = Self.app.models;
let transaction; let transaction;
@ -83,7 +83,7 @@ module.exports = Self => {
params['Ds_Currency'], params['Ds_Currency'],
params['Ds_Response'], params['Ds_Response'],
params['Ds_ErrorCode'] params['Ds_ErrorCode']
], {userId: ctx.req.accessToken.userId}); ]);
return true; return true;
} catch (err) { } catch (err) {

View File

@ -14,7 +14,7 @@ module.exports = Self => {
Self.validatesPresenceOf('street', { Self.validatesPresenceOf('street', {
message: 'Street cannot be empty' message: 'Street cannot be empty'
}); });
Self.validatesPresenceOf('city', { Self.validatesPresenceOf('city', {
message: 'City cannot be empty' message: 'City cannot be empty'
}); });
@ -282,7 +282,7 @@ module.exports = Self => {
await Self.changeCredit(ctx, finalState, changes); await Self.changeCredit(ctx, finalState, changes);
// Credit management changes // Credit management changes
if (orgData?.rating != changes.rating || orgData?.recommendedCredit != changes.recommendedCredit) if (changes?.rating || changes?.recommendedCredit)
await Self.changeCreditManagement(ctx, finalState, changes); await Self.changeCreditManagement(ctx, finalState, changes);
const oldInstance = {}; const oldInstance = {};
@ -401,22 +401,32 @@ module.exports = Self => {
Self.changeCredit = async function changeCredit(ctx, finalState, changes) { Self.changeCredit = async function changeCredit(ctx, finalState, changes) {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.options.accessToken.userId; const userId = ctx.options.accessToken.userId;
const accessToken = {req: {accessToken: ctx.options.accessToken} }; const accessToken = {req: {accessToken: ctx.options.accessToken}};
const canEditCredit = await models.ACL.checkAccessAcl(accessToken, 'Client', 'editCredit', 'WRITE'); const canEditCredit = await models.ACL.checkAccessAcl(accessToken, 'Client', 'editCredit', 'WRITE');
if (!canEditCredit) { if (!canEditCredit) {
const lastCredit = await models.ClientCredit.findOne({ const lastCredit = await models.ClientCredit.findOne({
field: ['workerFk', 'amount'],
where: { where: {
clientFk: finalState.id clientFk: finalState.id
}, },
order: 'id DESC' order: 'id DESC'
}, ctx.options); }, ctx.options);
const lastAmount = lastCredit && lastCredit.amount; if (lastCredit && lastCredit.amount == 0) {
const lastCreditIsNotEditable = !await models.ACL.checkAccessAcl(accessToken, 'Client', 'isNotEditableCredit', 'WRITE'); const zeroCreditEditor =
await models.ACL.checkAccessAcl(accessToken, 'Client', 'zeroCreditEditor', 'WRITE');
const lastCreditIsNotEditable =
await models.ACL.checkAccessAcl(
{req: {accessToken: {userId: lastCredit.workerFk}}},
'Client',
'zeroCreditEditor',
'WRITE'
);
if (lastAmount == 0 && lastCreditIsNotEditable) if (lastCreditIsNotEditable && !zeroCreditEditor)
throw new UserError(`You can't change the credit set to zero from a financialBoss`); throw new UserError(`You can't change the credit set to zero from a financialBoss`);
}
const creditLimits = await models.ClientCreditLimit.find({ const creditLimits = await models.ClientCreditLimit.find({
fields: ['roleFk'], fields: ['roleFk'],
@ -479,21 +489,6 @@ module.exports = Self => {
hasChanges = oldData.name != changes.name || oldData.active != changes.active; hasChanges = oldData.name != changes.name || oldData.active != changes.active;
if (!hasChanges) return; if (!hasChanges) return;
const isClient = await Self.app.models.Client.count({id: oldData.id});
if (isClient) {
const loopBackContext = LoopBackContext.getCurrentContext();
const userId = loopBackContext.active.accessToken.userId;
const logRecord = {
originFk: oldData.id,
userFk: userId,
action: 'update',
changedModel: 'VnUser',
oldInstance: {name: oldData.name, active: oldData.active},
newInstance: {name: changes.name, active: changes.active}
};
await Self.app.models.ClientLog.create(logRecord);
}
} }
}); });
}); });

View File

@ -60,22 +60,22 @@ describe('Client Model', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const context = {options}; const ctx = {options};
// Set credit to zero by a financialBoss // Set credit to zero by a financialBoss
const financialBoss = await models.VnUser.findOne({ const financialBoss = await models.VnUser.findOne({
where: {name: 'financialBoss'} where: {name: 'financialBoss'}
}, options); }, options);
context.options.accessToken = {userId: financialBoss.id}; ctx.options.accessToken = {userId: financialBoss.id};
await models.Client.changeCredit(context, instance, {credit: 0}); await models.Client.changeCredit(ctx, instance, {credit: 0});
const salesAssistant = await models.VnUser.findOne({ const salesAssistant = await models.VnUser.findOne({
where: {name: 'salesAssistant'} where: {name: 'salesAssistant'}
}, options); }, options);
context.options.accessToken = {userId: salesAssistant.id}; ctx.options.accessToken = {userId: salesAssistant.id};
await models.Client.changeCredit(context, instance, {credit: 300}); await models.Client.changeCredit(ctx, instance, {credit: 300});
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -93,14 +93,14 @@ describe('Client Model', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const context = {options}; const ctx = {options};
const salesAssistant = await models.VnUser.findOne({ const salesAssistant = await models.VnUser.findOne({
where: {name: 'salesAssistant'} where: {name: 'salesAssistant'}
}, options); }, options);
context.options.accessToken = {userId: salesAssistant.id}; ctx.options.accessToken = {userId: salesAssistant.id};
await models.Client.changeCredit(context, instance, {credit: 99999}); await models.Client.changeCredit(ctx, instance, {credit: 99999});
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -4,7 +4,7 @@
filter="::$ctrl.filter" filter="::$ctrl.filter"
limit="20" limit="20"
order="amount DESC" order="amount DESC"
data="defaulters" data="$ctrl.defaulters"
on-data-change="$ctrl.reCheck()" on-data-change="$ctrl.reCheck()"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
@ -34,7 +34,7 @@
</div> </div>
<div class="vn-pa-md"> <div class="vn-pa-md">
<vn-button <vn-button
ng-show="$ctrl.checked.length > 0" disabled="$ctrl.checked.length == 0"
ng-click="notesDialog.show()" ng-click="notesDialog.show()"
name="notesDialog" name="notesDialog"
vn-tooltip="Add observation" vn-tooltip="Add observation"
@ -54,6 +54,9 @@
<th field="clientFk"> <th field="clientFk">
<span translate>Client</span> <span translate>Client</span>
</th> </th>
<th>
<span translate>Es trabajador</span>
</th>
<th field="salesPersonFk"> <th field="salesPersonFk">
<span translate>Comercial</span> <span translate>Comercial</span>
</th> </th>
@ -94,7 +97,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="defaulter in defaulters"> <tr ng-repeat="defaulter in $ctrl.defaulters">
<td shrink> <td shrink>
<vn-check <vn-check
ng-model="defaulter.checked" ng-model="defaulter.checked"
@ -110,6 +113,12 @@
{{::defaulter.clientName}} {{::defaulter.clientName}}
</span> </span>
</td> </td>
<td>
<vn-check
ng-model="defaulter.isWorker"
disabled="true">
</vn-check>
</td>
<td> <td>
<span <span
title="{{::defaulter.salesPersonName}}" title="{{::defaulter.salesPersonName}}"

View File

@ -6,6 +6,7 @@ export default class Controller extends Section {
constructor($element, $) { constructor($element, $) {
super($element, $); super($element, $);
this.defaulter = {}; this.defaulter = {};
this.defaulters = [];
this.checkedDefaulers = []; this.checkedDefaulers = [];
this.smartTableOptions = { this.smartTableOptions = {
@ -69,6 +70,18 @@ export default class Controller extends Section {
this.getBalanceDueTotal(); this.getBalanceDueTotal();
} }
set defaulters(value) {
if (!value || !value.length) return;
for (let defaulter of value)
defaulter.isWorker = defaulter.businessTypeFk === 'worker';
this._defaulters = value;
}
get defaulters() {
return this._defaulters;
}
get checked() { get checked() {
const clients = this.$.model.data || []; const clients = this.$.model.data || [];
const checkedLines = []; const checkedLines = [];

View File

@ -1,7 +1,5 @@
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
url="VnUsers"
id-field="id"
data="$ctrl.account" data="$ctrl.account"
form="form"> form="form">
</vn-watcher> </vn-watcher>
@ -51,9 +49,9 @@
label="Save"> label="Save">
</vn-submit> </vn-submit>
<vn-button <vn-button
ng-if="$ctrl.canChangePassword" ng-if="$ctrl.canChangePassword"
label="Change password" label="Change password"
vn-dialog="change-pass"> vn-dialog="change-pass">
</vn-button> </vn-button>
<vn-button <vn-button
class="cancel" class="cancel"

View File

@ -8,6 +8,22 @@ export default class Controller extends Section {
this.canEnableCheckBox = true; this.canEnableCheckBox = true;
} }
set client(value) {
this._client = value;
if (!value) return;
const filter = {where: {id: value.id}};
this.$http.get(`VnUsers/preview`, {filter})
.then(res => {
const [user] = res.data;
this.account = user;
});
}
get client() {
return this._client;
}
$onChanges() { $onChanges() {
if (this.client) { if (this.client) {
this.account = this.client.account; this.account = this.client.account;

View File

@ -5,12 +5,14 @@ describe('Component VnClientWebAccess', () => {
let $scope; let $scope;
let vnApp; let vnApp;
let controller; let controller;
let $httpParamSerializer;
beforeEach(ngModule('client')); beforeEach(ngModule('client'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _vnApp_) => { beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_, _vnApp_) => {
$scope = $rootScope.$new(); $scope = $rootScope.$new();
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
vnApp = _vnApp_; vnApp = _vnApp_;
jest.spyOn(vnApp, 'showError'); jest.spyOn(vnApp, 'showError');
const $element = angular.element('<vn-client-web-access></vn-client-web-access>'); const $element = angular.element('<vn-client-web-access></vn-client-web-access>');
@ -32,7 +34,10 @@ describe('Component VnClientWebAccess', () => {
describe('isCustomer()', () => { describe('isCustomer()', () => {
it('should return true if the password can be modified', () => { it('should return true if the password can be modified', () => {
controller.client = {id: '1234'}; controller.client = {id: '1234'};
const filter = {where: {id: controller.client.id}};
const serializedParams = $httpParamSerializer({filter});
$httpBackend.expectGET(`VnUsers/preview?${serializedParams}`).respond('foo');
$httpBackend.expectGET(`Clients/${controller.client.id}/hasCustomerRole`).respond(true); $httpBackend.expectGET(`Clients/${controller.client.id}/hasCustomerRole`).respond(true);
controller.isCustomer(); controller.isCustomer();
$httpBackend.flush(); $httpBackend.flush();
@ -42,7 +47,10 @@ describe('Component VnClientWebAccess', () => {
it(`should return a false if the password can't be modified`, () => { it(`should return a false if the password can't be modified`, () => {
controller.client = {id: '1234'}; controller.client = {id: '1234'};
const filter = {where: {id: controller.client.id}};
const serializedParams = $httpParamSerializer({filter});
$httpBackend.expectGET(`VnUsers/preview?${serializedParams}`).respond('foo');
$httpBackend.expectGET(`Clients/${controller.client.id}/hasCustomerRole`).respond(false); $httpBackend.expectGET(`Clients/${controller.client.id}/hasCustomerRole`).respond(false);
controller.isCustomer(); controller.isCustomer();
$httpBackend.flush(); $httpBackend.flush();
@ -54,9 +62,12 @@ describe('Component VnClientWebAccess', () => {
describe('checkConditions()', () => { describe('checkConditions()', () => {
it('should perform a query to check if the client is valid', () => { it('should perform a query to check if the client is valid', () => {
controller.client = {id: '1234'}; controller.client = {id: '1234'};
const filter = {where: {id: controller.client.id}};
const serializedParams = $httpParamSerializer({filter});
expect(controller.canEnableCheckBox).toBeTruthy(); expect(controller.canEnableCheckBox).toBeTruthy();
$httpBackend.expectGET(`VnUsers/preview?${serializedParams}`).respond('foo');
$httpBackend.expectGET(`Clients/${controller.client.id}/isValidClient`).respond(false); $httpBackend.expectGET(`Clients/${controller.client.id}/isValidClient`).respond(false);
controller.checkConditions(); controller.checkConditions();
$httpBackend.flush(); $httpBackend.flush();
@ -82,7 +93,10 @@ describe('Component VnClientWebAccess', () => {
controller.newPassword = 'm24x8'; controller.newPassword = 'm24x8';
controller.repeatPassword = 'm24x8'; controller.repeatPassword = 'm24x8';
controller.canChangePassword = true; controller.canChangePassword = true;
const filter = {where: {id: controller.client.id}};
const serializedParams = $httpParamSerializer({filter});
$httpBackend.expectGET(`VnUsers/preview?${serializedParams}`).respond('foo');
const query = `Clients/${controller.client.id}/setPassword`; const query = `Clients/${controller.client.id}/setPassword`;
$httpBackend.expectPATCH(query, {newPassword: controller.newPassword}).respond('done'); $httpBackend.expectPATCH(query, {newPassword: controller.newPassword}).respond('done');
controller.onPassChange(); controller.onPassChange();

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