Merge branch 'test' into 5900-removeDuplicatedLogs
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Javi Gallego 2023-06-29 12:43:16 +00:00
commit bc4058512a
31 changed files with 344 additions and 224 deletions

View File

@ -18,11 +18,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### 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
### 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
-
- (General -> Históricos) Duplicidades eliminadas
- (Facturas -> Facturación global) Solucionados fallos que paran el proceso
## [2324.01] - 2023-06-15

View File

@ -17,23 +17,22 @@ module.exports = Self => {
Self.renewToken = async function(ctx) {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const created = ctx.req.accessToken.created;
const tokenId = ctx.req.accessToken.id;
const token = ctx.req.accessToken;
const now = new Date();
const differenceMilliseconds = now - created;
const differenceMilliseconds = now - token.created;
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
const accessTokenConfig = await models.AccessTokenConfig.findOne({fields: ['renewPeriod']});
const fields = ['renewPeriod', 'courtesyTime'];
const accessTokenConfig = await models.AccessTokenConfig.findOne({fields});
if (differenceSeconds <= accessTokenConfig.renewPeriod)
throw new UserError(`The renew period has not been exceeded`);
if (differenceSeconds < accessTokenConfig.renewPeriod - accessTokenConfig.courtesyTime)
throw new UserError(`The renew period has not been exceeded`, 'periodNotExceeded');
await Self.logout(tokenId);
const user = await Self.findById(userId);
await Self.logout(token.id);
const user = await Self.findById(token.userId);
const accessToken = await user.createAccessToken();
return {token: accessToken.id, created: accessToken.created};
return {id: accessToken.id, ttl: accessToken.ttl};
};
};

View File

@ -76,6 +76,6 @@ module.exports = Self => {
let loginInfo = Object.assign({password}, userInfo);
token = await Self.login(loginInfo, 'user');
return {token: token.id, created: token.created};
return {token: token.id, ttl: token.ttl};
};
};

View File

