7167-testToMaster_2414 #2244
|
@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2404.01] - 2024-01-25
|
||||
|
||||
### Added
|
||||
### Changed
|
||||
### Fixed
|
||||
|
||||
|
||||
## [2402.01] - 2024-01-11
|
||||
|
||||
### Added
|
||||
|
|
|
@ -1,14 +1,5 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
const {models} = require('vn-loopback/server/server');
|
||||
|
||||
const handlePromiseLogout = (Self, {id}, courtesyTime) => {
|
||||
new Promise(res => {
|
||||
setTimeout(() => {
|
||||
res(Self.logout(id));
|
||||
}
|
||||
, courtesyTime * 1000);
|
||||
});
|
||||
};
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('renewToken', {
|
||||
description: 'Checks if the token has more than renewPeriod seconds to live and if so, renews it',
|
||||
|
@ -28,14 +19,26 @@ module.exports = Self => {
|
|||
const {accessToken: token} = ctx.req;
|
||||
|
||||
// Check if current token is valid
|
||||
const isValid = await validateToken(token);
|
||||
if (isValid)
|
||||
|
||||
const {renewPeriod, courtesyTime} = await models.AccessTokenConfig.findOne({
|
||||
fields: ['renewPeriod', 'courtesyTime']
|
||||
});
|
||||
const now = Date.now();
|
||||
const differenceMilliseconds = now - token.created;
|
||||
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
|
||||
const isNotExceeded = differenceSeconds < renewPeriod - courtesyTime;
|
||||
if (isNotExceeded)
|
||||
return token;
|
||||
|
||||
const {courtesyTime} = await models.AccessTokenConfig.findOne({fields: ['courtesyTime']});
|
||||
|
||||
// Schedule to remove current token
|
||||
handlePromiseLogout(Self, token, courtesyTime);
|
||||
setTimeout(async() => {
|
||||
try {
|
||||
await Self.logout(token.id);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
}
|
||||
}, courtesyTime * 1000);
|
||||
|
||||
// Create new accessToken
|
||||
const user = await Self.findById(token.userId);
|
||||
|
@ -43,14 +46,4 @@ module.exports = Self => {
|
|||
|
||||
return {id: accessToken.id, ttl: accessToken.ttl};
|
||||
};
|
||||
|
||||
async function validateToken(token) {
|
||||
const accessTokenConfig = await models.AccessTokenConfig.findOne({fields: ['renewPeriod', 'courtesyTime']});
|
||||
const now = Date.now();
|
||||
const differenceMilliseconds = now - token.created;
|
||||
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
|
||||
const isValid = differenceSeconds < accessTokenConfig.renewPeriod - accessTokenConfig.courtesyTime;
|
||||
|
||||
return isValid;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('loopback model MailAliasAccount', () => {
|
||||
it('should fail to add a mail Alias if the worker doesnt have ACLs', async() => {
|
||||
const tx = await models.MailAliasAccount.beginTransaction({});
|
||||
let error;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx, accessToken: {userId: 57}};
|
||||
await models.MailAliasAccount.create({mailAlias: 2, account: 5}, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('The alias cant be modified');
|
||||
});
|
||||
|
||||
it('should add a mail Alias', async() => {
|
||||
const tx = await models.MailAliasAccount.beginTransaction({});
|
||||
let error;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx, accessToken: {userId: 9}};
|
||||
await models.MailAliasAccount.create({mailAlias: 2, account: 5}, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should add a mail Alias of an inherit role', async() => {
|
||||
const tx = await models.MailAliasAccount.beginTransaction({});
|
||||
let error;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx, accessToken: {userId: 9}};
|
||||
await models.MailAliasAccount.create({mailAlias: 3, account: 5}, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should delete a mail Alias', async() => {
|
||||
const tx = await models.MailAliasAccount.beginTransaction({});
|
||||
let error;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx, accessToken: {userId: 1}};
|
||||
const mailAclId = 2;
|
||||
await models.MailAliasAccount.destroyAll({id: mailAclId}, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -95,27 +95,30 @@
|
|||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
},
|
||||
{
|
||||
"property": "recoverPassword",
|
||||
"accessType": "EXECUTE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
},
|
||||
{
|
||||
"property": "validateAuth",
|
||||
}, {
|
||||
"property": "recoverPassword",
|
||||
"accessType": "EXECUTE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"property": "validateAuth",
|
||||
"accessType": "EXECUTE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}, {
|
||||
"property": "privileges",
|
||||
"accessType": "*",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$authenticated",
|
||||
"permission": "ALLOW"
|
||||
}, {
|
||||
"property": "renewToken",
|
||||
"accessType": "WRITE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$authenticated",
|
||||
"permission": "ALLOW"
|
||||
}
|
||||
],
|
||||
"scopes": {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
-- Definición de la tabla mailAliasACL
|
||||
|
||||
CREATE OR REPLACE TABLE `account`.`mailAliasAcl` (
|
||||
`mailAliasFk` int(10) unsigned NOT NULL,
|
||||
`roleFk` int(10) unsigned NOT NULL,
|
||||
FOREIGN KEY (`mailAliasFk`) REFERENCES `account`.`mailAlias` (`id`),
|
||||
FOREIGN KEY (`roleFk`) REFERENCES `account`.`role` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
|
@ -0,0 +1,7 @@
|
|||
ALTER TABLE `vn`.`invoiceCorrection` DROP FOREIGN KEY `cplusInvoiceTyoeFk`;
|
||||
ALTER TABLE `vn`.`invoiceCorrection` DROP FOREIGN KEY `invoiceCorrectionType_Fk33`;
|
||||
ALTER TABLE `vn`.`invoiceCorrection` DROP FOREIGN KEY `invoiceCorrection_ibfk_1`;
|
||||
|
||||
ALTER TABLE `vn`.`invoiceCorrection` ADD CONSTRAINT `siiTypeInvoiceOut_FK` FOREIGN KEY (`siiTypeInvoiceOutFk`) REFERENCES `vn`.`siiTypeInvoiceOut`(id) ON UPDATE CASCADE;
|
||||
ALTER TABLE `vn`.`invoiceCorrection` ADD CONSTRAINT `invoiceCorrectionType_FK` FOREIGN KEY (`invoiceCorrectionTypeFk`) REFERENCES `vn`.`invoiceCorrectionType`(id) ON UPDATE CASCADE;
|
||||
ALTER TABLE `vn`.`invoiceCorrection` ADD CONSTRAINT `cplusRectificationType_FK` FOREIGN KEY (`cplusRectificationTypeFk`) REFERENCES `vn`.`cplusRectificationType`(id) ON UPDATE CASCADE;
|
|
@ -0,0 +1,33 @@
|
|||
DELIMITER $$
|
||||
$$
|
||||
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`getTaxBases`()
|
||||
BEGIN
|
||||
/**
|
||||
* Calcula y devuelve en número de bases imponibles postivas y negativas
|
||||
* Requiere la tabla temporal tmp.ticketToInvoice(id)
|
||||
*
|
||||
* returns tmp.taxBases
|
||||
*/
|
||||
|
||||
CREATE OR REPLACE TEMPORARY TABLE tmp.ticket
|
||||
(KEY (ticketFk))
|
||||
ENGINE = MEMORY
|
||||
SELECT id ticketFk
|
||||
FROM tmp.ticketToInvoice;
|
||||
|
||||
CALL ticket_getTax(NULL);
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp.taxBases;
|
||||
CREATE TEMPORARY TABLE tmp.taxBases
|
||||
ENGINE = MEMORY
|
||||
SELECT
|
||||
SUM(taxableBase > 0) as positive,
|
||||
SUM(taxableBase < 0) as negative
|
||||
FROM(
|
||||
SELECT SUM(taxableBase) taxableBase
|
||||
FROM tmp.ticketTax
|
||||
GROUP BY pgcFk
|
||||
) t;
|
||||
|
||||
END$$
|
||||
DELIMITER ;
|
|
@ -0,0 +1,30 @@
|
|||
DELIMITER $$
|
||||
$$
|
||||
CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`hasAnyPositiveBase`() RETURNS tinyint(1)
|
||||
DETERMINISTIC
|
||||
BEGIN
|
||||
|
||||
/**
|
||||
* Calcula si existe alguna base imponible positiva
|
||||
* Requiere la tabla temporal tmp.ticketToInvoice(id) para getTaxBases()
|
||||
*
|
||||
* returns BOOLEAN
|
||||
*/
|
||||
|
||||
DECLARE hasAnyPositiveBase BOOLEAN;
|
||||
|
||||
CALL getTaxBases();
|
||||
|
||||
SELECT positive INTO hasAnyPositiveBase
|
||||
FROM tmp.taxBases
|
||||
LIMIT 1;
|
||||
|
||||
DROP TEMPORARY TABLE
|
||||
tmp.ticketTax,
|
||||
tmp.ticket,
|
||||
tmp.taxBases;
|
||||
|
||||
RETURN hasAnyPositiveBase;
|
||||
|
||||
END$$
|
||||
DELIMITER ;
|
|
@ -0,0 +1,32 @@
|
|||
DELIMITER $$
|
||||
$$
|
||||
CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`hasAnyNegativeBase`() RETURNS tinyint(1)
|
||||
DETERMINISTIC
|
||||
BEGIN
|
||||
|
||||
/**
|
||||
* Calcula si existe alguna base imponible negativa
|
||||
* Requiere la tabla temporal tmp.ticketToInvoice(id) para getTaxBases()
|
||||
*
|
||||
* returns BOOLEAN
|
||||
*/
|
||||
|
||||
DECLARE hasAnyNegativeBase BOOLEAN;
|
||||
|
||||
CALL getTaxBases();
|
||||
|
||||
SELECT negative INTO hasAnyNegativeBase
|
||||
FROM tmp.taxBases
|
||||
LIMIT 1;
|
||||
|
||||
DROP TEMPORARY TABLE
|
||||
tmp.ticketTax,
|
||||
tmp.ticket,
|
||||
tmp.taxBases;
|
||||
|
||||
RETURN hasAnyNegativeBase;
|
||||
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
DELETE FROM `salix`.`ACL`
|
||||
WHERE model = 'VnUser'
|
||||
AND property = 'renewToken';
|
||||
|
||||
INSERT INTO `account`.`role` (name, description)
|
||||
VALUES ('timeControl','Tablet para fichar');
|
||||
|
||||
INSERT INTO `account`.`roleInherit` (role, inheritsFrom)
|
||||
VALUES (127, 11);
|
||||
|
||||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('WorkerTimeControl', 'login', 'READ', 'ALLOW', 'ROLE', 'timeControl'),
|
||||
('WorkerTimeControl', 'getClockIn', 'READ', 'ALLOW', 'ROLE', 'timeControl'),
|
||||
('WorkerTimeControl', 'clockIn', 'WRITE', 'ALLOW', 'ROLE', 'timeControl');
|
||||
|
||||
CALL `account`.`role_sync`();
|
|
@ -602,18 +602,19 @@ INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion`
|
|||
|
||||
INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaFk`, `isCEE`, `type`)
|
||||
VALUES
|
||||
('A', 'Global nacional', 1, 'NATIONAL', 0, 'global'),
|
||||
('T', 'Española rapida', 1, 'NATIONAL', 0, 'quick'),
|
||||
('V', 'Intracomunitaria global', 0, 'CEE', 1, 'global'),
|
||||
('M', 'Múltiple nacional', 1, 'NATIONAL', 0, 'quick'),
|
||||
('E', 'Exportación rápida', 0, 'WORLD', 0, 'quick');
|
||||
('A', 'Global nacional', 1, 'NATIONAL', 0, 'global'),
|
||||
('T', 'Española rapida', 1, 'NATIONAL', 0, 'quick'),
|
||||
('V', 'Intracomunitaria global', 0, 'CEE', 1, 'global'),
|
||||
('M', 'Múltiple nacional', 1, 'NATIONAL', 0, 'quick'),
|
||||
('R', 'Rectificativa', 1, 'NATIONAL', 0, NULL),
|
||||
('E', 'Exportación rápida', 0, 'WORLD', 0, 'quick');
|
||||
|
||||
INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`)
|
||||
VALUES
|
||||
(1, 'T', 1026.24, util.VN_CURDATE(), 1101, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
|
||||
(2, 'T', 121.36, util.VN_CURDATE(), 1102, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
|
||||
(3, 'T', 8.88, util.VN_CURDATE(), 1103, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
|
||||
(4, 'T', 8.88, util.VN_CURDATE(), 1103, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
|
||||
(4, 'T', 8.88, util.VN_CURDATE(), 1104, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
|
||||
(5, 'A', 8.88, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1103, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 442, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1, 0);
|
||||
|
||||
UPDATE `vn`.`invoiceOut` SET ref = 'T1111111' WHERE id = 1;
|
||||
|
@ -719,7 +720,7 @@ 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`, `weight`)
|
||||
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, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1),
|
||||
(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), 2),
|
||||
(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), 1101, 'Bat cave', 1, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2),
|
||||
(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), NULL),
|
||||
(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), NULL),
|
||||
(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), NULL),
|
||||
|
@ -3011,6 +3012,12 @@ INSERT INTO `vn`.`invoiceCorrectionType` (`id`, `description`)
|
|||
(2, 'Error in sales details'),
|
||||
(3, 'Error in customer data');
|
||||
|
||||
INSERT INTO `account`.`mailAliasAcl` (`mailAliasFk`, `roleFk`)
|
||||
VALUES
|
||||
(1, 1),
|
||||
(2, 9),
|
||||
(3, 15);
|
||||
|
||||
INSERT INTO `vn`.`docuwareTablet` (`tablet`,`description`)
|
||||
VALUES
|
||||
('Tablet1','Jarvis tablet'),
|
||||
|
|
|
@ -35,7 +35,7 @@ describe('Ticket index payout path', () => {
|
|||
await page.waitToClick(selectors.ticketsIndex.openAdvancedSearchButton);
|
||||
await page.write(selectors.ticketsIndex.advancedSearchClient, '1101');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForNumberOfElements(selectors.ticketsIndex.anySearchResult, 9);
|
||||
await page.waitForNumberOfElements(selectors.ticketsIndex.anySearchResult, 10);
|
||||
await page.waitToClick(selectors.ticketsIndex.firstTicketCheckbox);
|
||||
await page.waitToClick(selectors.ticketsIndex.secondTicketCheckbox);
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@ describe('InvoiceOut summary path', () => {
|
|||
|
||||
it('should contain the tax breakdown', async() => {
|
||||
const firstTax = await page.waitToGetProperty(selectors.invoiceOutSummary.taxOne, 'innerText');
|
||||
|
||||
const secondTax = await page.waitToGetProperty(selectors.invoiceOutSummary.taxTwo, 'innerText');
|
||||
|
||||
expect(firstTax).toContain('10%');
|
||||
|
@ -37,10 +36,9 @@ describe('InvoiceOut summary path', () => {
|
|||
|
||||
it('should contain the tickets info', async() => {
|
||||
const firstTicket = await page.waitToGetProperty(selectors.invoiceOutSummary.ticketOne, 'innerText');
|
||||
|
||||
const secondTicket = await page.waitToGetProperty(selectors.invoiceOutSummary.ticketTwo, 'innerText');
|
||||
|
||||
expect(firstTicket).toContain('Bat cave');
|
||||
expect(secondTicket).toContain('Stark tower');
|
||||
expect(secondTicket).toContain('Bat cave');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -68,3 +68,4 @@ Load more results: Cargar más resultados
|
|||
Send cau: Enviar cau
|
||||
By sending this ticket, all the data related to the error, the section, the user, etc., are already sent.: Al enviar este cau ya se envían todos los datos relacionados con el error, la sección, el usuario, etc
|
||||
ExplainReason: Explique el motivo por el que no deberia aparecer este fallo
|
||||
You already have the mailAlias: Ya tienes este alias de correo
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"Extension format is invalid": "Extension format is invalid",
|
||||
"NO_ZONE_FOR_THIS_PARAMETERS": "NO_ZONE_FOR_THIS_PARAMETERS",
|
||||
"This client can't be invoiced": "This client can't be invoiced",
|
||||
"You must provide the correction information to generate a corrective invoice": "You must provide the correction information to generate a corrective invoice",
|
||||
"The introduced hour already exists": "The introduced hour already exists",
|
||||
"Invalid parameters to create a new ticket": "Invalid parameters to create a new ticket",
|
||||
"Concept cannot be blank": "Concept cannot be blank",
|
||||
|
@ -178,7 +179,8 @@
|
|||
"The renew period has not been exceeded": "The renew period has not been exceeded",
|
||||
"You can not use the same password": "You can not use the same password",
|
||||
"Valid priorities": "Valid priorities: %d",
|
||||
"Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}",
|
||||
"hasAnyNegativeBase": "Negative basis of tickets: {{ticketsIds}}",
|
||||
"hasAnyPositiveBase": "Positive basis of tickets: {{ticketsIds}}",
|
||||
"This ticket cannot be left empty.": "This ticket cannot be left empty. %s",
|
||||
"Social name should be uppercase": "Social name should be uppercase",
|
||||
"Street should be uppercase": "Street should be uppercase",
|
||||
|
@ -201,5 +203,6 @@
|
|||
"keepPrice": "keepPrice",
|
||||
"Cannot past travels with entries": "Cannot past travels with entries",
|
||||
"The notification subscription of this worker cant be modified": "The notification subscription of this worker cant be modified",
|
||||
"It was not able to remove the next expeditions:": "It was not able to remove the next expeditions: {{expeditions}}"
|
||||
"It was not able to remove the next expeditions:": "It was not able to remove the next expeditions: {{expeditions}}",
|
||||
"Incorrect pin": "Incorrect pin."
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
"The secret can't be blank": "La contraseña no puede estar en blanco",
|
||||
"We weren't able to send this SMS": "No hemos podido enviar el SMS",
|
||||
"This client can't be invoiced": "Este cliente no puede ser facturado",
|
||||
"You must provide the correction information to generate a corrective invoice": "Debes informar la información de corrección para generar una factura rectificativa",
|
||||
"This ticket can't be invoiced": "Este ticket no puede ser facturado",
|
||||
"You cannot add or modify services to an invoiced ticket": "No puedes añadir o modificar servicios a un ticket facturado",
|
||||
"This ticket can not be modified": "Este ticket no puede ser modificado",
|
||||
|
@ -305,7 +306,8 @@
|
|||
"Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico",
|
||||
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado",
|
||||
"Valid priorities": "Prioridades válidas: %d",
|
||||
"Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}",
|
||||
"hasAnyNegativeBase": "Base negativa para los tickets: {{ticketsIds}}",
|
||||
"hasAnyPositiveBase": "Base positivas para los tickets: {{ticketsIds}}",
|
||||
"You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado",
|
||||
"This ticket cannot be left empty.": "Este ticket no se puede dejar vacío. %s",
|
||||
"The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias",
|
||||
|
@ -331,5 +333,9 @@
|
|||
"Cannot past travels with entries": "No se pueden pasar envíos con entradas",
|
||||
"It was not able to remove the next expeditions:": "No se pudo eliminar las siguientes expediciones: {{expeditions}}",
|
||||
"The line could not be marked": "The line could not be marked",
|
||||
"This user does not have an assigned tablet": "Este usuario no tiene tablet asignada"
|
||||
"This user does not have an assigned tablet": "Este usuario no tiene tablet asignada",
|
||||
"Incorrect pin": "Pin incorrecto.",
|
||||
"You already have the mailAlias": "Ya tienes este alias de correo",
|
||||
"The alias cant be modified": "Este alias de correo no puede ser modificado",
|
||||
"No tickets to invoice": "No hay tickets para facturar"
|
||||
}
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
"MailAliasAccount": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"MailAliasAcl": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"MailConfig": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
|
|
@ -2,54 +2,44 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.rewriteDbError(function(err) {
|
||||
if (err.code === 'ER_DUP_ENTRY')
|
||||
return new UserError(`You already have the mailAlias`);
|
||||
return err;
|
||||
});
|
||||
|
||||
Self.observe('before save', async ctx => {
|
||||
const changes = ctx.currentInstance || ctx.instance;
|
||||
|
||||
await Self.hasGrant(ctx, changes.mailAlias);
|
||||
await checkModifyPermission(ctx, changes.mailAlias);
|
||||
});
|
||||
|
||||
Self.observe('before delete', async ctx => {
|
||||
const mailAliasAccount = await Self.findById(ctx.where.id);
|
||||
|
||||
await Self.hasGrant(ctx, mailAliasAccount.mailAlias);
|
||||
await checkModifyPermission(ctx, mailAliasAccount.mailAlias);
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks if current user has
|
||||
* grant to add/remove alias
|
||||
*
|
||||
* @param {Object} ctx - Request context
|
||||
* @param {Interger} mailAlias - mailAlias id
|
||||
* @return {Boolean} True for user with grant
|
||||
*/
|
||||
Self.hasGrant = async function(ctx, mailAlias) {
|
||||
async function checkModifyPermission(ctx, mailAliasFk) {
|
||||
const userId = ctx.options.accessToken.userId;
|
||||
const models = Self.app.models;
|
||||
const accessToken = {req: {accessToken: ctx.options.accessToken}};
|
||||
const userId = accessToken.req.accessToken.userId;
|
||||
|
||||
const canEditAlias = await models.ACL.checkAccessAcl(accessToken, 'MailAliasAccount', 'canEditAlias', 'WRITE');
|
||||
if (canEditAlias) return true;
|
||||
const roles = await models.RoleMapping.find({
|
||||
fields: ['roleId'],
|
||||
where: {principalId: userId}
|
||||
});
|
||||
|
||||
const user = await models.VnUser.findById(userId, {fields: ['hasGrant']});
|
||||
if (!user.hasGrant)
|
||||
throw new UserError(`You don't have grant privilege`);
|
||||
|
||||
const account = await models.Account.findById(userId, {
|
||||
fields: ['id'],
|
||||
include: {
|
||||
relation: 'aliases',
|
||||
scope: {
|
||||
fields: ['mailAlias']
|
||||
}
|
||||
const availableMailAlias = await models.MailAliasAcl.findOne({
|
||||
fields: ['mailAliasFk'],
|
||||
include: {relation: 'mailAlias'},
|
||||
where: {
|
||||
roleFk: {
|
||||
inq: roles.map(role => role.roleId),
|
||||
},
|
||||
mailAliasFk
|
||||
}
|
||||
});
|
||||
|
||||
const aliases = account.aliases().map(alias => alias.mailAlias);
|
||||
|
||||
const hasAlias = aliases.includes(mailAlias);
|
||||
if (!hasAlias)
|
||||
throw new UserError(`You cannot assign/remove an alias that you are not assigned to`);
|
||||
|
||||
return true;
|
||||
};
|
||||
if (!availableMailAlias) throw new UserError('The alias cant be modified');
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "MailAliasAcl",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "account.mailAliasAcl"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"mailAliasFk": {
|
||||
"id": true,
|
||||
"type": "number"
|
||||
},
|
||||
"roleFk": {
|
||||
"id": true,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"mailAlias": {
|
||||
"type": "belongsTo",
|
||||
"model": "MailAlias",
|
||||
"foreignKey": "mailAliasFk"
|
||||
},
|
||||
"role": {
|
||||
"type": "belongsTo",
|
||||
"model": "Role",
|
||||
"foreignKey": "roleFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = function(Self) {
|
||||
Self.remoteMethodCtx('canBeInvoiced', {
|
||||
Self.remoteMethod('canBeInvoiced', {
|
||||
description: 'Change property isEqualizated in all client addresses',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
|
|
|
@ -16,7 +16,7 @@ describe('client consumption() filter', () => {
|
|||
};
|
||||
const result = await models.Client.consumption(ctx, filter, options);
|
||||
|
||||
expect(result.length).toEqual(10);
|
||||
expect(result.length).toEqual(11);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
@ -49,7 +49,7 @@ describe('client consumption() filter', () => {
|
|||
const thirdRow = result[2];
|
||||
|
||||
expect(result.length).toEqual(3);
|
||||
expect(firstRow.quantity).toEqual(10);
|
||||
expect(firstRow.quantity).toEqual(11);
|
||||
expect(secondRow.quantity).toEqual(15);
|
||||
expect(thirdRow.quantity).toEqual(20);
|
||||
|
||||
|
|
|
@ -80,6 +80,7 @@ module.exports = Self => {
|
|||
invoiceType,
|
||||
args.companyFk,
|
||||
args.invoiceDate,
|
||||
null,
|
||||
options
|
||||
);
|
||||
|
||||
|
|
|
@ -10,13 +10,17 @@ module.exports = Self => {
|
|||
type: 'date',
|
||||
description: 'From date',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
}, {
|
||||
arg: 'to',
|
||||
type: 'date',
|
||||
description: 'To date',
|
||||
required: true
|
||||
}],
|
||||
}, {
|
||||
arg: 'filter',
|
||||
type: 'object',
|
||||
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string'
|
||||
},
|
||||
],
|
||||
returns: [
|
||||
{
|
||||
arg: 'body',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('InvoiceOut tranferInvoice()', () => {
|
||||
describe('InvoiceOut transferInvoice()', () => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 5},
|
||||
http: {
|
||||
|
@ -23,20 +23,29 @@ describe('InvoiceOut tranferInvoice()', () => {
|
|||
const tx = await models.InvoiceOut.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
const args = {
|
||||
id: '1',
|
||||
ref: 'T4444444',
|
||||
id: '4',
|
||||
refFk: 'T4444444',
|
||||
newClientFk: 1,
|
||||
cplusRectificationId: 1,
|
||||
siiTypeInvoiceOutId: 1,
|
||||
invoiceCorrectionTypeId: 1
|
||||
cplusRectificationTypeFk: 1,
|
||||
siiTypeInvoiceOutFk: 1,
|
||||
invoiceCorrectionTypeFk: 1
|
||||
};
|
||||
ctx.args = args;
|
||||
try {
|
||||
const {clientFk: oldClient} = await models.InvoiceOut.findById(args.id, {fields: ['clientFk']});
|
||||
const invoicesBefore = await models.InvoiceOut.find({}, options);
|
||||
const result = await models.InvoiceOut.transferInvoice(
|
||||
ctx,
|
||||
options);
|
||||
const invoicesAfter = await models.InvoiceOut.find({}, options);
|
||||
const rectificativeInvoice = invoicesAfter[invoicesAfter.length - 2];
|
||||
const newInvoice = invoicesAfter[invoicesAfter.length - 1];
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(invoicesAfter.length - invoicesBefore.length).toEqual(2);
|
||||
expect(rectificativeInvoice.clientFk).toEqual(oldClient);
|
||||
expect(newInvoice.clientFk).toEqual(args.newClientFk);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
|
@ -49,20 +58,44 @@ describe('InvoiceOut tranferInvoice()', () => {
|
|||
const options = {transaction: tx};
|
||||
const args = {
|
||||
id: '1',
|
||||
ref: 'T1111111',
|
||||
refFk: 'T1111111',
|
||||
newClientFk: 1101,
|
||||
cplusRectificationId: 1,
|
||||
siiTypeInvoiceOutId: 1,
|
||||
invoiceCorrectionTypeId: 1
|
||||
cplusRectificationTypeFk: 1,
|
||||
siiTypeInvoiceOutFk: 1,
|
||||
invoiceCorrectionTypeFk: 1
|
||||
};
|
||||
ctx.args = args;
|
||||
try {
|
||||
await models.InvoiceOut.transferInvoice(
|
||||
ctx,
|
||||
options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(`Select a different client`);
|
||||
await tx.rollback();
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw an UserError when it is refund', async() => {
|
||||
const tx = await models.InvoiceOut.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
const args = {
|
||||
id: '1',
|
||||
refFk: 'T1111111',
|
||||
newClientFk: 1102,
|
||||
cplusRectificationTypeFk: 1,
|
||||
siiTypeInvoiceOutFk: 1,
|
||||
invoiceCorrectionTypeFk: 1
|
||||
};
|
||||
ctx.args = args;
|
||||
try {
|
||||
await models.InvoiceOut.transferInvoice(
|
||||
ctx,
|
||||
options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
expect(e.message).toContain(`This ticket is already a refund`);
|
||||
await tx.rollback();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ module.exports = Self => {
|
|||
description: 'Issued invoice id'
|
||||
},
|
||||
{
|
||||
arg: 'ref',
|
||||
arg: 'refFk',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
|
@ -22,17 +22,17 @@ module.exports = Self => {
|
|||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'cplusRectificationId',
|
||||
arg: 'cplusRectificationTypeFk',
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'siiTypeInvoiceOutId',
|
||||
arg: 'siiTypeInvoiceOutFk',
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'invoiceCorrectionTypeId',
|
||||
arg: 'invoiceCorrectionTypeFk',
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
|
@ -50,14 +50,14 @@ module.exports = Self => {
|
|||
Self.transferInvoice = async(ctx, options) => {
|
||||
const models = Self.app.models;
|
||||
const myOptions = {userId: ctx.req.accessToken.userId};
|
||||
const args = ctx.args;
|
||||
const {id, refFk, newClientFk, cplusRectificationTypeFk, siiTypeInvoiceOutFk, invoiceCorrectionTypeFk} = ctx.args;
|
||||
let tx;
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const {clientFk} = await models.InvoiceOut.findById(args.id);
|
||||
const {clientFk} = await models.InvoiceOut.findById(id);
|
||||
|
||||
if (clientFk == args.newClientFk)
|
||||
if (clientFk == newClientFk)
|
||||
throw new UserError(`Select a different client`);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
|
@ -65,10 +65,10 @@ module.exports = Self => {
|
|||
myOptions.transaction = tx;
|
||||
}
|
||||
try {
|
||||
const filterRef = {where: {refFk: args.ref}};
|
||||
const filterRef = {where: {refFk: refFk}};
|
||||
const tickets = await models.Ticket.find(filterRef, myOptions);
|
||||
const ticketsIds = tickets.map(ticket => ticket.id);
|
||||
await models.Ticket.refund(ctx, ticketsIds, null, myOptions);
|
||||
const refundTickets = await models.Ticket.refund(ctx, ticketsIds, null, myOptions);
|
||||
|
||||
const filterTicket = {where: {ticketFk: {inq: ticketsIds}}};
|
||||
|
||||
|
@ -82,20 +82,16 @@ module.exports = Self => {
|
|||
const clonedTicketIds = [];
|
||||
|
||||
for (const clonedTicket of clonedTickets) {
|
||||
await clonedTicket.updateAttribute('clientFk', args.newClientFk, myOptions);
|
||||
await clonedTicket.updateAttribute('clientFk', newClientFk, myOptions);
|
||||
clonedTicketIds.push(clonedTicket.id);
|
||||
}
|
||||
|
||||
const invoiceIds = await models.Ticket.invoiceTickets(ctx, clonedTicketIds, myOptions);
|
||||
const [invoiceId] = invoiceIds;
|
||||
const invoiceCorrection =
|
||||
{correctedFk: id, cplusRectificationTypeFk, siiTypeInvoiceOutFk, invoiceCorrectionTypeFk};
|
||||
const refundTicketIds = refundTickets.map(ticket => ticket.id);
|
||||
|
||||
await models.InvoiceCorrection.create({
|
||||
correctingFk: invoiceId,
|
||||
correctedFk: args.id,
|
||||
cplusRectificationTypeFk: args.cplusRectificationId,
|
||||
siiTypeInvoiceOutFk: args.siiTypeInvoiceOutId,
|
||||
invoiceCorrectionTypeFk: args.invoiceCorrectionTypeId
|
||||
}, myOptions);
|
||||
await models.Ticket.invoiceTickets(ctx, refundTicketIds, invoiceCorrection, myOptions);
|
||||
const [invoiceId] = await models.Ticket.invoiceTickets(ctx, clonedTicketIds, null, myOptions);
|
||||
|
||||
if (tx) {
|
||||
await tx.commit();
|
||||
|
|
|
@ -16,13 +16,43 @@
|
|||
"type": "number"
|
||||
},
|
||||
"cplusRectificationTypeFk": {
|
||||
"type": "number"
|
||||
"type": "number",
|
||||
"required": true
|
||||
},
|
||||
"siiTypeInvoiceOutFk": {
|
||||
"type": "number"
|
||||
"type": "number",
|
||||
"required": true
|
||||
},
|
||||
"invoiceCorrectionTypeFk": {
|
||||
"type": "number"
|
||||
"type": "number",
|
||||
"required": true
|
||||
},
|
||||
"relations": {
|
||||
"correcting": {
|
||||
"type": "belongsTo",
|
||||
"model": "InvoiceOut",
|
||||
"foreignKey": "correctingFk"
|
||||
},
|
||||
"corrected": {
|
||||
"type": "belongsTo",
|
||||
"model": "InvoiceOut",
|
||||
"foreignKey": "correctedFk"
|
||||
},
|
||||
"cplusRectificationType": {
|
||||
"type": "belongsTo",
|
||||
"model": "cplusRectificationType",
|
||||
"foreignKey": "cplusRectificationTypeFk"
|
||||
},
|
||||
"siiTypeInvoiceOut": {
|
||||
"type": "belongsTo",
|
||||
"model": "siiTypeInvoiceOut",
|
||||
"foreignKey": "siiTypeInvoiceOutFk"
|
||||
},
|
||||
"invoiceCorrectionType": {
|
||||
"type": "belongsTo",
|
||||
"model": "invoiceCorrectionType",
|
||||
"foreignKey": "invoiceCorrectionTypeFk"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
"type": "number",
|
||||
"description": "Identifier"
|
||||
},
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="SiiTypeInvoiceOuts"
|
||||
data="siiTypeInvoiceOut">
|
||||
data="siiTypeInvoiceOuts"
|
||||
where="{code: {like: 'R%'}}">
|
||||
</vn-crud-model>
|
||||
<vn-crud-model
|
||||
auto-load="true"
|
||||
|
@ -185,64 +186,69 @@
|
|||
vn-id="transferInvoice"
|
||||
title="transferInvoice"
|
||||
on-accept="$ctrl.transferInvoice()">
|
||||
<tpl-title translate>
|
||||
transferInvoice
|
||||
</tpl-title>
|
||||
<tpl-body>
|
||||
<section class="transferInvoice">
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
vn-id="client"
|
||||
required="true"
|
||||
url="Clients"
|
||||
label="Client"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
search-function="{or: [{id: $search}, {name: {like: '%'+ $search +'%'}}]}"
|
||||
ng-model="$ctrl.invoiceOut.client.id"
|
||||
initial-data="$ctrl.invoiceOut.client.id"
|
||||
order="id">
|
||||
<tpl-item>
|
||||
#{{id}} - {{::name}}
|
||||
</tpl-item>
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
vn-id="cplusRectificationType"
|
||||
required="true"
|
||||
data="cplusRectificationTypes"
|
||||
show-field="description"
|
||||
value-field="id"
|
||||
ng-model="$ctrl.cplusRectificationType"
|
||||
search-function="{or: [{id: $search}, {description: {like: '%'+ $search +'%'}}]}"
|
||||
label="Cplus Type">
|
||||
<tpl-item>
|
||||
{{::description}}
|
||||
</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
vn-id="cplusInvoiceType"
|
||||
data="siiTypeInvoiceOut"
|
||||
show-field="description"
|
||||
value-field="id"
|
||||
required="true"
|
||||
ng-model="$ctrl.siiTypeInvoiceOut"
|
||||
search-function="{or: [{id: $search}, {description: {like: '%'+ $search +'%'}}]}"
|
||||
label="Class">
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
vn-id="invoiceCorrectionType"
|
||||
data="invoiceCorrectionTypes"
|
||||
ng-model="$ctrl.invoiceCorrectionType"
|
||||
show-field="description"
|
||||
value-field="id"
|
||||
required="true"
|
||||
label="Type">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
</section>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
vn-id="client"
|
||||
required="true"
|
||||
url="Clients"
|
||||
label="Client"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
search-function="{or: [{id: $search}, {name: {like: '%'+ $search +'%'}}]}"
|
||||
ng-model="$ctrl.clientId"
|
||||
order="id">
|
||||
<tpl-item>
|
||||
#{{id}} - {{::name}}
|
||||
</tpl-item>
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
vn-id="cplusRectificationType"
|
||||
required="true"
|
||||
data="cplusRectificationTypes"
|
||||
show-field="description"
|
||||
value-field="id"
|
||||
ng-model="$ctrl.cplusRectificationType"
|
||||
search-function="{or: [{id: $search}, {description: {like: '%'+ $search +'%'}}]}"
|
||||
label="Rectificative type">
|
||||
<tpl-item>
|
||||
{{::description}}
|
||||
</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
vn-id="siiTypeInvoiceOut"
|
||||
data="siiTypeInvoiceOuts"
|
||||
show-field="description"
|
||||
value-field="id"
|
||||
fields="['id','code','description']"
|
||||
required="true"
|
||||
ng-model="$ctrl.siiTypeInvoiceOut"
|
||||
label="Class">
|
||||
<tpl-item>
|
||||
{{::code}} - {{::description}}
|
||||
</tpl-item>
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
vn-id="invoiceCorrectionType"
|
||||
data="invoiceCorrectionTypes"
|
||||
ng-model="$ctrl.invoiceCorrectionType"
|
||||
show-field="description"
|
||||
value-field="id"
|
||||
required="true"
|
||||
label="Type">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
</section>
|
||||
</tpl-body>
|
||||
<tpl-buttons>
|
||||
<button response="accept" translate>Transfer client</button>
|
||||
|
|
|
@ -129,15 +129,15 @@ class Controller extends Section {
|
|||
transferInvoice() {
|
||||
const params = {
|
||||
id: this.invoiceOut.id,
|
||||
ref: this.invoiceOut.ref,
|
||||
newClientFk: this.invoiceOut.client.id,
|
||||
cplusRectificationId: this.cplusRectificationType,
|
||||
siiTypeInvoiceOutId: this.siiTypeInvoiceOut,
|
||||
invoiceCorrectionTypeId: this.invoiceCorrectionType
|
||||
refFk: this.invoiceOut.ref,
|
||||
newClientFk: this.clientId,
|
||||
cplusRectificationTypeFk: this.cplusRectificationType,
|
||||
siiTypeInvoiceOutFk: this.siiTypeInvoiceOut,
|
||||
invoiceCorrectionTypeFk: this.invoiceCorrectionType
|
||||
};
|
||||
this.$http.post(`InvoiceOuts/transferInvoice`, params).then(res => {
|
||||
const invoiceId = res.data;
|
||||
this.vnApp.showSuccess(this.$t('Invoice trasfered!'));
|
||||
this.vnApp.showSuccess(this.$t('Transferred invoice'));
|
||||
this.$state.go('invoiceOut.card.summary', {id: invoiceId});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,4 +22,5 @@ The email can't be empty: El correo no puede estar vacío
|
|||
The following refund tickets have been created: "Se han creado los siguientes tickets de abono: {{ticketIds}}"
|
||||
Refund...: Abono...
|
||||
Transfer invoice to...: Transferir factura a...
|
||||
Cplus Type: Cplus Tipo
|
||||
Rectificative type: Tipo rectificativa
|
||||
Transferred invoice: Factura transferida
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
auto-load="true"
|
||||
url="InvoiceOutSerials"
|
||||
data="invoiceOutSerials"
|
||||
where="{code: {neq: 'R'}}"
|
||||
order="code">
|
||||
</vn-crud-model>
|
||||
<vn-crud-model
|
||||
|
|
|
@ -20,18 +20,10 @@
|
|||
"type": "number",
|
||||
"required": true
|
||||
},
|
||||
"isPreviousPreparedByPacking": {
|
||||
"type": "boolean",
|
||||
"required": true
|
||||
},
|
||||
"code": {
|
||||
"type": "string",
|
||||
"required": false
|
||||
},
|
||||
"isPreviousPrepared": {
|
||||
"type": "boolean",
|
||||
"required": true
|
||||
},
|
||||
"isPackagingArea": {
|
||||
"type": "boolean",
|
||||
"required": true
|
||||
|
|
|
@ -19,7 +19,7 @@ module.exports = Self => {
|
|||
}
|
||||
],
|
||||
returns: {
|
||||
type: ['number'],
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
|
@ -54,7 +54,7 @@ module.exports = Self => {
|
|||
|
||||
if (tx) await tx.commit();
|
||||
|
||||
return refundsTicket[0];
|
||||
return refundsTicket;
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
|
|
|
@ -23,9 +23,9 @@ describe('Sale refund()', () => {
|
|||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const refundedTicket = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options);
|
||||
const refundedTickets = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options);
|
||||
|
||||
expect(refundedTicket).toBeDefined();
|
||||
expect(refundedTickets).toBeDefined();
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
@ -42,11 +42,11 @@ describe('Sale refund()', () => {
|
|||
const options = {transaction: tx};
|
||||
const ticketsBefore = await models.Ticket.find({}, options);
|
||||
|
||||
const ticket = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options);
|
||||
const tickets = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options);
|
||||
|
||||
const refundedTicket = await models.Ticket.findOne({
|
||||
where: {
|
||||
id: ticket.id
|
||||
id: tickets[0].id
|
||||
},
|
||||
include: [
|
||||
{
|
||||
|
|
|
@ -10,20 +10,20 @@ module.exports = function(Self) {
|
|||
description: 'The tickets id',
|
||||
type: ['number'],
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'isRectificative',
|
||||
description: 'If it is rectificative',
|
||||
type: 'boolean'
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
arg: 'data',
|
||||
type: 'boolean',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/canBeInvoiced`,
|
||||
verb: 'get'
|
||||
}
|
||||
});
|
||||
|
||||
Self.canBeInvoiced = async(ctx, ticketsIds, options) => {
|
||||
Self.canBeInvoiced = async(ctx, ticketsIds, isRectificative, options) => {
|
||||
const myOptions = {};
|
||||
const $t = ctx.req.__; // $translate
|
||||
|
||||
|
@ -34,26 +34,14 @@ module.exports = function(Self) {
|
|||
where: {
|
||||
id: {inq: ticketsIds}
|
||||
},
|
||||
fields: ['id', 'refFk', 'shipped', 'totalWithVat', 'companyFk']
|
||||
fields: ['id', 'refFk', 'shipped', 'totalWithVat']
|
||||
}, myOptions);
|
||||
const [firstTicket] = tickets;
|
||||
const companyFk = firstTicket.companyFk;
|
||||
|
||||
const query =
|
||||
`SELECT COUNT(*) isSpanishCompany
|
||||
FROM supplier s
|
||||
JOIN country c ON c.id = s.countryFk
|
||||
AND c.code = 'ES'
|
||||
WHERE s.id = ?`;
|
||||
const [supplierCompany] = await Self.rawSql(query, [companyFk], options);
|
||||
|
||||
const isSpanishCompany = supplierCompany?.isSpanishCompany;
|
||||
|
||||
const [result] = await Self.rawSql('SELECT hasAnyNegativeBase() AS base', null, options);
|
||||
const hasAnyNegativeBase = result?.base && isSpanishCompany;
|
||||
if (hasAnyNegativeBase)
|
||||
throw new UserError($t('Negative basis of tickets', {ticketsIds: ticketsIds}));
|
||||
|
||||
const taxBaseFunction = isRectificative ? 'hasAnyPositiveBase' : 'hasAnyNegativeBase';
|
||||
const [hasAnyIncorrectBase] =
|
||||
await Self.rawSql(`SELECT ${taxBaseFunction}() AS hasBasesProblem`, null, options);
|
||||
if (hasAnyIncorrectBase?.hasBasesProblem)
|
||||
throw new UserError($t(taxBaseFunction, {ticketsIds: ticketsIds}));
|
||||
const today = Date.vnNew();
|
||||
|
||||
tickets.some(ticket => {
|
||||
|
@ -70,7 +58,5 @@ module.exports = function(Self) {
|
|||
if (ticketsIds.length == 1 && priceZero)
|
||||
throw new UserError(`A ticket with an amount of zero can't be invoiced`);
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -10,7 +10,13 @@ module.exports = function(Self) {
|
|||
description: 'The tickets id',
|
||||
type: ['number'],
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'invoiceCorrection',
|
||||
description: 'The invoice correction',
|
||||
type: 'object',
|
||||
}
|
||||
|
||||
],
|
||||
returns: {
|
||||
type: ['object'],
|
||||
|
@ -22,7 +28,7 @@ module.exports = function(Self) {
|
|||
}
|
||||
});
|
||||
|
||||
Self.invoiceTickets = async(ctx, ticketsIds, options) => {
|
||||
Self.invoiceTickets = async(ctx, ticketsIds, invoiceCorrection, options) => {
|
||||
const models = Self.app.models;
|
||||
const date = Date.vnNew();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
|
@ -41,10 +47,10 @@ module.exports = function(Self) {
|
|||
let invoicesIds = [];
|
||||
try {
|
||||
const tickets = await models.Ticket.find({
|
||||
fields: ['id', 'clientFk', 'companyFk', 'addressFk'],
|
||||
where: {
|
||||
id: {inq: ticketsIds}
|
||||
},
|
||||
fields: ['id', 'clientFk', 'companyFk']
|
||||
}
|
||||
}, myOptions);
|
||||
|
||||
const [firstTicket] = tickets;
|
||||
|
@ -55,22 +61,20 @@ module.exports = function(Self) {
|
|||
if (!isSameClient)
|
||||
throw new UserError(`You can't invoice tickets from multiple clients`);
|
||||
|
||||
const client = await models.Client.findById(clientId, {
|
||||
fields: ['id', 'hasToInvoiceByAddress']
|
||||
const {hasToInvoiceByAddress} = await models.Client.findById(clientId, {
|
||||
fields: ['hasToInvoiceByAddress']
|
||||
}, myOptions);
|
||||
|
||||
if (client.hasToInvoiceByAddress) {
|
||||
const query = `
|
||||
SELECT DISTINCT addressFk
|
||||
FROM ticket t
|
||||
WHERE id IN (?)`;
|
||||
const result = await Self.rawSql(query, [ticketsIds], myOptions);
|
||||
let ticketsByAddress = hasToInvoiceByAddress
|
||||
? Object.values(tickets.reduce((group, {id, addressFk}) => {
|
||||
group[addressFk] = group[addressFk] ?? [];
|
||||
group[addressFk].push(id);
|
||||
return group;
|
||||
}, {}))
|
||||
: [ticketsIds];
|
||||
|
||||
const addressIds = result.map(address => address.addressFk);
|
||||
for (const address of addressIds)
|
||||
await createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, myOptions);
|
||||
} else
|
||||
await createInvoice(ctx, companyId, ticketsIds, null, invoicesIds, myOptions);
|
||||
for (const ticketIds of ticketsByAddress)
|
||||
invoicesIds.push(await createInvoice(ctx, companyId, ticketIds, invoiceCorrection, myOptions));
|
||||
|
||||
if (tx) await tx.commit();
|
||||
} catch (e) {
|
||||
|
@ -85,9 +89,8 @@ module.exports = function(Self) {
|
|||
return invoicesIds;
|
||||
};
|
||||
|
||||
async function createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, myOptions) {
|
||||
async function createInvoice(ctx, companyId, ticketsIds, invoiceCorrection, myOptions) {
|
||||
const models = Self.app.models;
|
||||
|
||||
await models.Ticket.rawSql(`
|
||||
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice
|
||||
(PRIMARY KEY (id))
|
||||
|
@ -95,11 +98,8 @@ module.exports = function(Self) {
|
|||
SELECT id
|
||||
FROM vn.ticket
|
||||
WHERE id IN (?)
|
||||
${address ? `AND addressFk = ${address}` : ''}
|
||||
`, [ticketsIds], myOptions);
|
||||
|
||||
const invoiceId = await models.Ticket.makeInvoice(ctx, 'R', companyId, Date.vnNew(), myOptions);
|
||||
invoicesIds.push(invoiceId);
|
||||
return models.Ticket.makeInvoice(ctx, 'R', companyId, Date.vnNew(), invoiceCorrection, myOptions);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -22,6 +22,11 @@ module.exports = function(Self) {
|
|||
description: 'The invoice date',
|
||||
type: 'date',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'invoiceCorrection',
|
||||
description: 'The invoice correction',
|
||||
type: 'object',
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
|
@ -34,7 +39,7 @@ module.exports = function(Self) {
|
|||
}
|
||||
});
|
||||
|
||||
Self.makeInvoice = async(ctx, invoiceType, companyFk, invoiceDate, options) => {
|
||||
Self.makeInvoice = async(ctx, invoiceType, companyFk, invoiceDate, invoiceCorrection, options) => {
|
||||
const models = Self.app.models;
|
||||
invoiceDate.setHours(0, 0, 0, 0);
|
||||
|
||||
|
@ -62,20 +67,24 @@ module.exports = function(Self) {
|
|||
fields: ['id', 'clientFk', 'addressFk']
|
||||
}, myOptions);
|
||||
|
||||
await models.Ticket.canBeInvoiced(ctx, ticketsIds, myOptions);
|
||||
await models.Ticket.canBeInvoiced(ctx, ticketsIds, !!invoiceCorrection, myOptions);
|
||||
|
||||
const [firstTicket] = tickets;
|
||||
const clientId = firstTicket.clientFk;
|
||||
const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, companyFk, myOptions);
|
||||
const clientCanBeInvoiced =
|
||||
await models.Client.canBeInvoiced(clientId, companyFk, myOptions);
|
||||
|
||||
if (!clientCanBeInvoiced)
|
||||
throw new UserError(`This client can't be invoiced`);
|
||||
|
||||
const query = `SELECT vn.invoiceSerial(?, ?, ?) AS serial`;
|
||||
const [{serial}] = await Self.rawSql(query, [
|
||||
clientId,
|
||||
companyFk,
|
||||
invoiceType,
|
||||
], myOptions);
|
||||
const [{serial}] = invoiceCorrection ? [{serial: 'R'}] : await Self.rawSql(
|
||||
`SELECT vn.invoiceSerial(?, ?, ?) AS serial`,
|
||||
[
|
||||
clientId,
|
||||
companyFk,
|
||||
invoiceType
|
||||
],
|
||||
myOptions);
|
||||
|
||||
const invoiceOutSerial = await models.InvoiceOutSerial.findById(serial);
|
||||
if (invoiceOutSerial?.taxAreaFk == 'WORLD') {
|
||||
|
@ -87,11 +96,17 @@ module.exports = function(Self) {
|
|||
await Self.rawSql('CALL invoiceOut_new(?, ?, null, @invoiceId)', [serial, invoiceDate], myOptions);
|
||||
|
||||
const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], myOptions);
|
||||
if (!resultInvoice)
|
||||
if (!resultInvoice?.id)
|
||||
throw new UserError('No tickets to invoice', 'notInvoiced');
|
||||
|
||||
if (serial != 'R' && resultInvoice.id)
|
||||
await Self.rawSql('CALL invoiceOutBooking(?)', [resultInvoice.id], myOptions);
|
||||
if (invoiceCorrection) {
|
||||
await models.InvoiceCorrection.create(
|
||||
Object.assign(invoiceCorrection, {correctingFk: resultInvoice.id}),
|
||||
myOptions
|
||||
);
|
||||
}
|
||||
|
||||
await Self.rawSql('CALL invoiceOutBooking(?)', [resultInvoice.id], myOptions);
|
||||
|
||||
if (tx) await tx.commit();
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ module.exports = Self => {
|
|||
}
|
||||
],
|
||||
returns: {
|
||||
type: ['number'],
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
|
|
|
@ -27,10 +27,7 @@ describe('ticket canBeInvoiced()', () => {
|
|||
WHERE id IN (?)
|
||||
`, [ticketId], options);
|
||||
|
||||
const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options);
|
||||
|
||||
expect(canBeInvoiced).toEqual(false);
|
||||
|
||||
await models.Ticket.canBeInvoiced(ctx, [ticketId], false, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
|
@ -59,10 +56,7 @@ describe('ticket canBeInvoiced()', () => {
|
|||
WHERE id IN (?)
|
||||
`, [ticketId], options);
|
||||
|
||||
const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options);
|
||||
|
||||
expect(canBeInvoiced).toEqual(false);
|
||||
|
||||
await models.Ticket.canBeInvoiced(ctx, [ticketId], false, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
|
@ -95,10 +89,7 @@ describe('ticket canBeInvoiced()', () => {
|
|||
WHERE id IN (?)
|
||||
`, [ticketId], options);
|
||||
|
||||
const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options);
|
||||
|
||||
expect(canBeInvoiced).toEqual(false);
|
||||
|
||||
await models.Ticket.canBeInvoiced(ctx, [ticketId], false, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
|
@ -123,14 +114,36 @@ describe('ticket canBeInvoiced()', () => {
|
|||
WHERE id IN (?)
|
||||
`, [ticketId], options);
|
||||
|
||||
const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options);
|
||||
|
||||
expect(canBeInvoiced).toEqual(true);
|
||||
|
||||
await models.Ticket.canBeInvoiced(ctx, [ticketId], false, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return falsy for a ticket has positiveBase', async() => {
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await models.Ticket.rawSql(`
|
||||
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice
|
||||
(PRIMARY KEY (id))
|
||||
ENGINE = MEMORY
|
||||
SELECT id
|
||||
FROM vn.ticket
|
||||
WHERE id IN (?)
|
||||
`, [ticketId], options);
|
||||
|
||||
await models.Ticket.canBeInvoiced(ctx, [ticketId], true, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`hasAnyPositiveBase`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,7 +31,7 @@ describe('ticket invoiceTickets()', () => {
|
|||
const options = {transaction: tx};
|
||||
|
||||
const ticketsIds = [11, 16];
|
||||
await models.Ticket.invoiceTickets(ctx, ticketsIds, options);
|
||||
await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
@ -57,7 +57,7 @@ describe('ticket invoiceTickets()', () => {
|
|||
await client.updateAttribute('isTaxDataChecked', false, options);
|
||||
|
||||
const ticketsIds = [11];
|
||||
await models.Ticket.invoiceTickets(ctx, ticketsIds, options);
|
||||
await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
@ -80,8 +80,8 @@ describe('ticket invoiceTickets()', () => {
|
|||
const options = {transaction: tx};
|
||||
|
||||
const ticketsIds = [11];
|
||||
await models.Ticket.invoiceTickets(ctx, ticketsIds, options);
|
||||
await models.Ticket.invoiceTickets(ctx, ticketsIds, options);
|
||||
await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options);
|
||||
await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
@ -102,7 +102,7 @@ describe('ticket invoiceTickets()', () => {
|
|||
const options = {transaction: tx};
|
||||
|
||||
const ticketsIds = [11];
|
||||
const invoicesIds = await models.Ticket.invoiceTickets(ctx, ticketsIds, options);
|
||||
const invoicesIds = await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options);
|
||||
|
||||
expect(invoicesIds.length).toBeGreaterThan(0);
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ describe('ticket makeInvoice()', () => {
|
|||
WHERE id IN (?)
|
||||
`, [ticketsIds], options);
|
||||
|
||||
const invoiceId = await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, options);
|
||||
const invoiceId = await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, null, options);
|
||||
|
||||
expect(invoiceId).toBeDefined();
|
||||
|
||||
|
@ -70,7 +70,7 @@ describe('ticket makeInvoice()', () => {
|
|||
WHERE id IN (?)
|
||||
`, [ticketsId], options);
|
||||
|
||||
await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, options);
|
||||
await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, null, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
|
|
|
@ -20,9 +20,6 @@
|
|||
},
|
||||
"stateFk": {
|
||||
"type": "number"
|
||||
},
|
||||
"userFk": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
|
|
|
@ -292,7 +292,7 @@ class Controller extends Section {
|
|||
const query = 'Tickets/refund';
|
||||
return this.$http.post(query, params)
|
||||
.then(res => {
|
||||
const refundTicket = res.data;
|
||||
const [refundTicket] = res.data;
|
||||
this.vnApp.showSuccess(this.$t('The following refund ticket have been created', {
|
||||
ticketId: refundTicket.id
|
||||
}));
|
||||
|
|
|
@ -262,11 +262,12 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
|
|||
const params = {
|
||||
ticketsIds: [16]
|
||||
};
|
||||
$httpBackend.expectPOST('Tickets/refund', params).respond({id: 99});
|
||||
const response = {id: 99};
|
||||
$httpBackend.expectPOST('Tickets/refund', params).respond([response]);
|
||||
controller.refund();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: 99});
|
||||
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', response);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -526,7 +526,7 @@ class Controller extends Section {
|
|||
const params = {salesIds: salesIds, withWarehouse: withWarehouse};
|
||||
const query = 'Sales/refund';
|
||||
this.$http.post(query, params).then(res => {
|
||||
const refundTicket = res.data;
|
||||
const [refundTicket] = res.data;
|
||||
this.vnApp.showSuccess(this.$t('The following refund ticket have been created', {
|
||||
ticketId: refundTicket.id
|
||||
}));
|
||||
|
|
|
@ -729,7 +729,7 @@ describe('Ticket', () => {
|
|||
salesIds: [1, 4],
|
||||
};
|
||||
const refundTicket = {id: 99};
|
||||
$httpBackend.expect('POST', 'Sales/refund', params).respond(200, refundTicket);
|
||||
$httpBackend.expect('POST', 'Sales/refund', params).respond(200, [refundTicket]);
|
||||
controller.createRefund();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
<vn-td>{{::tracking.state.name}}</vn-td>
|
||||
<vn-td expand>
|
||||
<span
|
||||
ng-class="{'link': tracking.worker.id}"
|
||||
ng-click="workerDescriptor.show($event, tracking.worker.user.id)">
|
||||
{{::tracking.worker.user.name || 'System' | translate}}
|
||||
ng-class="{'link': tracking.user.id}"
|
||||
ng-click="workerDescriptor.show($event, tracking.user.id)">
|
||||
{{::tracking.user.name || 'System' | translate}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td shrink-datetime>{{::tracking.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
|
||||
|
|
|
@ -39,6 +39,7 @@ module.exports = Self => {
|
|||
started.setFullYear(year);
|
||||
started.setMonth(0);
|
||||
started.setDate(1);
|
||||
started.setHours(0, 0, 0, 0);
|
||||
|
||||
const ended = Date.vnNew();
|
||||
ended.setFullYear(year);
|
||||
|
|
|
@ -43,16 +43,9 @@ module.exports = Self => {
|
|||
const isTeamBoss = await models.ACL.checkAccessAcl(ctx, 'Worker', 'isTeamBoss', 'WRITE');
|
||||
const isHimself = userId == workerId;
|
||||
|
||||
if (!isSubordinate || (isSubordinate && isHimself && !isTeamBoss))
|
||||
if (!isSubordinate || (isHimself && !isTeamBoss))
|
||||
throw new UserError(`You don't have enough privileges`);
|
||||
|
||||
query = `CALL vn.workerTimeControl_clockIn(?,?,?)`;
|
||||
const [response] = await Self.rawSql(query, [workerId, args.timed, args.direction], myOptions);
|
||||
if (response[0] && response[0].error)
|
||||
throw new UserError(response[0].error);
|
||||
|
||||
await models.WorkerTimeControl.resendWeeklyHourEmail(ctx, workerId, args.timed, myOptions);
|
||||
|
||||
return response;
|
||||
return Self.clockIn(workerId, args.timed, args.direction, myOptions);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('clockIn', {
|
||||
description: 'Check if the employee can clock in',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'workerFk',
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
arg: 'timed',
|
||||
type: 'date'
|
||||
},
|
||||
{
|
||||
arg: 'direction',
|
||||
type: 'string'
|
||||
},
|
||||
|
||||
],
|
||||
http: {
|
||||
path: `/clockIn`,
|
||||
verb: 'POST'
|
||||
},
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
}
|
||||
});
|
||||
|
||||
Self.clockIn = async(workerFk, timed, direction, options) => {
|
||||
const myOptions = {};
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const query = 'CALL vn.workerTimeControl_clockIn(?, ?, ?)';
|
||||
const [[response]] = await Self.rawSql(query, [workerFk, timed, direction], myOptions);
|
||||
if (response && response.error)
|
||||
throw new UserError(response.error);
|
||||
|
||||
return response;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('getClockIn', {
|
||||
description: 'Shows the clockings for each day, in columns per day',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'workerFk',
|
||||
type: 'int',
|
||||
required: true,
|
||||
},
|
||||
|
||||
],
|
||||
http: {
|
||||
path: `/getClockIn`,
|
||||
verb: 'GET'
|
||||
},
|
||||
returns: {
|
||||
type: ['Object'],
|
||||
root: true
|
||||
},
|
||||
});
|
||||
|
||||
Self.getClockIn = async(workerFk, options) => {
|
||||
const myOptions = {};
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const query = `CALL vn.workerTimeControl_getClockIn(?, ?)`;
|
||||
const [result] = await Self.rawSql(query, [workerFk, Date.vnNew()], myOptions);
|
||||
return result;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('login', {
|
||||
description: 'Consult the user\'s information and the buttons that must be activated after logging in',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'pin',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/login`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.login = async(ctx, pin, options) => {
|
||||
const myOptions = {};
|
||||
const $t = ctx.req.__;
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const query = `CALL vn.workerTimeControl_login(?)`;
|
||||
const [[user]] = await Self.rawSql(query, [pin], myOptions);
|
||||
if (!user) throw new UserError($t('Incorrect pin'));
|
||||
return user;
|
||||
};
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('resendWeeklyHourEmail', {
|
||||
description: 'Adds a new hour registry',
|
||||
description: 'Send the records for the week of the date provided',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'id',
|
||||
|
|
|
@ -0,0 +1,581 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('workerTimeControl clockIn()', () => {
|
||||
const workerId = 9;
|
||||
const salesBossId = 19;
|
||||
const hankPymId = 1107;
|
||||
const jessicaJonesId = 1110;
|
||||
const HHRRId = 37;
|
||||
const teamBossId = 13;
|
||||
const monday = 1;
|
||||
const tuesday = 2;
|
||||
const thursday = 4;
|
||||
const friday = 5;
|
||||
const sunday = 7;
|
||||
const inTime = '2001-01-01T00:00:00.000Z';
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 50},
|
||||
};
|
||||
const ctx = {req: activeCtx};
|
||||
|
||||
beforeAll(() => {
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly clock in', async() => {
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
await models.WorkerTimeControl.clockIn(workerId, inTime, 'in', options);
|
||||
const isClockIn = await models.WorkerTimeControl.findOne({
|
||||
where: {
|
||||
userFk: workerId
|
||||
}
|
||||
}, options);
|
||||
|
||||
expect(isClockIn).toBeDefined();
|
||||
expect(isClockIn.direction).toBe('in');
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
describe('as Role errors', () => {
|
||||
it('should add if the current user is team boss and the target user is himself', async() => {
|
||||
activeCtx.accessToken.userId = teamBossId;
|
||||
const workerId = teamBossId;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const todayAtOne = Date.vnNew();
|
||||
todayAtOne.setHours(1, 0, 0, 0);
|
||||
|
||||
ctx.args = {timed: todayAtOne, direction: 'in'};
|
||||
const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
expect(createdTimeEntry.id).toBeDefined();
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should delete the created time entry for the team boss as himself', async() => {
|
||||
activeCtx.accessToken.userId = teamBossId;
|
||||
const workerId = teamBossId;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const todayAtOne = Date.vnNew();
|
||||
todayAtOne.setHours(1, 0, 0, 0);
|
||||
|
||||
ctx.args = {timed: todayAtOne, direction: 'in'};
|
||||
const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
expect(createdTimeEntry.id).toBeDefined();
|
||||
|
||||
await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options);
|
||||
|
||||
const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options);
|
||||
|
||||
expect(deletedTimeEntry).toBeNull();
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should edit the created time entry for the team boss as HHRR', async() => {
|
||||
activeCtx.accessToken.userId = HHRRId;
|
||||
const workerId = teamBossId;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const todayAtOne = Date.vnNew();
|
||||
todayAtOne.setHours(1, 0, 0, 0);
|
||||
|
||||
ctx.args = {timed: todayAtOne, direction: 'in'};
|
||||
const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
expect(createdTimeEntry.id).toBeDefined();
|
||||
|
||||
ctx.args = {direction: 'out'};
|
||||
const updatedTimeEntry = await models.WorkerTimeControl.updateTimeEntry(
|
||||
ctx, createdTimeEntry.id, options
|
||||
);
|
||||
|
||||
expect(updatedTimeEntry.direction).toEqual('out');
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('as saleBoss editor', () => {
|
||||
let workerId;
|
||||
beforeEach(() => {
|
||||
activeCtx.accessToken.userId = salesBossId;
|
||||
workerId = hankPymId;
|
||||
});
|
||||
|
||||
it('should fail to add a time entry if the target user has an absence that day', async() => {
|
||||
const date = Date.vnNew();
|
||||
date.setHours(8, 0, 0);
|
||||
date.setDate(date.getDate() - 16);
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
try {
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`No está permitido trabajar`);
|
||||
});
|
||||
|
||||
it('should fail to add a time entry for a worker without an existing contract', async() => {
|
||||
const date = Date.vnNew();
|
||||
date.setFullYear(date.getFullYear() - 2);
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
try {
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`No hay un contrato en vigor`);
|
||||
});
|
||||
|
||||
it('should fail to add a time entry for a worker without an existing contract and exceeding time', async() => {
|
||||
let date = Date.vnNew();
|
||||
date.setDate(date.getDate() - 2);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
date.setHours(0, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date.setHours(20, 0, 1);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Superado el tiempo máximo entre entrada y salida`);
|
||||
});
|
||||
|
||||
describe('direction errors', () => {
|
||||
let date = Date.vnNew();
|
||||
date.setDate(date.getDate() - 1);
|
||||
let error;
|
||||
it('should throw an error when trying "in" direction twice', async() => {
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date.setHours(10, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Dirección incorrecta`);
|
||||
});
|
||||
|
||||
it('should throw an error when trying "in" direction after insert "in" and "middle"', async() => {
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
date.setHours(9, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'middle'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date.setHours(10, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Dirección incorrecta`);
|
||||
});
|
||||
|
||||
it('Should throw an error when trying "out" before closing a "middle" couple', async() => {
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(9, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'middle'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date.setHours(10, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Dirección incorrecta`);
|
||||
});
|
||||
|
||||
it('should throw an error when trying "middle" after "out"', async() => {
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(9, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date.setHours(10, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'middle'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Dirección incorrecta`);
|
||||
});
|
||||
|
||||
it('should throw an error when trying "out" direction twice', async() => {
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(9, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date.setHours(10, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Dirección incorrecta`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('12h rest', () => {
|
||||
activeCtx.accessToken.userId = salesBossId;
|
||||
const workerId = hankPymId;
|
||||
it('should throw an error when the 12h rest is not fulfilled yet', async() => {
|
||||
let date = Date.vnNew();
|
||||
date.setDate(date.getDate() - 2);
|
||||
date = weekDay(date, monday);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(16, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date = weekDay(date, tuesday);
|
||||
date.setHours(4, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Descanso diario`);
|
||||
});
|
||||
|
||||
it('should not fail as the 12h rest is fulfilled', async() => {
|
||||
let date = Date.vnNew();
|
||||
date.setDate(date.getDate() - 2);
|
||||
date = weekDay(date, monday);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(16, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date = weekDay(date, tuesday);
|
||||
date.setHours(4, 1, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).not.toBeDefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('for 3500kg drivers with enforced 9h rest', () => {
|
||||
activeCtx.accessToken.userId = salesBossId;
|
||||
const workerId = jessicaJonesId;
|
||||
it('should throw an error when the 9h enforced rest is not fulfilled', async() => {
|
||||
let date = Date.vnNew();
|
||||
date.setDate(date.getDate() - 2);
|
||||
date = weekDay(date, monday);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(16, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date = weekDay(date, tuesday);
|
||||
date.setHours(1, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Descanso diario`);
|
||||
});
|
||||
|
||||
it('should not fail when the 9h enforced rest is fulfilled', async() => {
|
||||
let date = Date.vnNew();
|
||||
date.setDate(date.getDate() - 2);
|
||||
date = weekDay(date, monday);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(16, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date = weekDay(date, tuesday);
|
||||
date.setHours(1, 1, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).not.toBeDefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('for 72h weekly rest', () => {
|
||||
|
||||
it('should throw an error when work 11 consecutive days', async() => {
|
||||
let date = Date.vnNew();
|
||||
date.setMonth(date.getMonth() - 1);
|
||||
date.setDate(1);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
await populateWeek(date, monday, sunday, ctx, workerId, options);
|
||||
date = nextWeek(date);
|
||||
await populateWeek(date, monday, thursday, ctx, workerId, options);
|
||||
try {
|
||||
date = weekDay(date, friday);
|
||||
date.setHours(10, 0, 1);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Descanso semanal`);
|
||||
});
|
||||
|
||||
it('should throw an error when the 72h weekly rest is not fulfilled', async() => {
|
||||
|
||||
let date = Date.vnNew();
|
||||
date.setMonth(date.getMonth() - 1);
|
||||
date.setDate(1);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
await populateWeek(date, monday, sunday, ctx, workerId, options);
|
||||
date = nextWeek(date);
|
||||
await populateWeek(date, monday, thursday, ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date = weekDay(date, sunday);
|
||||
date.setHours(17, 59, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Descanso semanal`);
|
||||
});
|
||||
|
||||
it('should throw an error when the 72h weekly rest is fulfilled', async() => {
|
||||
|
||||
let date = Date.vnNew();
|
||||
date.setMonth(date.getMonth() - 1);
|
||||
date.setDate(1);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
await populateWeek(date, monday, sunday, ctx, workerId, options);
|
||||
date = nextWeek(date);
|
||||
await populateWeek(date, monday, thursday, ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date = weekDay(date, sunday);
|
||||
date.setHours(18, 00, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).not.toBeDefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function weekDay(date, dayToSet) {
|
||||
const currentDay = date.getDay();
|
||||
const distance = dayToSet - currentDay;
|
||||
|
||||
date.setDate(date.getDate() + distance);
|
||||
return date;
|
||||
}
|
||||
|
||||
function nextWeek(date) {
|
||||
const sunday = 7;
|
||||
const currentDay = date.getDay();
|
||||
let newDate = date;
|
||||
if (currentDay != 0)
|
||||
newDate = weekDay(date, sunday);
|
||||
|
||||
newDate.setDate(newDate.getDate() + 1);
|
||||
return newDate;
|
||||
}
|
||||
|
||||
async function populateWeek(date, dayStart, dayEnd, ctx, workerId, options) {
|
||||
const dateStart = new Date(weekDay(date, dayStart));
|
||||
const dateEnd = new Date(dateStart);
|
||||
dateEnd.setDate(dateStart.getDate() + dayEnd);
|
||||
|
||||
for (let i = dayStart; i <= dayEnd; i++) {
|
||||
dateStart.setHours(10, 0, 0);
|
||||
ctx.args = {timed: dateStart, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
dateStart.setHours(18, 0, 0);
|
||||
ctx.args = {timed: dateStart, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
dateStart.setDate(dateStart.getDate() + 1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('workerTimeControl getClockIn()', () => {
|
||||
it('should correctly get the timetable of a worker', async() => {
|
||||
const response = await models.WorkerTimeControl.getClockIn(1106, {});
|
||||
|
||||
expect(response.length).toEqual(4);
|
||||
const [inHrs, middleOutHrs, middleInHrs, outHrs] = response;
|
||||
|
||||
expect(inHrs['0daysAgo']).toEqual('07:00');
|
||||
expect(middleOutHrs['0daysAgo']).toEqual('10:00');
|
||||
expect(middleInHrs['0daysAgo']).toEqual('10:20');
|
||||
expect(outHrs['0daysAgo']).toEqual('14:50');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
describe('workerTimeControl login()', () => {
|
||||
let ctx;
|
||||
beforeAll(async() => {
|
||||
ctx = {
|
||||
accessToken: {userId: 9},
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'},
|
||||
__: key => key
|
||||
}
|
||||
};
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: ctx
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly login', async() => {
|
||||
const response = await models.WorkerTimeControl.login(ctx, 9);
|
||||
|
||||
expect(response.name).toBe('developer');
|
||||
});
|
||||
|
||||
it('should throw UserError if pin is not provided', async() => {
|
||||
try {
|
||||
await models.WorkerTimeControl.login(ctx);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(UserError);
|
||||
expect(error.message).toBe('Incorrect pin');
|
||||
}
|
||||
});
|
||||
});
|
|
@ -3,18 +3,10 @@ const models = require('vn-loopback/server/server').models;
|
|||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('workerTimeControl add/delete timeEntry()', () => {
|
||||
const HHRRId = 37;
|
||||
const teamBossId = 13;
|
||||
const employeeId = 1;
|
||||
const salesPersonId = 1106;
|
||||
const salesBossId = 19;
|
||||
const hankPymId = 1107;
|
||||
const jessicaJonesId = 1110;
|
||||
const monday = 1;
|
||||
const tuesday = 2;
|
||||
const thursday = 4;
|
||||
const friday = 5;
|
||||
const sunday = 7;
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 50},
|
||||
};
|
||||
|
@ -61,560 +53,11 @@ describe('workerTimeControl add/delete timeEntry()', () => {
|
|||
expect(error.statusCode).toBe(400);
|
||||
expect(error.message).toBe(`You don't have enough privileges`);
|
||||
});
|
||||
|
||||
it('should add if the current user is team boss and the target user is himself', async() => {
|
||||
activeCtx.accessToken.userId = teamBossId;
|
||||
const workerId = teamBossId;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const todayAtOne = Date.vnNew();
|
||||
todayAtOne.setHours(1, 0, 0, 0);
|
||||
|
||||
ctx.args = {timed: todayAtOne, direction: 'in'};
|
||||
const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
expect(createdTimeEntry.id).toBeDefined();
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should try but fail to delete his own time entry', async() => {
|
||||
activeCtx.accessToken.userId = salesBossId;
|
||||
const workerId = salesBossId;
|
||||
|
||||
let error;
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const todayAtOne = Date.vnNew();
|
||||
todayAtOne.setHours(1, 0, 0, 0);
|
||||
|
||||
ctx.args = {timed: todayAtOne, direction: 'in'};
|
||||
const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
activeCtx.accessToken.userId = salesPersonId;
|
||||
await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
expect(error.statusCode).toBe(400);
|
||||
expect(error.message).toBe(`You don't have enough privileges`);
|
||||
});
|
||||
|
||||
it('should delete the created time entry for the team boss as himself', async() => {
|
||||
activeCtx.accessToken.userId = teamBossId;
|
||||
const workerId = teamBossId;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const todayAtOne = Date.vnNew();
|
||||
todayAtOne.setHours(1, 0, 0, 0);
|
||||
|
||||
ctx.args = {timed: todayAtOne, direction: 'in'};
|
||||
const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
expect(createdTimeEntry.id).toBeDefined();
|
||||
|
||||
await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options);
|
||||
|
||||
const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options);
|
||||
|
||||
expect(deletedTimeEntry).toBeNull();
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should delete the created time entry for the team boss as HHRR', async() => {
|
||||
activeCtx.accessToken.userId = HHRRId;
|
||||
const workerId = teamBossId;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const todayAtOne = Date.vnNew();
|
||||
todayAtOne.setHours(1, 0, 0, 0);
|
||||
|
||||
ctx.args = {timed: todayAtOne, direction: 'in'};
|
||||
const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
expect(createdTimeEntry.id).toBeDefined();
|
||||
|
||||
await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options);
|
||||
|
||||
const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options);
|
||||
|
||||
expect(deletedTimeEntry).toBeNull();
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should edit the created time entry for the team boss as HHRR', async() => {
|
||||
activeCtx.accessToken.userId = HHRRId;
|
||||
const workerId = teamBossId;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const todayAtOne = Date.vnNew();
|
||||
todayAtOne.setHours(1, 0, 0, 0);
|
||||
|
||||
ctx.args = {timed: todayAtOne, direction: 'in'};
|
||||
const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
expect(createdTimeEntry.id).toBeDefined();
|
||||
|
||||
ctx.args = {direction: 'out'};
|
||||
const updatedTimeEntry = await models.WorkerTimeControl.updateTimeEntry(ctx, createdTimeEntry.id, options);
|
||||
|
||||
expect(updatedTimeEntry.direction).toEqual('out');
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('WorkerTimeControl_clockIn calls', () => {
|
||||
let workerId;
|
||||
beforeEach(() => {
|
||||
activeCtx.accessToken.userId = salesBossId;
|
||||
workerId = hankPymId;
|
||||
});
|
||||
it('should fail to add a time entry if the target user has an absence that day', async() => {
|
||||
const date = Date.vnNew();
|
||||
date.setHours(8, 0, 0);
|
||||
date.setDate(date.getDate() - 16);
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
try {
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
beforeEach(() => activeCtx.accessToken.userId = salesBossId);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`No está permitido trabajar`);
|
||||
});
|
||||
|
||||
it('should fail to add a time entry for a worker without an existing contract', async() => {
|
||||
const date = Date.vnNew();
|
||||
date.setFullYear(date.getFullYear() - 2);
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
try {
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`No hay un contrato en vigor`);
|
||||
});
|
||||
|
||||
it('should fail to add a time entry for a worker without an existing contract', async() => {
|
||||
let date = Date.vnNew();
|
||||
date.setDate(date.getDate() - 2);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
date.setHours(0, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date.setHours(20,0, 1);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Superado el tiempo máximo entre entrada y salida`);
|
||||
});
|
||||
|
||||
describe('direction errors', () => {
|
||||
let date = Date.vnNew();
|
||||
date.setDate(date.getDate() - 1);
|
||||
let error;
|
||||
it('should throw an error when trying "in" direction twice', async() => {
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date.setHours(10, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Dirección incorrecta`);
|
||||
});
|
||||
|
||||
it('should throw an error when trying "in" direction after insert "in" and "middle"', async() => {
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
date.setHours(9, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'middle'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date.setHours(10, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Dirección incorrecta`);
|
||||
});
|
||||
|
||||
it('Should throw an error when trying "out" before closing a "middle" couple', async() => {
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(9, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'middle'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date.setHours(10, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Dirección incorrecta`);
|
||||
});
|
||||
|
||||
it('should throw an error when trying "middle" after "out"', async() => {
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(9, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date.setHours(10, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'middle'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Dirección incorrecta`);
|
||||
});
|
||||
|
||||
it('should throw an error when trying "out" direction twice', async() => {
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(9, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date.setHours(10, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Dirección incorrecta`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('12h rest', () => {
|
||||
activeCtx.accessToken.userId = salesBossId;
|
||||
const workerId = hankPymId;
|
||||
it('should throw an error when the 12h rest is not fulfilled yet', async() => {
|
||||
|
||||
let date = Date.vnNew();
|
||||
date.setDate(date.getDate() - 2);
|
||||
date = weekDay(date, monday);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(16, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date = weekDay(date, tuesday);
|
||||
date.setHours(4, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Descanso diario`);
|
||||
});
|
||||
|
||||
it('should not fail as the 12h rest is fulfilled', async() => {
|
||||
let date = Date.vnNew();
|
||||
date.setDate(date.getDate() - 2);
|
||||
date = weekDay(date, monday);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(16, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date = weekDay(date, tuesday);
|
||||
date.setHours(4, 1, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).not.toBeDefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('for 3500kg drivers with enforced 9h rest', () => {
|
||||
activeCtx.accessToken.userId = salesBossId;
|
||||
const workerId = jessicaJonesId;
|
||||
it('should throw an error when the 9h enforced rest is not fulfilled', async() => {
|
||||
|
||||
let date = Date.vnNew();
|
||||
date.setDate(date.getDate() - 2);
|
||||
date = weekDay(date, monday);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(16, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date = weekDay(date, tuesday);
|
||||
date.setHours(1, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Descanso diario`);
|
||||
});
|
||||
|
||||
it('should not fail when the 9h enforced rest is fulfilled', async() => {
|
||||
|
||||
let date = Date.vnNew();
|
||||
date.setDate(date.getDate() - 2);
|
||||
date = weekDay(date, monday);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
date.setHours(8, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
date.setHours(16, 0, 0);
|
||||
ctx.args = {timed: date, direction: 'out'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date = weekDay(date, tuesday);
|
||||
date.setHours(1, 1, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).not.toBeDefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('for 72h weekly rest', () => {
|
||||
|
||||
it('should throw an error when work 11 consecutive days', async() => {
|
||||
let date = Date.vnNew();
|
||||
date.setMonth(date.getMonth() - 1);
|
||||
date.setDate(1);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
await populateWeek(date, monday, sunday, ctx, workerId, options);
|
||||
date = nextWeek(date);
|
||||
await populateWeek(date, monday, thursday, ctx, workerId, options);
|
||||
try {
|
||||
date = weekDay(date, friday);
|
||||
date.setHours(10, 0, 1);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Descanso semanal`);
|
||||
});
|
||||
|
||||
it('should throw an error when the 72h weekly rest is not fulfilled', async() => {
|
||||
|
||||
let date = Date.vnNew();
|
||||
date.setMonth(date.getMonth() - 1);
|
||||
date.setDate(1);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
await populateWeek(date, monday, sunday, ctx, workerId, options);
|
||||
date = nextWeek(date);
|
||||
await populateWeek(date, monday, thursday, ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date = weekDay(date, sunday);
|
||||
date.setHours(17, 59, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe(`Descanso semanal`);
|
||||
});
|
||||
|
||||
it('should throw an error when the 72h weekly rest is fulfilled', async() => {
|
||||
|
||||
let date = Date.vnNew();
|
||||
date.setMonth(date.getMonth() - 1);
|
||||
date.setDate(1);
|
||||
let error;
|
||||
|
||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
await populateWeek(date, monday, sunday, ctx, workerId, options);
|
||||
date = nextWeek(date);
|
||||
await populateWeek(date, monday, thursday, ctx, workerId, options);
|
||||
|
||||
try {
|
||||
date = weekDay(date, sunday);
|
||||
date.setHours(18, 00, 0);
|
||||
ctx.args = {timed: date, direction: 'in'};
|
||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).not.toBeDefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('WorkerTimeControl_calculate calls', () => {
|
||||
let dated = Date.vnNew();
|
||||
dated.setDate(dated.getDate() - 7);
|
||||
|
@ -836,25 +279,6 @@ function weekDay(date, dayToSet) {
|
|||
return date;
|
||||
}
|
||||
|
||||
function nextWeek(date) {
|
||||
const sunday = 7;
|
||||
const currentDay = date.getDay();
|
||||
let newDate = date;
|
||||
if (currentDay != 0)
|
||||
newDate = weekDay(date, sunday);
|
||||
|
||||
newDate.setDate(newDate.getDate() + 1);
|
||||
return newDate;
|
||||
}
|
||||
|
||||
function lastWeek(date) {
|
||||
const monday = 1;
|
||||
newDate = weekDay(date, monday);
|
||||
|
||||
newDate.setDate(newDate.getDate() - 1);
|
||||
return newDate;
|
||||
}
|
||||
|
||||
async function populateWeek(date, dayStart, dayEnd, ctx, workerId, options) {
|
||||
const dateStart = new Date(weekDay(date, dayStart));
|
||||
const dateEnd = new Date(dateStart);
|
||||
|
|
|
@ -10,6 +10,9 @@ module.exports = Self => {
|
|||
require('../methods/worker-time-control/weeklyHourRecordEmail')(Self);
|
||||
require('../methods/worker-time-control/getMailStates')(Self);
|
||||
require('../methods/worker-time-control/resendWeeklyHourEmail')(Self);
|
||||
require('../methods/worker-time-control/login')(Self);
|
||||
require('../methods/worker-time-control/getClockIn')(Self);
|
||||
require('../methods/worker-time-control/clockIn')(Self);
|
||||
|
||||
Self.rewriteDbError(function(err) {
|
||||
if (err.code === 'ER_DUP_ENTRY')
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "salix-back",
|
||||
"version": "24.02.01",
|
||||
"version": "24.04.01",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "salix-back",
|
||||
"version": "24.02.01",
|
||||
"version": "24.04.01",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.2.2",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "salix-back",
|
||||
"version": "24.02.01",
|
||||
"version": "24.04.01",
|
||||
"author": "Verdnatura Levante SL",
|
||||
"description": "Salix backend",
|
||||
"license": "GPL-3.0",
|
||||
|
|
Loading…
Reference in New Issue