@ -16,6 +16,10 @@
"type": "number",
"required": true
},
"courtesyTime": {
"type": "number",
"required": true
},
"renewInterval": {
"type": "number",
"required": true

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

@ -1,10 +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`, `renewInterval`)
INSERT IGNORE INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `courtesyTime`, `renewInterval`)
VALUES
(1, 21600, 300);
(1, 21600, 5, 300);

View File

@ -699,12 +699,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`)
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)),
(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)),
(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)),
(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)),
(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)),
(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)),
(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, 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, 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, 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, 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, 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()),
(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()),
@ -2572,6 +2572,26 @@ INSERT INTO `vn`.`ticketRecalc`(`ticketFk`)
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`)
VALUES
(1, 1, 1),

View File

@ -12605,7 +12605,7 @@ BEGIN
FROM myTicket t
WHERE shipped BETWEEN TIMESTAMP(vFrom) AND TIMESTAMP(vTo, '23:59:59');
CALL vn.ticketGetTotal;
CALL vn.ticketGetTotal(NULL);
SELECT v.id, IFNULL(v.landed, v.shipped) landed,
v.shipped, v.companyFk, v.nickname,
@ -47167,7 +47167,7 @@ BEGIN
ENGINE = MEMORY
SELECT vTicketId ticketFk;
CALL ticketGetTotal;
CALL ticketGetTotal(NULL);
SELECT total INTO vTotal FROM tmp.ticketTotal;
@ -58494,6 +58494,13 @@ BEGIN
DECLARE vIsCEESerial BOOL DEFAULT FALSE;
DECLARE vIsCorrectInvoiceDate BOOL;
DECLARE vMaxShipped DATE;
DECLARE vDone BOOL;
DECLARE vTicketFk INT;
DECLARE vCursor CURSOR FOR
SELECT id
FROM ticketToInvoice;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
SET vInvoiceDate = IFNULL(vInvoiceDate, util.VN_CURDATE());
@ -58579,6 +58586,20 @@ BEGIN
FROM invoiceOut
WHERE id = vNewInvoiceId;
OPEN vCursor;
l: LOOP
SET vDone = FALSE;
FETCH vCursor INTO vTicketFk;
IF vDone THEN
LEAVE l;
END IF;
CALL ticket_recalc(vTicketFk, vTaxArea);
END LOOP;
CLOSE vCursor;
UPDATE ticket t
JOIN tmp.ticketToInvoice ti ON ti.id = t.id
SET t.refFk = vNewRef;
@ -58594,10 +58615,6 @@ BEGIN
INSERT INTO ticketTracking(stateFk,ticketFk,workerFk)
SELECT * FROM tmp.updateInter;
INSERT INTO ticketLog (action, userFk, originFk, description)
SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef)
FROM tmp.ticketToInvoice ti;
CALL invoiceExpenceMake(vNewInvoiceId);
CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
@ -69870,7 +69887,7 @@ DELIMITER ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `ticketGetTotal`()
CREATE DEFINER=`root`@`localhost` PROCEDURE `ticketGetTotal`(vTaxArea VARCHAR(25))
BEGIN
/**
* Calcula el total con IVA para un conjunto de tickets.
@ -69878,7 +69895,7 @@ BEGIN
* @table tmp.ticket(ticketFk) Identificadores de los tickets a calcular
* @return tmp.ticketTotal Total para cada ticket
*/
CALL ticket_getTax(NULL);
CALL ticket_getTax(vTaxArea);
DROP TEMPORARY TABLE IF EXISTS tmp.ticketTotal;
CREATE TEMPORARY TABLE tmp.ticketTotal
@ -70029,7 +70046,7 @@ BEGIN
AND clientFk = vClientFk
AND shipped > '2001-01-01';
CALL vn.ticketGetTotal;
CALL vn.ticketGetTotal(NULL);
SELECT c.id,
c.name as Cliente,
@ -71878,7 +71895,7 @@ proc: BEGIN
LEAVE myLoop;
END IF;
CALL ticket_recalc(vTicketFk);
CALL ticket_recalc(vTicketFk, NULL);
END LOOP;
CLOSE cCur;
@ -72334,14 +72351,14 @@ BEGIN
CALL addressTaxArea ();
IF vTaxArea > '' THEN
IF vTaxArea IS NOT NULL THEN
UPDATE tmp.addressTaxArea
SET areaFk = vTaxArea;
END IF;
/* Solo se calcula la base imponible (taxableBase) y el impuesto se calculará posteriormente
* No se debería cambiar el sistema por problemas con los decimales
*/
DROP TEMPORARY TABLE IF EXISTS tmp.ticketTax;
CREATE TEMPORARY TABLE tmp.ticketTax
(PRIMARY KEY (ticketFk, code, rate))
@ -72349,7 +72366,7 @@ BEGIN
SELECT * FROM (
SELECT tmpTicket.ticketFk,
bp.pgcFk,
SUM(s.quantity * s.price * (100 - s.discount)/100 ) AS taxableBase,
SUM(s.quantity * s.price * (100 - s.discount)/100 ) taxableBase,
pgc.rate,
tc.code,
bp.priority
@ -72369,7 +72386,7 @@ BEGIN
JOIN pgc ON pgc.code = bp.pgcFk
JOIN taxClass tc ON tc.id = bp.taxClassFk
GROUP BY tmpTicket.ticketFk, pgc.code, pgc.rate
HAVING taxableBase != 0) t3
HAVING taxableBase <> 0) t3
ORDER BY priority;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketServiceTax;
@ -72378,7 +72395,7 @@ BEGIN
ENGINE = MEMORY
SELECT tt.ticketFk,
pgc.code pgcFk,
SUM(ts.quantity * ts.price) AS taxableBase,
SUM(ts.quantity * ts.price) taxableBase,
pgc.rate,
tc.code
FROM tmp.ticket tt
@ -72394,7 +72411,7 @@ BEGIN
JOIN pgc ON pgc.code = bp.pgcFk
JOIN taxClass tc ON tc.id = bp.taxClassFk
GROUP BY tt.ticketFk, pgc.code
HAVING taxableBase != 0;
HAVING taxableBase <> 0;
INSERT INTO tmp.ticketTax (ticketFk, pgcFk, taxableBase, rate, code)
SELECT ts.ticketFk, ts.pgcFk, ts.taxableBase, ts.rate, ts.code
@ -72725,20 +72742,31 @@ DELIMITER ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `ticket_recalc`(vTicketId INT)
BEGIN
CREATE DEFINER=`root`@`localhost` PROCEDURE `ticket_recalc`(vSelf INT, vTaxArea VARCHAR(25))
proc:BEGIN
/**
* Calcula y guarda el total con/sin IVA en un ticket.
*
* @param vTicketId Identificador del ticket
*/
DECLARE hasInvoice BOOL;
SELECT COUNT(*) INTO hasInvoice
FROM ticket
WHERE id = vSelf
AND refFk IS NOT NULL;
IF hasInvoice THEN
LEAVE proc;
END IF;
DROP TEMPORARY TABLE IF EXISTS tmp.ticket;
CREATE TEMPORARY TABLE tmp.ticket
ENGINE = MEMORY
SELECT vTicketId ticketFk;
SELECT vSelf ticketFk;
CALL ticketGetTotal;
CALL ticketGetTotal(vTaxArea);
UPDATE ticket t
JOIN tmp.ticketTotal tt ON tt.ticketFk = t.id

View File

@ -9,7 +9,7 @@ describe('Travel descriptor path', () => {
browser = await getBrowser();
page = browser.page;
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.waitForState('travel.card.summary');
});
@ -23,7 +23,7 @@ describe('Travel descriptor path', () => {
await page.waitForState('travel.index');
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() => {

View File

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

View File

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

View File

@ -6,37 +6,118 @@ import ngModule from '../module';
* @property {String} token The current login token or %null
*/
export default class Token {
constructor() {
try {
this.token = sessionStorage.getItem('vnToken');
this.created = sessionStorage.getItem('vnTokenCreated');
if (!this.token) {
this.token = localStorage.getItem('vnToken');
this.created = localStorage.getItem('vnTokenCreated');
}
} catch (e) {}
}
set(token, created, remember) {
this.unset();
try {
if (remember) {
localStorage.setItem('vnToken', token);
localStorage.setItem('vnTokenCreated', created);
} else {
sessionStorage.setItem('vnToken', token);
sessionStorage.setItem('vnTokenCreated', created);
}
} catch (e) {}
constructor(vnInterceptor, $http, $rootScope) {
Object.assign(this, {
vnInterceptor,
$http,
$rootScope
});
this.token = token;
this.created = created;
try {
this.getStorage(sessionStorage);
this.remember = true;
if (!this.token) {
this.getStorage(localStorage);
this.remember = false;
}
} catch (e) {}
}
set(token, created, ttl, remember) {
this.unset();
Object.assign(this, {
token,
created,
ttl,
remember
});
this.vnInterceptor.setToken(token);
try {
if (remember)
this.setStorage(localStorage, token, created, ttl);
else
this.setStorage(sessionStorage, token, created, ttl);
} catch (err) {
console.error(err);
}
}
unset() {
localStorage.removeItem('vnToken');
sessionStorage.removeItem('vnToken');
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);

View File

@ -42,7 +42,7 @@
<button class="buttonAccount">
<img
id="user"
ng-src="{{$ctrl.getImageUrl()}}"
ng-src="{{::$ctrl.getImageUrl()}}"
ng-click="userPopover.show($event)"
translate-attr="{title: 'Account'}"
on-error-src/>

View File

@ -3,14 +3,13 @@ import Component from 'core/lib/component';
import './style.scss';
export class Layout extends Component {
constructor($element, $, vnModules, vnToken) {
constructor($element, $, vnModules) {
super($element, $);
this.modules = vnModules.get();
}
$onInit() {
this.getUserData();
this.getAccessTokenConfig();
}
getUserData() {
@ -32,41 +31,11 @@ export class Layout extends Component {
window.location.reload();
}
getAccessTokenConfig() {
this.$http.get('AccessTokenConfigs').then(json => {
const firtsResult = json.data[0];
if (!firtsResult) return;
this.renewPeriod = firtsResult.renewPeriod;
this.renewInterval = firtsResult.renewInterval;
const intervalMilliseconds = firtsResult.renewInterval * 1000;
this.inservalId = setInterval(this.checkTokenValidity.bind(this), intervalMilliseconds);
});
}
checkTokenValidity() {
const now = new Date();
const differenceMilliseconds = now - new Date(this.vnToken.created);
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
if (differenceSeconds > this.renewPeriod) {
this.$http.post('VnUsers/renewToken')
.then(json => {
if (json.data.token) {
let remember = true;
if (window.sessionStorage.vnToken) remember = false;
this.vnToken.set(json.data.token, json.data.created, remember);
}
});
}
}
$onDestroy() {
clearInterval(this.inservalId);
this.vnToken.stopRenewer();
}
}
Layout.$inject = ['$element', '$scope', 'vnModules', 'vnToken'];
Layout.$inject = ['$element', '$scope', 'vnModules'];
ngModule.vnComponent('vnLayout', {
template: require('./index.html'),

View File

@ -37,49 +37,4 @@ describe('Component vnLayout', () => {
expect(url).not.toBeDefined();
});
});
describe('getAccessTokenConfig()', () => {
it(`should set the renewPeriod and renewInterval properties in localStorage`, () => {
const response = [{
renewPeriod: 100,
renewInterval: 5
}];
$httpBackend.expect('GET', `AccessTokenConfigs`).respond(response);
controller.getAccessTokenConfig();
$httpBackend.flush();
expect(controller.renewPeriod).toBe(100);
expect(controller.renewInterval).toBe(5);
expect(controller.inservalId).toBeDefined();
});
});
describe('checkTokenValidity()', () => {
it(`should not call renewToken and not set vnToken in the controller`, () => {
controller.renewPeriod = 100;
controller.vnToken.created = new Date();
controller.checkTokenValidity();
expect(controller.vnToken.token).toBeNull();
});
it(`should call renewToken and set vnToken properties in the controller`, () => {
const response = {
token: 999,
created: new Date()
};
controller.renewPeriod = 100;
const oneHourBefore = new Date(Date.now() - (60 * 60 * 1000));
controller.vnToken.created = oneHourBefore;
$httpBackend.expect('POST', `VnUsers/renewToken`).respond(response);
controller.checkTokenValidity();
$httpBackend.flush();
expect(controller.vnToken.token).toBe(999);
expect(controller.vnToken.created).toEqual(response.created);
});
});
});

View File

@ -3,7 +3,7 @@
url="{{$ctrl.url}}"
filter="$ctrl.filter"
data="$ctrl.logs"
order="creationDate DESC, originFk DESC, id DESC"
order="creationDate DESC, id DESC"
limit="20">
</vn-crud-model>
<vn-crud-model
@ -24,7 +24,8 @@
</div>
<div class="user-log vn-mb-sm" ng-repeat="userLog in ::originLog.logs">
<div class="timeline">
<vn-avatar
<div class="user-avatar">
<vn-avatar
ng-class="::{system: !userLog.user}"
val="{{::userLog.user ? userLog.user.nickname : $ctrl.$t('System')}}"
ng-click="$ctrl.showWorkerDescriptor($event, userLog)">
@ -33,6 +34,7 @@
ng-src="/api/Images/user/160x160/{{::userLog.userFk}}/download?access_token={{::$ctrl.vnToken.token}}">
</img>
</vn-avatar>
</div>
<div class="arrow bg-panel" ng-if="::$ctrl.byRecord"></div>
<div class="line"></div>
</div>
@ -228,9 +230,6 @@
</vn-date-picker>
</form>
</vn-side-menu>
<vn-worker-descriptor-popover
vn-id="worker-descriptor">
</vn-worker-descriptor-popover>
<vn-popover vn-id="instance-popover">
<tpl-body class="vn-log-instance">
<vn-spinner
@ -240,9 +239,9 @@
</vn-spinner>
<div
ng-if="!$ctrl.instance.canceler" class="instance">
<div class="header vn-pa-sm">
<h6 class="header vn-pa-sm">
{{$ctrl.instance.modelLog.modelI18n}} #{{$ctrl.instance.modelLog.id}}
</div>
</h6>
<div class="change-detail vn-pa-sm">
<div ng-if="$ctrl.instance.props"
ng-repeat="prop in $ctrl.instance.props">
@ -258,3 +257,6 @@
</div>
</tpl-body>
</vn-popover>
<vn-worker-descriptor-popover
vn-id="worker-descriptor">
</vn-worker-descriptor-popover>

View File

@ -67,7 +67,7 @@ export default class Controller extends Section {
$onInit() {
const match = this.url?.match(/(.*)Logs$/);
this.modelI18n = this.translateModel(match && match[1]);
this.modelI18n = match && this.translateModel(match[1]);
}
$postLink() {
@ -115,23 +115,21 @@ export default class Controller extends Section {
// User
const userChanged = originChanged
|| log.userFk != prevLog.userFk
|| nLogs >= 6;
|| log.userFk != prevLog.userFk;
if (userChanged) {
originLog.logs.push(userLog = {
user: log.user,
userFk: log.userFk,
logs: []
});
nLogs = 0;
}
nLogs++;
// Model
const modelChanged = userChanged
|| log.changedModel != prevLog.changedModel
|| log.changedModelId != prevLog.changedModelId;
|| log.changedModelId != prevLog.changedModelId
|| nLogs >= 6;
if (modelChanged) {
userLog.logs.push(modelLog = {
model: log.changedModel,
@ -140,7 +138,9 @@ export default class Controller extends Section {
showValue: log.changedModelValue,
logs: []
});
nLogs = 0;
}
nLogs++;
modelLog.logs.push(log);
@ -181,8 +181,8 @@ export default class Controller extends Section {
props.push({
name: prop,
nameI18n: firstUpper(locale.columns?.[prop]) || prop,
old: getVal(olds, prop),
val: getVal(vals, prop)
val: getVal(vals, prop),
old: olds && getVal(olds, prop)
});
}
props.sort(
@ -220,7 +220,7 @@ export default class Controller extends Section {
const instance = res.data;
const propNames = Object.keys(instance);
const locale = window.validations[modelLog.model]?.locale || {};
this.instance.props = this.parseProps(propNames, locale, instance, {});
this.instance.props = this.parseProps(propNames, locale, instance);
})
.finally(() => {
this.instance.canceler = null;

View File

@ -44,11 +44,20 @@ vn-log {
right: -4px;
z-index: 1;
}
& > vn-avatar {
cursor: pointer;
& > .user-avatar {
background-color: $color-bg;
padding: $spacing-sm 0;
margin-top: -$spacing-sm;
position: sticky;
top: 64px;
&.system {
background-color: $color-main !important;
& > vn-avatar {
cursor: pointer;
display: block;
&.system {
background-color: $color-main !important;
}
}
}
& > .line {
@ -57,8 +66,8 @@ vn-log {
width: 2px;
left: 18px;
z-index: -1;
top: 44px;
bottom: -2px;
top: 0;
bottom: -$spacing-sm;
}
}
&:last-child > .timeline > .line {
@ -228,6 +237,7 @@ vn-log {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin: 0;
}
& > .change-detail {
color: $color-font-light;

View File

@ -11,7 +11,8 @@ function config($stateProvider, $urlRouterProvider) {
abstract: true,
template: '<vn-layout></vn-layout>',
resolve: {
config: ['vnConfig', vnConfig => vnConfig.initialize()]
config: ['vnConfig', vnConfig => vnConfig.initialize()],
token: ['vnToken', vnToken => vnToken.fetchConfig()]
}
})
.state('outLayout', {

View File

@ -37,37 +37,52 @@ module.exports = Self => {
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'],
fields: ['id', 'creationDate', 'newInstance'],
where: Object.assign(createdWhere, where),
order: 'creationDate DESC'
order: 'creationDate DESC, id DESC'
});
if (!createdLog)
throw new NotFoundError('Cannot find creation log');
const logsWhere = {
id: {between: [
Math.min(id, createdLog.id),
Math.max(id, createdLog.id)
]},
creationDate: {between: [
createdLog.creationDate,
log.creationDate
]}
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'
order: 'creationDate, id'
});
if (!logs.length)
if (!logs.length && !createdLog)
throw new NotFoundError('No logs found for record');
const instance = {};
// Merge all logs in order into one instance
for (const log of logs)
Object.assign(instance, log.newInstance);

View File

@ -106,7 +106,7 @@ module.exports = Self => {
}
}
const query = `CALL vn.ticket_recalc(?)`;
const query = `CALL vn.ticket_recalc(?, NULL)`;
await Self.rawSql(query, [refundTicket.id], myOptions);
if (tx) await tx.commit();

View File

@ -96,7 +96,7 @@ module.exports = Self => {
await sale.updateAttributes({price: newPrice}, myOptions);
await Self.rawSql('CALL vn.manaSpellersRequery(?)', [userId], myOptions);
await Self.rawSql('CALL vn.ticket_recalc(?)', [sale.ticketFk], myOptions);
await Self.rawSql('CALL vn.ticket_recalc(?, NULL)', [sale.ticketFk], myOptions);
const salesPerson = sale.ticket().client().salesPersonUser();
if (salesPerson) {

View File

@ -85,7 +85,7 @@ module.exports = Self => {
}, myOptions);
await Self.rawSql('CALL vn.sale_calculateComponent(?, NULL)', [newSale.id], myOptions);
await Self.rawSql('CALL vn.ticket_recalc(?)', [id], myOptions);
await Self.rawSql('CALL vn.ticket_recalc(?, NULL)', [id], myOptions);
const sale = await models.Sale.findById(newSale.id, {
include: {

View File

@ -81,17 +81,15 @@ module.exports = Self => {
throw new UserError('You must delete all the buy requests first');
// removes item shelvings
if (hasItemShelvingSales && isSalesAssistant) {
const promises = [];
for (let sale of sales) {
if (sale.itemShelvingSale()) {
const itemShelvingSale = sale.itemShelvingSale();
const destroyedShelving = models.ItemShelvingSale.destroyById(itemShelvingSale.id, myOptions);
promises.push(destroyedShelving);
}
const promises = [];
for (let sale of sales) {
if (sale.itemShelvingSale()) {
const itemShelvingSale = sale.itemShelvingSale();
const destroyedShelving = models.ItemShelvingSale.destroyById(itemShelvingSale.id, myOptions);
promises.push(destroyedShelving);
}
await Promise.all(promises);
}
await Promise.all(promises);
// Remove ticket greuges
const ticketGreuges = await models.Greuge.find({where: {ticketFk: id}}, myOptions);

View File

@ -147,7 +147,7 @@ module.exports = Self => {
await Promise.all(promises);
await Self.rawSql('CALL vn.manaSpellersRequery(?)', [userId], myOptions);
await Self.rawSql('CALL vn.ticket_recalc(?)', [id], myOptions);
await Self.rawSql('CALL vn.ticket_recalc(?, NULL)', [id], myOptions);
const ticket = await models.Ticket.findById(id, {
include: {

View File

@ -156,7 +156,7 @@
icon="install_mobile"
ng-show="$ctrl.totalChecked > 0"
ng-click="$ctrl.sendDocuware()"
vn-tooltip="Set as delivered and open delivery note(s)"
vn-tooltip="Set as delivered and send delivery note(s) to the tablet"
tooltip-position="left">
</vn-button>
<vn-button class="round vn-mb-sm"

View File

@ -3,7 +3,7 @@ Go to lines: Ir a lineas
Not available: No disponible
Not visible: No visible
Payment on account...: Pago a cuenta...
Set as delivered and open delivery note(s): Marcar como servido/s y abrir albarán/es
Set as delivered and send delivery note(s) to the tablet: Marcar como servido/s y enviar albarán/es a la tablet
Closure: Cierre
You cannot make a payment on account from multiple clients: No puedes realizar un pago a cuenta de clientes diferentes
Filter by selection: Filtro por selección

View File

@ -37,6 +37,16 @@ class Controller extends SearchPanel {
}
applyFilters(param) {
if (typeof this.filter.scopeDays === 'number') {
const shippedFrom = Date.vnNew();
shippedFrom.setHours(0, 0, 0, 0);
const shippedTo = new Date(shippedFrom.getTime());
shippedTo.setDate(shippedTo.getDate() + this.filter.scopeDays);
shippedTo.setHours(23, 59, 59, 999);
Object.assign(this.filter, {shippedFrom, shippedTo});
}
this.model.applyFilter({}, this.filter)
.then(() => {
if (param && this.model._orgData.length === 1)

View File

@ -92,7 +92,7 @@
</span>
</vn-td>
<vn-td expand>{{entry.supplierName}}</vn-td>
<vn-td shrink>{{entry.ref}}</vn-td>
<vn-td shrink>{{entry.reference}}</vn-td>
<vn-td shrink>{{entry.hb}}</vn-td>
<vn-td shrink>{{entry.freightValue | currency: 'EUR': 2}}</vn-td>
<vn-td shrink>{{entry.packageValue | currency: 'EUR': 2}}</vn-td>

View File

@ -1,10 +1,10 @@
module.exports = function(Self) {
Self.observe('after save', async function(ctx) {
const instance = ctx.instance;
const instance = ctx.data || ctx.instance;
const models = Self.app.models;
const options = ctx.options;
if (!instance.sectorFk || !instance.labelerFk) return;
if (!instance?.sectorFk || !instance?.labelerFk) return;
const sector = await models.Sector.findById(instance.sectorFk, {
fields: ['mainPrinterFk']

View File

@ -16,18 +16,12 @@
"type": "number"
},
"trainFk": {
"type": "number",
"required": true
"type": "number"
},
"itemPackingTypeFk": {
"type": "string",
"required": true
"type": "string"
},
"warehouseFk": {
"type": "number",
"required": true
},
"sectorFk": {
"type": "number"
},
"labelerFk": {