fix: refs #7917 fix route filter freeLancer #3327

Merged
carlossa merged 33 commits from 7917-freelancerRoute2 into dev 2025-01-29 10:37:05 +00:00
36 changed files with 652 additions and 203 deletions
Showing only changes of commit 62f26ec8c8 - Show all commits

View File

@ -19,6 +19,7 @@ BEGIN
sub2.iptd futureIpt, sub2.iptd futureIpt,
sub2.state futureState, sub2.state futureState,
t.clientFk, t.clientFk,
cl.salespersonFk,
t.warehouseFk, t.warehouseFk,
ts.alertLevel, ts.alertLevel,
sub2.alertLevel futureAlertLevel, sub2.alertLevel futureAlertLevel,
@ -38,6 +39,7 @@ BEGIN
JOIN vn.province p ON p.id = a.provinceFk JOIN vn.province p ON p.id = a.provinceFk
JOIN vn.country c ON c.id = p.countryFk JOIN vn.country c ON c.id = p.countryFk
JOIN vn.ticketState ts ON ts.ticketFk = t.id JOIN vn.ticketState ts ON ts.ticketFk = t.id
JOIN vn.client cl ON cl.id = t.clientFk
JOIN vn.state st ON st.id = ts.stateFk JOIN vn.state st ON st.id = ts.stateFk
JOIN vn.alertLevel al ON al.id = ts.alertLevel JOIN vn.alertLevel al ON al.id = ts.alertLevel
LEFT JOIN vn.ticketParking tp ON tp.ticketFk = t.id LEFT JOIN vn.ticketParking tp ON tp.ticketFk = t.id

View File

@ -0,0 +1,8 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`itemTaxCountry_beforeDelete`
BEFORE DELETE ON `itemTaxCountry`
FOR EACH ROW
BEGIN
CALL util.throw('Records in this table cannot be deleted');
END$$
DELIMITER ;

View File

@ -4,5 +4,9 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`itemTaxCountry_beforeUp
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
SET NEW.editorFk = account.myUser_getId(); SET NEW.editorFk = account.myUser_getId();
IF NOT(NEW.`countryFk` <=> OLD.`countryFk`) OR NOT(NEW.`itemFk` <=> OLD.`itemFk`) THEN
CALL util.throw('Only the VAT can be modified');
END IF;
END$$ END$$
DELIMITER ; DELIMITER ;

View File

@ -1,8 +0,0 @@
CREATE OR REPLACE DEFINER=`root`@`localhost`
SQL SECURITY DEFINER
VIEW `vn2008`.`Split_lines`
AS SELECT `sl`.`id` AS `Id_Split_lines`,
`sl`.`splitFk` AS `Id_Split`,
`sl`.`itemFk` AS `Id_Article`,
`sl`.`buyFk` AS `Id_Compra`
FROM `vn`.`splitLine` `sl`

View File

@ -0,0 +1,10 @@
UPDATE vn.town t
LEFT JOIN vn.zoneGeo zg ON zg.id = t.geoFk
SET t.geoFk = NULL
WHERE zg.id IS NULL;
ALTER TABLE vn.town
ADD CONSTRAINT town_zoneGeo_FK FOREIGN KEY (geoFk)
REFERENCES vn.zoneGeo(id)
ON DELETE RESTRICT
ON UPDATE CASCADE;

View File

@ -0,0 +1,10 @@
UPDATE vn.postCode pc
LEFT JOIN vn.zoneGeo zg ON zg.id = pc.geoFk
SET pc.geoFk = NULL
WHERE zg.id IS NULL;
ALTER TABLE vn.postCode
ADD CONSTRAINT postCode_zoneGeo_FK FOREIGN KEY (geoFk)
REFERENCES vn.zoneGeo(id)
ON DELETE RESTRICT
ON UPDATE CASCADE;

View File

@ -0,0 +1,10 @@
UPDATE vn.province p
LEFT JOIN vn.zoneGeo zg ON zg.id = p.geoFk
SET p.geoFk = NULL
WHERE zg.id IS NULL;
ALTER TABLE vn.province
ADD CONSTRAINT province_zoneGeo_FK FOREIGN KEY (geoFk)
REFERENCES vn.zoneGeo(id)
ON DELETE RESTRICT
ON UPDATE CASCADE;

View File

@ -0,0 +1,130 @@
-- Place your SQL code here
CREATE TABLE IF NOT EXISTS `vn`.`itemSoldOutTag` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT
CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Ultimas unidades');
INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Temporalmente');
INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Descatalogado');
INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta mayo');
INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta febrero');
INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta diciembre');
INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta enero');
INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta marzo');
INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta nueva temporada');
INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta septiembre');
UPDATE vn.tag
SET isFree=FALSE,
sourceTable='itemSoldOutTag'
WHERE name= 'Agotado';
CREATE TABLE IF NOT EXISTS `vn`.`itemDurationTag` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT
CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('10 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('11 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('12 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('13 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('14 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('15 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('17 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('7 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('9 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('16-20 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('17-21 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('19-23 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('3-4 semanas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('13-17 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('14-16 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('15-19 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('18-25 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('20 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('6 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('9 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('10-13 días');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('6 meses');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('5 años');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('10 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('20 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('35 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('6 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('11 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('12 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('14 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('15 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('18 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('19 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('24 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('25 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('30 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('32 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('4 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('40 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('45 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('50 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('55 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('70 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('8 horas');
INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('9 horas');
UPDATE vn.tag
SET isFree=FALSE,
sourceTable='itemDurationTag'
WHERE name= 'Duracion';
CREATE TABLE IF NOT EXISTS `vn`.`itemGrowingTag` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT
CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-05');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-06');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-12');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('02-06');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('03-05');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('03-07');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('03-08');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('03-11');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('04-06');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('04-09');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('04-11');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('05-07');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('05-08');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('05-10');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('05-11');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('06-09');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('06-10');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('06-11');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('07-09');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('07-10');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('07-11');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('07-12');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('09-12');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-04 / 10-12');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-04 / 9-12');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-05 / 10-12');
INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-05 / 11-12');
UPDATE vn.tag
SET isFree=FALSE,
sourceTable='itemGrowingTag'
WHERE name= 'Recolecta';
GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE vn.itemSoldOutTag TO logisticAssist;
GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE vn.itemDurationTag TO logisticAssist;
GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE vn.itemGrowingTag TO logisticAssist;

View File

@ -0,0 +1,5 @@
RENAME TABLE vn.sorter TO vn.sorter__;
ALTER TABLE vn.sorter__ COMMENT='@deprecated 2025-01-22';
RENAME TABLE vn.splitLine TO vn.splitLine__;
ALTER TABLE vn.splitLine__ COMMENT='@deprecated 2025-01-22';

View File

@ -238,25 +238,11 @@ describe('Ticket Edit sale path', () => {
await page.waitToClick(selectors.globalItems.cancelButton); await page.waitToClick(selectors.globalItems.cancelButton);
}); });
it('should select the third sale and create a claim of it', async() => { it('should select the third sale and delete it', async() => {
await page.accessToSearchResult('16');
await page.accessToSection('ticket.card.sale');
await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim);
await page.waitToClick(selectors.globalItems.acceptButton);
await page.waitForNavigation();
});
it('should search for a ticket then access to the sales section', async() => {
await page.goBack();
await page.goBack();
await page.loginAndModule('salesPerson', 'ticket'); await page.loginAndModule('salesPerson', 'ticket');
await page.accessToSearchResult('16'); await page.accessToSearchResult('16');
await page.accessToSection('ticket.card.sale'); await page.accessToSection('ticket.card.sale');
});
it('should select the third sale and delete it', async() => {
await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox); await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketSales.deleteSaleButton); await page.waitToClick(selectors.ticketSales.deleteSaleButton);
await page.waitToClick(selectors.globalItems.acceptButton); await page.waitToClick(selectors.globalItems.acceptButton);

View File

@ -75,7 +75,7 @@ describe('Ticket Edit basic data path', () => {
const result = await page const result = await page
.waitToGetProperty(selectors.ticketBasicData.stepTwoTotalPriceDif, 'innerText'); .waitToGetProperty(selectors.ticketBasicData.stepTwoTotalPriceDif, 'innerText');
expect(result).toContain('-€228.25'); expect(result).toContain('-€111.75');
}); });
it(`should select a new reason for the changes made then click on finalize`, async() => { it(`should select a new reason for the changes made then click on finalize`, async() => {

View File

@ -211,7 +211,7 @@
"Name should be uppercase": "Name should be uppercase", "Name should be uppercase": "Name should be uppercase",
"You cannot update these fields": "You cannot update these fields", "You cannot update these fields": "You cannot update these fields",
"CountryFK cannot be empty": "Country cannot be empty", "CountryFK cannot be empty": "Country cannot be empty",
"No tickets to invoice": "There are no tickets to invoice that meet the invoicing requirements", "No tickets to invoice": "There are no tickets to invoice that meet the invoicing requirements",
"You are not allowed to modify the alias": "You are not allowed to modify the alias", "You are not allowed to modify the alias": "You are not allowed to modify the alias",
"You already have the mailAlias": "You already have the mailAlias", "You already have the mailAlias": "You already have the mailAlias",
"This machine is already in use.": "This machine is already in use.", "This machine is already in use.": "This machine is already in use.",
@ -247,9 +247,11 @@
"ticketLostExpedition": "The ticket [{{ticketId}}]({{{ticketUrl}}}) has the following lost expedition:{{ expeditionId }}", "ticketLostExpedition": "The ticket [{{ticketId}}]({{{ticketUrl}}}) has the following lost expedition:{{ expeditionId }}",
"The raid information is not correct": "The raid information is not correct", "The raid information is not correct": "The raid information is not correct",
"Payment method is required": "Payment method is required", "Payment method is required": "Payment method is required",
"Sales already moved": "Sales already moved",
"Holidays to past days not available": "Holidays to past days not available",
"Price cannot be blank": "Price cannot be blank", "Price cannot be blank": "Price cannot be blank",
"There are tickets to be invoiced": "There are tickets to be invoiced", "There are tickets to be invoiced": "There are tickets to be invoiced",
"The address of the customer must have information about Incoterms and Customs Agent": "The address of the customer must have information about Incoterms and Customs Agent" "The address of the customer must have information about Incoterms and Customs Agent": "The address of the customer must have information about Incoterms and Customs Agent",
"Sales already moved": "Sales already moved",
"Holidays to past days not available": "Holidays to past days not available",
"Incorrect delivery order alert on route": "Incorrect delivery order alert on route: {{ route }} zone: {{ zone }}",
"Ticket has been delivered out of order": "The ticket {{ticket}} {{{fullUrl}}} has been delivered out of order."
} }

View File

@ -390,14 +390,11 @@
"The web user's email already exists": "El correo del usuario web ya existe", "The web user's email already exists": "El correo del usuario web ya existe",
"Sales already moved": "Ya han sido transferidas", "Sales already moved": "Ya han sido transferidas",
"The raid information is not correct": "La información de la redada no es correcta", "The raid information is not correct": "La información de la redada no es correcta",
"No trips found because input coordinates are not connected": "No se encontraron rutas porque las coordenadas de entrada no están conectadas",
"This request is not supported": "Esta solicitud no es compatible",
"Invalid options or too many coordinates": "Opciones invalidas o demasiadas coordenadas",
"No address has coordinates": "Ninguna dirección tiene coordenadas",
"An item type with the same code already exists": "Un tipo con el mismo código ya existe", "An item type with the same code already exists": "Un tipo con el mismo código ya existe",
"Holidays to past days not available": "Las vacaciones a días pasados no están disponibles", "Holidays to past days not available": "Las vacaciones a días pasados no están disponibles",
"All tickets have a route order": "Todos los tickets tienen orden de ruta", "All tickets have a route order": "Todos los tickets tienen orden de ruta",
"Price cannot be blank": "Price cannot be blank",
"There are tickets to be invoiced": "La zona tiene tickets por facturar", "There are tickets to be invoiced": "La zona tiene tickets por facturar",
"Social name should be uppercase": "La razón social debe ir en mayúscula" "Incorrect delivery order alert on route": "Alerta de orden de entrega incorrecta en ruta: {{ route }} zona: {{ zone }}",
"Ticket has been delivered out of order": "El ticket {{ticket}} {{{fullUrl}}} no ha sigo entregado en su orden.",
"Price cannot be blank": "El precio no puede estar en blanco"
} }

View File

@ -362,9 +362,11 @@
"The invoices have been created but the PDFs could not be generated": "La facture a été émise mais le PDF n'a pas pu être généré", "The invoices have been created but the PDFs could not be generated": "La facture a été émise mais le PDF n'a pas pu être généré",
"It has been invoiced but the PDF of refund not be generated": "Il a été facturé mais le PDF de remboursement n'a pas été généré", "It has been invoiced but the PDF of refund not be generated": "Il a été facturé mais le PDF de remboursement n'a pas été généré",
"Cannot send mail": "Impossible d'envoyer le mail", "Cannot send mail": "Impossible d'envoyer le mail",
"Original invoice not found": "Facture originale introuvable", "Original invoice not found": "Facture originale introuvable",
"The quantity claimed cannot be greater than the quantity of the line": "Le montant réclamé ne peut pas être supérieur au montant de la ligne", "The quantity claimed cannot be greater than the quantity of the line": "Le montant réclamé ne peut pas être supérieur au montant de la ligne",
"You do not have permission to modify the booked field": "Vous n'avez pas la permission de modifier le champ comptabilisé", "You do not have permission to modify the booked field": "Vous n'avez pas la permission de modifier le champ comptabilisé",
"ticketLostExpedition": "Le ticket [{{ticketId}}]({{{ticketUrl}}}) a l'expédition perdue suivante : {{expeditionId}}", "ticketLostExpedition": "Le ticket [{{ticketId}}]({{{ticketUrl}}}) a l'expédition perdue suivante : {{expeditionId}}",
"The web user's email already exists": "L'email de l'internaute existe déjà" "The web user's email already exists": "L'email de l'internaute existe déjà",
"Incorrect delivery order alert on route": "Alerte de bon de livraison incorrect sur l'itinéraire: {{ route }} zone : {{ zone }}",
"Ticket has been delivered out of order": "Le ticket {{ticket}} {{{fullUrl}}} a été livré hors ordre."
} }

View File

@ -365,5 +365,7 @@
"Cannot send mail": "Não é possível enviar o email", "Cannot send mail": "Não é possível enviar o email",
"The quantity claimed cannot be greater than the quantity of the line": "O valor reclamado não pode ser superior ao valor da linha", "The quantity claimed cannot be greater than the quantity of the line": "O valor reclamado não pode ser superior ao valor da linha",
"ticketLostExpedition": "O ticket [{{ticketId}}]({{{ticketUrl}}}) tem a seguinte expedição perdida: {{expeditionId}}", "ticketLostExpedition": "O ticket [{{ticketId}}]({{{ticketUrl}}}) tem a seguinte expedição perdida: {{expeditionId}}",
"The web user's email already exists": "O e-mail do utilizador da web já existe." "The web user's email already exists": "O e-mail do utilizador da web já existe.",
"Incorrect delivery order alert on route": "Alerta de ordem de entrega incorreta na rota: {{ route }} zona: {{ zone }}",
"Ticket has been delivered out of order": "O ticket {{ticket}} {{{fullUrl}}} foi entregue fora de ordem."
} }

View File

@ -109,6 +109,7 @@ module.exports = Self => {
const args = ctx.args; const args = ctx.args;
const myOptions = {}; const myOptions = {};
let to; let to;
let myTeamIds = [];
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
@ -133,21 +134,8 @@ module.exports = Self => {
claimIdsByClaimResponsibleFk = claims.map(claim => claim.claimFk); claimIdsByClaimResponsibleFk = claims.map(claim => claim.claimFk);
} }
// Apply filter by team if (args.myTeam != null)
const teamMembersId = []; myTeamIds = await models.Worker.myTeam(userId);
if (args.myTeam != null) {
const worker = await models.Worker.findById(userId, {
include: {
relation: 'collegues'
}
}, myOptions);
const collegues = worker.collegues() || [];
for (let collegue of collegues)
teamMembersId.push(collegue.collegueFk);
if (teamMembersId.length == 0)
teamMembersId.push(userId);
}
const where = buildFilter(ctx.args, (param, value) => { const where = buildFilter(ctx.args, (param, value) => {
switch (param) { switch (param) {
@ -184,9 +172,9 @@ module.exports = Self => {
return {'t.zoneFk': value}; return {'t.zoneFk': value};
case 'myTeam': case 'myTeam':
if (value) if (value)
return {'cl.workerFk': {inq: teamMembersId}}; return {'cl.workerFk': {inq: myTeamIds}};
else else
return {'cl.workerFk': {nin: teamMembersId}}; return {'cl.workerFk': {nin: myTeamIds}};
} }
}); });

View File

@ -157,4 +157,52 @@ describe('Address updateAddress', () => {
throw e; throw e;
} }
}); });
it('should update ticket observations when updateObservations is true', async() => {
const tx = await models.Client.beginTransaction({});
const client = 1103;
const address = 123;
const ticket = 31;
const observationType = 3;
const salesAssistantId = 21;
const addressObservation = 'nuevo texto';
const ticketObservation = 'texto a modificar';
try {
const options = {transaction: tx};
ctx.req.accessToken.userId = salesAssistantId;
ctx.args = {
updateObservations: true,
incotermsFk: incotermsId,
provinceFk: provinceId,
customsAgentFk: customAgentOneId
};
await models.AddressObservation.create({
addressFk: address,
observationTypeFk: observationType,
description: addressObservation
}, options);
await models.TicketObservation.create({
ticketFk: ticket,
observationTypeFk: observationType,
description: ticketObservation
}, options);
await models.Client.updateAddress(ctx, client, address, options);
const updatedObservation = await models.TicketObservation.findOne({
where: {ticketFk: ticket, observationTypeFk: observationType}
}, options);
expect(updatedObservation.description).toEqual(addressObservation);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
}); });

View File

@ -80,6 +80,10 @@ module.exports = function(Self) {
{ {
arg: 'latitude', arg: 'latitude',
type: 'any', type: 'any',
},
{
arg: 'updateObservations',
type: 'boolean'
} }
], ],
returns: { returns: {
@ -135,6 +139,17 @@ module.exports = function(Self) {
delete args.ctx; // Remove unwanted properties delete args.ctx; // Remove unwanted properties
const updatedAddress = await address.updateAttributes(ctx.args, myOptions); const updatedAddress = await address.updateAttributes(ctx.args, myOptions);
if (args.updateObservations) {
const ticket = await Self.rawSql(`
UPDATE ticketObservation to2
JOIN ticket t ON t.id = to2.ticketFk
JOIN address a ON a.id = t.addressFk
JOIN addressObservation ao ON ao.addressFk = a.id
SET to2.description = ao.description
WHERE ao.observationTypeFk = to2.observationTypeFk
AND a.id = ?
AND t.shipped >= util.VN_CURDATE()`, [addressId], myOptions);
}
return updatedAddress; return updatedAddress;
}; };

View File

@ -21,7 +21,7 @@ module.exports = Self => {
} }
], ],
returns: { returns: {
type: ['object'], type: 'object',
root: true root: true
}, },
http: { http: {
@ -41,23 +41,28 @@ module.exports = Self => {
switch (param) { switch (param) {
case 'search': case 'search':
return {or: [ return {or: [
{'d.clientFk': value}, {'c.id': value},
{'d.clientName': {like: `%${value}%`}} {'c.name': {like: `%${value}%`}}
]}; ]};
} }
}); });
filter = mergeFilters(ctx.args.filter, {where}); const date = Date.vnNew();
date.setHours(0, 0, 0, 0);
filter = mergeFilters({where: {'d.created': date, 'd.amount': {gt: 0}}}, ctx.args.filter);
filter = mergeFilters(filter, {where});
const stmts = []; const stmts = [];
const date = Date.vnNew(); let stmt = new ParameterizedSQL(
date.setHours(0, 0, 0, 0); `CREATE OR REPLACE TEMPORARY TABLE tmp.defaulters
const stmt = new ParameterizedSQL( WITH clientObservations AS
`SELECT * (SELECT clientFk,text, created, workerFk
FROM ( FROM vn.clientObservation
SELECT GROUP BY clientFk
DISTINCT c.id clientFk, ORDER BY created DESC
)SELECT c.id clientFk,
c.name clientName, c.name clientName,
c.salesPersonFk, c.salesPersonFk,
c.businessTypeFk = 'worker' isWorker, c.businessTypeFk = 'worker' isWorker,
@ -80,36 +85,43 @@ module.exports = Self => {
JOIN client c ON c.id = d.clientFk JOIN client c ON c.id = d.clientFk
JOIN country cn ON cn.id = c.countryFk JOIN country cn ON cn.id = c.countryFk
JOIN payMethod pm ON pm.id = c.payMethodFk JOIN payMethod pm ON pm.id = c.payMethodFk
LEFT JOIN clientObservation co ON co.clientFk = c.id LEFT JOIN clientObservations co ON co.clientFk = c.id
LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN account.user u ON u.id = c.salesPersonFk
LEFT JOIN account.user uw ON uw.id = co.workerFk LEFT JOIN account.user uw ON uw.id = co.workerFk
LEFT JOIN ( LEFT JOIN (
SELECT r1.started, r1.clientFk, r1.finished SELECT r1.started, r1.clientFk, r1.finished
FROM recovery r1 FROM recovery r1
JOIN ( JOIN (
SELECT MAX(started) AS maxStarted, clientFk SELECT MAX(started) maxStarted, clientFk
FROM recovery FROM recovery
GROUP BY clientFk GROUP BY clientFk
) r2 ON r1.clientFk = r2.clientFk ) r2 ON r1.clientFk = r2.clientFk
AND r1.started = r2.maxStarted AND r1.started = r2.maxStarted
WHERE r1.finished
GROUP BY r1.clientFk
) r ON r.clientFk = c.id ) r ON r.clientFk = c.id
LEFT JOIN workerDepartment wd ON wd.workerFk = u.id LEFT JOIN workerDepartment wd ON wd.workerFk = u.id
LEFT JOIN department dp ON dp.id = wd.departmentFk LEFT JOIN department dp ON dp.id = wd.departmentFk`);
WHERE
d.created = ?
AND d.amount > 0
ORDER BY co.created DESC) d`
, [date]);
stmt.merge(conn.makeWhere(filter.where)); stmt.merge(conn.makeWhere(filter.where));
stmt.merge(`GROUP BY d.clientFk`); stmts.push(stmt);
stmt = new ParameterizedSQL(`
SELECT SUM(amount) amount
FROM tmp.defaulters
`);
stmts.push(stmt);
stmt = new ParameterizedSQL(`
SELECT *
FROM tmp.defaulters
`);
stmt.merge(conn.makeOrderBy(filter.order)); stmt.merge(conn.makeOrderBy(filter.order));
stmt.merge(conn.makeLimit(filter));
const itemsIndex = stmts.push(stmt) - 1; const itemsIndex = stmts.push(stmt) - 1;
const sql = ParameterizedSQL.join(stmts, ';'); const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions); const result = await conn.executeStmt(sql, myOptions);
return itemsIndex === 0 ? result : result[itemsIndex]; return {defaulters: result[itemsIndex], amount: result[itemsIndex - 1][0].amount};
}; };
}; };

View File

@ -11,10 +11,10 @@ describe('defaulter filter()', () => {
const ctx = {req: {accessToken: {userId: authUserId}}, args: {filter: filter}}; const ctx = {req: {accessToken: {userId: authUserId}}, args: {filter: filter}};
const result = await models.Defaulter.filter(ctx, null, options); const result = await models.Defaulter.filter(ctx, null, options);
const firstRow = result[0]; const firstRow = result.defaulters[0];
expect(firstRow.clientFk).toEqual(1101); expect(firstRow.clientFk).toEqual(1101);
expect(result.length).toEqual(5); expect(result.defaulters.length).toEqual(5);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -31,7 +31,7 @@ describe('defaulter filter()', () => {
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 1101}}; const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 1101}};
const result = await models.Defaulter.filter(ctx, null, options); const result = await models.Defaulter.filter(ctx, null, options);
const firstRow = result[0]; const firstRow = result.defaulters[0];
expect(firstRow.clientFk).toEqual(1101); expect(firstRow.clientFk).toEqual(1101);
@ -50,7 +50,7 @@ describe('defaulter filter()', () => {
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'Petter Parker'}}; const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'Petter Parker'}};
const result = await models.Defaulter.filter(ctx, null, options); const result = await models.Defaulter.filter(ctx, null, options);
const firstRow = result[0]; const firstRow = result.defaulters[0];
expect(firstRow.clientName).toEqual('Petter Parker'); expect(firstRow.clientName).toEqual('Petter Parker');
@ -60,4 +60,23 @@ describe('defaulter filter()', () => {
throw e; throw e;
} }
}); });
it('should return the defaulter the sum of every defaulters', async() => {
const tx = await models.Defaulter.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 1101}};
const {defaulters, amount} = await models.Defaulter.filter(ctx, null, options);
const total = defaulters.reduce((total, row) => total + row.amount, 0);
expect(total).toEqual(amount);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
}); });

View File

@ -123,25 +123,13 @@ module.exports = Self => {
date.setHours(0, 0, 0, 0); date.setHours(0, 0, 0, 0);
const args = ctx.args; const args = ctx.args;
const myOptions = {}; const myOptions = {};
let myTeamIds = [];
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
// Apply filter by team if (args.myTeam != null)
const teamMembersId = []; myTeamIds = await models.Worker.myTeam(userId);
if (args.myTeam != null) {
const worker = await models.Worker.findById(userId, {
include: {
relation: 'collegues'
}
}, myOptions);
const collegues = worker.collegues() || [];
for (let collegue of collegues)
teamMembersId.push(collegue.collegueFk);
if (teamMembersId.length == 0)
teamMembersId.push(userId);
}
if (ctx.args && args.to) { if (ctx.args && args.to) {
const dateTo = args.to; const dateTo = args.to;
@ -163,9 +151,9 @@ module.exports = Self => {
case 'mine': case 'mine':
case 'myTeam': case 'myTeam':
if (value) if (value)
return {'c.salesPersonFk': {inq: teamMembersId}}; return {'c.salesPersonFk': {inq: myTeamIds}};
else else
return {'c.salesPersonFk': {nin: teamMembersId}}; return {'c.salesPersonFk': {nin: myTeamIds}};
case 'id': case 'id':
case 'clientFk': case 'clientFk':
param = `t.${param}`; param = `t.${param}`;

View File

@ -80,29 +80,15 @@ module.exports = Self => {
const conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
const myOptions = {}; const myOptions = {};
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
let myTeamIds = [];
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const args = ctx.args; const args = ctx.args;
// Apply filter by team
const teamMembersId = [];
if (args.myTeam != null) {
const worker = await models.Worker.findById(userId, {
include: {
relation: 'collegues'
}
}, myOptions);
const collegues = worker.collegues() || [];
for (let collegue of collegues)
teamMembersId.push(collegue.collegueFk);
if (teamMembersId.length == 0) if (args.myTeam != null)
teamMembersId.push(userId); myTeamIds = await models.Worker.myTeam(userId);
}
if (args?.myTeam)
args.teamIds = teamIds;
if (args?.to) if (args?.to)
args.to.setHours(23, 59, 0, 0); args.to.setHours(23, 59, 0, 0);
@ -133,9 +119,9 @@ module.exports = Self => {
return {'o.confirmed': value ? 1 : 0}; return {'o.confirmed': value ? 1 : 0};
case 'myTeam': case 'myTeam':
if (value) if (value)
return {'c.salesPersonFk': {inq: teamMembersId}}; return {'c.salesPersonFk': {inq: myTeamIds}};
else else
return {'c.salesPersonFk': {nin: teamMembersId}}; return {'c.salesPersonFk': {nin: myTeamIds}};
case 'showEmpty': case 'showEmpty':
return {'o.total': {neq: value}}; return {'o.total': {neq: value}};
case 'id': case 'id':

View File

@ -87,6 +87,7 @@ module.exports = Self => {
const myOptions = {}; const myOptions = {};
const models = Self.app.models; const models = Self.app.models;
const args = ctx.args; const args = ctx.args;
let myTeamIds = [];
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
@ -94,20 +95,8 @@ module.exports = Self => {
if (ctx.args.mine) if (ctx.args.mine)
ctx.args.attenderFk = userId; ctx.args.attenderFk = userId;
const teamMembersId = []; if (args.myTeam != null)
if (args.myTeam != null) { myTeamIds = await models.Worker.myTeam(userId);
const worker = await models.Worker.findById(userId, {
include: {
relation: 'collegues'
}
}, myOptions);
const collegues = worker.collegues() || [];
for (let collegue of collegues)
teamMembersId.push(collegue.collegueFk);
if (teamMembersId.length == 0)
teamMembersId.push(userId);
}
const today = Date.vnNew(); const today = Date.vnNew();
const future = Date.vnNew(); const future = Date.vnNew();
@ -145,9 +134,9 @@ module.exports = Self => {
return {'c.salesPersonFk': value}; return {'c.salesPersonFk': value};
case 'myTeam': case 'myTeam':
if (value) if (value)
return {'tr.requesterFk': {inq: teamMembersId}}; return {'tr.requesterFk': {inq: myTeamIds}};
else else
return {'tr.requesterFk': {nin: teamMembersId}}; return {'tr.requesterFk': {nin: myTeamIds}};
case 'daysOnward': case 'daysOnward':
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
future.setDate(today.getDate() + value); future.setDate(today.getDate() + value);

View File

@ -142,28 +142,14 @@ module.exports = Self => {
date.setHours(0, 0, 0, 0); date.setHours(0, 0, 0, 0);
const models = Self.app.models; const models = Self.app.models;
const args = ctx.args; const args = ctx.args;
let myTeamIds = [];
const myOptions = {}; const myOptions = {};
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
// Apply filter by team if (args.myTeam != null)
const teamMembersId = []; myTeamIds = await models.Worker.myTeam(userId);
if (args.myTeam != null) {
const worker = await models.Worker.findById(userId, {
include: {
relation: 'collegues'
}
}, myOptions);
const collegues = worker.collegues() || [];
for (let collegue of collegues)
teamMembersId.push(collegue.collegueFk);
if (teamMembersId.length == 0)
teamMembersId.push(userId);
}
if (ctx.args && args.to) { if (ctx.args && args.to) {
const dateTo = args.to; const dateTo = args.to;
@ -195,9 +181,9 @@ module.exports = Self => {
case 'mine': case 'mine':
case 'myTeam': case 'myTeam':
if (value) if (value)
return {'c.salesPersonFk': {inq: teamMembersId}}; return {'c.salesPersonFk': {inq: myTeamIds}};
else else
return {'c.salesPersonFk': {nin: teamMembersId}}; return {'c.salesPersonFk': {nin: myTeamIds}};
case 'alertLevel': case 'alertLevel':
return {'ts.alertLevel': value}; return {'ts.alertLevel': value};

View File

@ -40,21 +40,23 @@ module.exports = Self => {
try { try {
for (let ticket of tickets) { for (let ticket of tickets) {
const originFullPath = `${url}ticket/${ticket.originId}/summary`;
const destinationFullPath = `${url}ticket/${ticket.destinationId}/summary`;
const message = $t('Ticket merged', {
originDated: dateUtil.toString(new Date(ticket.originShipped)),
destinationDated: dateUtil.toString(new Date(ticket.destinationShipped)),
originId: ticket.originId,
destinationId: ticket.destinationId,
originFullPath,
destinationFullPath
});
if (!ticket.originId || !ticket.destinationId) continue; if (!ticket.originId || !ticket.destinationId) continue;
await models.Sale.updateAll({ticketFk: ticket.originId}, {ticketFk: ticket.destinationId}, myOptions); await models.Sale.updateAll({ticketFk: ticket.originId}, {ticketFk: ticket.destinationId}, myOptions);
if (await models.Ticket.setDeleted(ctx, ticket.originId, myOptions)) if (await models.Ticket.setDeleted(ctx, ticket.originId, myOptions)) {
await models.Chat.sendCheckingPresence(ctx, ticket.workerFk, message); if (!ticket.salesPersonFk) continue;
const originFullPath = `${url}ticket/${ticket.originId}/summary`;
const destinationFullPath = `${url}ticket/${ticket.destinationId}/summary`;
const message = $t('Ticket merged', {
originDated: dateUtil.toString(new Date(ticket.originShipped)),
destinationDated: dateUtil.toString(new Date(ticket.destinationShipped)),
originId: ticket.originId,
destinationId: ticket.destinationId,
originFullPath,
destinationFullPath
});
await models.Chat.sendCheckingPresence(ctx, ticket.salesPersonFk, message);
}
} }
if (tx) if (tx)
await tx.commit(); await tx.commit();

View File

@ -108,7 +108,10 @@ module.exports = Self => {
// Get items movable // Get items movable
const ticketOrigin = await models.Ticket.findById(args.id, null, myOptions); const ticketOrigin = await models.Ticket.findById(args.id, null, myOptions);
const differenceShipped = ticketOrigin.shipped.getTime() > args.shipped.getTime(); let shipped = ticketOrigin.shipped ?? ticketOrigin.updated;
const differenceShipped = shipped.getTime() > args.shipped.getTime();
const differenceWarehouse = ticketOrigin.warehouseFk != args.warehouseId; const differenceWarehouse = ticketOrigin.warehouseFk != args.warehouseId;
salesObj.haveDifferences = differenceShipped || differenceWarehouse; salesObj.haveDifferences = differenceShipped || differenceWarehouse;

View File

@ -28,7 +28,6 @@ module.exports = Self => {
verb: 'POST' verb: 'POST'
} }
}); });
Self.saveSign = async(ctx, tickets, location, signedTime, options) => { Self.saveSign = async(ctx, tickets, location, signedTime, options) => {
const models = Self.app.models; const models = Self.app.models;
const myOptions = {userId: ctx.req.accessToken.userId}; const myOptions = {userId: ctx.req.accessToken.userId};
@ -111,6 +110,12 @@ module.exports = Self => {
scope: { scope: {
fields: ['id'] fields: ['id']
} }
},
{
relation: 'zone',
scope: {
fields: ['id', 'zoneFk,', 'name']
}
}] }]
}, myOptions); }, myOptions);
@ -151,6 +156,28 @@ module.exports = Self => {
await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [ticketId, stateCode], myOptions); await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [ticketId, stateCode], myOptions);
if (stateCode == 'DELIVERED' && ticket.priority) {
const orderState = await models.State.findOne({
where: {code: 'DELIVERED'},
fields: ['id']
}, myOptions);
const ticketIncorrect = await Self.rawSql(`
SELECT t.id
FROM ticket t
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN state s ON s.code = ts.code
WHERE t.routeFk = ?
AND s.\`order\` < ?
AND priority <(SELECT t.priority
FROM ticket t
WHERE t.id = ?)`
, [ticket.routeFk, orderState.id, ticket.id], myOptions);
if (ticketIncorrect?.length > 0)
await sendMail(ctx, ticket.routeFk, ticket.id, ticket.zone().name);
}
if (ticket?.address()?.province()?.country()?.code != 'ES' && ticket.$cmrFk) { if (ticket?.address()?.province()?.country()?.code != 'ES' && ticket.$cmrFk) {
await models.Ticket.saveCmr(ctx, [ticketId], myOptions); await models.Ticket.saveCmr(ctx, [ticketId], myOptions);
externalTickets.push(ticketId); externalTickets.push(ticketId);
@ -163,4 +190,25 @@ module.exports = Self => {
} }
await models.Ticket.sendCmrEmail(ctx, externalTickets); await models.Ticket.sendCmrEmail(ctx, externalTickets);
}; };
async function sendMail(ctx, route, ticket, zoneName) {
const $t = ctx.req.__;
const url = await Self.app.models.Url.getUrl();
const sendTo = 'repartos@verdnatura.es';
const fullUrl = `${url}route/${route}/summary`;
const emailSubject = $t('Incorrect delivery order alert on route', {
route,
zone: zoneName
});
const emailBody = $t('Ticket has been delivered out of order', {
ticket,
fullUrl
});
await Self.app.models.Mail.create({
receiver: sendTo,
subject: emailSubject,
body: emailBody
});
}
}; };

View File

@ -7,7 +7,7 @@ describe('ticket merge()', () => {
destinationId: 12, destinationId: 12,
originShipped: Date.vnNew(), originShipped: Date.vnNew(),
destinationShipped: Date.vnNew(), destinationShipped: Date.vnNew(),
workerFk: 1 salesPersonFk: 1
}; };
it('should merge two tickets', async() => { it('should merge two tickets', async() => {

View File

@ -1,12 +1,20 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('Ticket saveSign()', () => { describe('Ticket saveSign()', () => {
let ctx = {req: { let ctx = {req: {
getLocale: () => { getLocale: () => {
return 'en'; return 'en';
}, },
__: () => {},
accessToken: {userId: 9} accessToken: {userId: 9}
}}; }
};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: ctx
});
});
it(`should throw error if the ticket's alert level is lower than 2`, async() => { it(`should throw error if the ticket's alert level is lower than 2`, async() => {
const tx = await models.TicketDms.beginTransaction({}); const tx = await models.TicketDms.beginTransaction({});
@ -51,4 +59,46 @@ describe('Ticket saveSign()', () => {
expect(ticketTrackingAfter.name).toBe('Entregado en parte'); expect(ticketTrackingAfter.name).toBe('Entregado en parte');
}); });
it('should send an email to notify that the delivery order is not correct', async() => {
const tx = await models.Ticket.beginTransaction({});
const ticketFk = 8;
const priority = 5;
const stateFk = 10;
const stateTicketFk = 2;
const expeditionFk = 11;
const expeditionStateFK = 2;
let mailCountBefore;
let mailCountAfter;
spyOn(models.Dms, 'uploadFile').and.returnValue([{id: 1}]);
const options = {transaction: tx};
const tickets = [ticketFk];
const expedition = await models.Expedition.findById(expeditionFk, null, options);
expedition.updateAttribute('stateTypeFk', expeditionStateFK, options);
const ticket = await models.Ticket.findById(ticketFk, null, options);
ticket.updateAttribute('priority', priority, options);
const filter = {where: {
ticketFk: ticketFk,
stateFk: stateTicketFk}
};
try {
const ticketTracking = await models.TicketTracking.findOne(filter, options);
ticketTracking.updateAttribute('stateFk', stateFk, options);
mailCountBefore = await models.Mail.count(options);
await models.Ticket.saveSign(ctx, tickets, null, null, options);
mailCountAfter = await models.Mail.count(options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(mailCountAfter).toBeGreaterThan(mailCountBefore);
});
}); });

View File

@ -22,14 +22,16 @@ describe('Ticket transferClient()', () => {
it('should throw an error as the ticket is not editable', async() => { it('should throw an error as the ticket is not editable', async() => {
try { try {
const ticketId = 4; const ticketId = 4;
await models.Ticket.transferClient(ctx, ticketId, clientId, options); const addressFk = null;
await models.Ticket.transferClient(ctx, ticketId, clientId, addressFk, options);
} catch (e) { } catch (e) {
expect(e.message).toEqual('This ticket is locked'); expect(e.message).toEqual('This ticket is locked');
} }
}); });
it('should be assigned a different clientFk and nickname in the original ticket', async() => { it('should be assigned a different clientFk and nickname in the original ticket', async() => {
await models.Ticket.transferClient(ctx, 2, clientId, options); const addressFk = null;
await models.Ticket.transferClient(ctx, 2, clientId, addressFk, options);
const afterTransfer = await models.Ticket.findById(2, null, options); const afterTransfer = await models.Ticket.findById(2, null, options);
const client = await models.Client.findById(clientId, {fields: ['defaultAddressFk']}, options); const client = await models.Client.findById(clientId, {fields: ['defaultAddressFk']}, options);
const address = await models.Address.findById(client.defaultAddressFk, {fields: ['nickname']}, options); const address = await models.Address.findById(client.defaultAddressFk, {fields: ['nickname']}, options);
@ -39,7 +41,8 @@ describe('Ticket transferClient()', () => {
}); });
it('should be assigned a different clientFk and nickname in the original and refund ticket and claim', async() => { it('should be assigned a different clientFk and nickname in the original and refund ticket and claim', async() => {
await models.Ticket.transferClient(ctx, originalTicketId, clientId, options); const addressFk = null;
await models.Ticket.transferClient(ctx, originalTicketId, clientId, addressFk, options);
const [originalTicket, refundTicket] = await models.Ticket.find({ const [originalTicket, refundTicket] = await models.Ticket.find({
where: {id: {inq: [originalTicketId, refundTicketId]}} where: {id: {inq: [originalTicketId, refundTicketId]}}
@ -59,4 +62,39 @@ describe('Ticket transferClient()', () => {
expect(originalTicket.nickname).toEqual(address.nickname); expect(originalTicket.nickname).toEqual(address.nickname);
expect(refundTicket.nickname).toEqual(address.nickname); expect(refundTicket.nickname).toEqual(address.nickname);
}); });
it('should be assigned a different addressFk and nickname in the original and refund ticket', async() => {
const addressFk = 131;
await models.Ticket.transferClient(ctx, originalTicketId, clientId, addressFk, options);
const [originalTicket, refundTicket] = await models.Ticket.find({
where: {id: {inq: [originalTicketId, refundTicketId]}}
}, options);
const claim = await models.Claim.findOne({
where: {ticketFk: originalTicketId}
}, options);
const address = await models.Address.findById(addressFk, {fields: ['id', 'nickname', 'clientFk']}, options);
expect(originalTicket.clientFk).toEqual(clientId);
expect(originalTicket.clientFk).toEqual(address.clientFk);
expect(refundTicket.clientFk).toEqual(clientId);
expect(refundTicket.clientFk).toEqual(address.clientFk);
expect(claim.clientFk).toEqual(clientId);
expect(claim.clientFk).toEqual(address.clientFk);
expect(originalTicket.nickname).toEqual(address.nickname);
expect(refundTicket.nickname).toEqual(address.nickname);
});
it('should be thrown an error if the new address is not belong to the client', async() => {
const addressFk = 1;
try {
await models.Ticket.transferClient(ctx, originalTicketId, clientId, addressFk, options);
fail('Expected an error to be thrown, but none was thrown.');
} catch (e) {
expect(e.message).toEqual('The address does not belong to the client');
}
});
}); });

View File

@ -1,3 +1,4 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('transferClient', { Self.remoteMethodCtx('transferClient', {
description: 'Transferring ticket to another client', description: 'Transferring ticket to another client',
@ -12,6 +13,10 @@ module.exports = Self => {
arg: 'clientFk', arg: 'clientFk',
type: 'number', type: 'number',
required: true, required: true,
}, {
arg: 'addressFk',
type: 'number',
required: false,
}], }],
http: { http: {
path: `/:id/transferClient`, path: `/:id/transferClient`,
@ -19,7 +24,7 @@ module.exports = Self => {
} }
}); });
Self.transferClient = async(ctx, id, clientFk, options) => { Self.transferClient = async(ctx, id, clientFk, addressFk, options) => {
const models = Self.app.models; const models = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx; let tx;
@ -43,10 +48,12 @@ module.exports = Self => {
const client = await models.Client.findById(clientFk, const client = await models.Client.findById(clientFk,
{fields: ['id', 'defaultAddressFk']}, myOptions); {fields: ['id', 'defaultAddressFk']}, myOptions);
const address = await models.Address.findById(client.defaultAddressFk, const address = await models.Address.findById(addressFk ? addressFk : client.defaultAddressFk,
{fields: ['id', 'nickname']}, myOptions); {fields: ['id', 'nickname', 'clientFk']}, myOptions);
const attributes = {clientFk, addressFk: client.defaultAddressFk, nickname: address.nickname}; if (address.clientFk !== clientFk) throw new UserError('The address does not belong to the client');
const attributes = {clientFk, addressFk: address.id, nickname: address.nickname};
const tickets = []; const tickets = [];
const ticketIds = []; const ticketIds = [];

View File

@ -73,6 +73,11 @@ module.exports = Self => {
type: 'String', type: 'String',
description: 'The user email', description: 'The user email',
http: {source: 'query'} http: {source: 'query'}
},
{
arg: 'myTeam',
type: 'boolean',
description: 'Whether to show only tickets for the current logged user team (currently user tickets)'
} }
], ],
returns: { returns: {
@ -85,10 +90,21 @@ module.exports = Self => {
} }
}); });
Self.filter = async(ctx, filter) => { Self.filter = async(ctx, filter, options) => {
let conn = Self.dataSource.connector; const userId = ctx.req.accessToken.userId;
const conn = Self.dataSource.connector;
const models = Self.app.models;
const args = ctx.args;
let myTeamIds = [];
const myOptions = {};
let where = buildFilter(ctx.args, (param, value) => { if (typeof options == 'object')
Object.assign(myOptions, options);
if (args.myTeam != null)
myTeamIds = await models.Worker.myTeam(userId);
const where = buildFilter(ctx.args, (param, value) => {
switch (param) { switch (param) {
case 'search': case 'search':
return /^\d+$/.test(value) return /^\d+$/.test(value)
@ -117,12 +133,17 @@ module.exports = Self => {
return {'u.name': {like: `%${value}%`}}; return {'u.name': {like: `%${value}%`}};
case 'email': case 'email':
return {'eu.email': {like: `%${value}%`}}; return {'eu.email': {like: `%${value}%`}};
case 'myTeam':
if (value)
return {'c.salesPersonFk': {inq: myTeamIds}};
else
return {'c.salesPersonFk': {nin: myTeamIds}};
} }
}); });
filter = mergeFilters(ctx.args.filter, {where}); filter = mergeFilters(filter, {where});
let stmts = []; const stmts = [];
let stmt; let stmt;
stmt = new ParameterizedSQL( stmt = new ParameterizedSQL(
@ -145,11 +166,12 @@ module.exports = Self => {
LEFT JOIN account.emailUser eu ON eu.userFk = u.id` LEFT JOIN account.emailUser eu ON eu.userFk = u.id`
); );
stmt.merge(conn.makeSuffix(filter)); stmt.merge(conn.makeWhere(filter.where));
let itemsIndex = stmts.push(stmt) - 1; stmts.push(stmt);
let sql = ParameterizedSQL.join(stmts, ';'); const itemsIndex = stmts.push(stmt) - 1;
let result = await conn.executeStmt(sql); const sql = ParameterizedSQL.join(stmts, ';');
return itemsIndex === 0 ? result : result[itemsIndex]; const result = await conn.executeStmt(sql, myOptions);
return result[itemsIndex];
}; };
}; };

View File

@ -0,0 +1,43 @@
module.exports = Self => {
Self.remoteMethod('myTeam', {
description: 'Return the members of the user team',
accessType: 'READ',
accepts: [{
arg: 'userId',
type: 'string',
required: true
}],
returns: {
type: 'string',
root: true
},
http: {
path: `/myTeam`,
verb: 'GET'
}
});
Self.myTeam = async(userId, options) => {
const models = Self.app.models;
const myOptions = {};
const teamMembersId = [];
if (typeof options == 'object')
Object.assign(myOptions, options);
const worker = await models.Worker.findById(userId, {
include: {
relation: 'collegues'
}
}, myOptions);
const collegues = worker.collegues() || [];
for (let collegue of collegues)
teamMembersId.push(collegue.collegueFk);
if (teamMembersId.length == 0)
teamMembersId.push(userId);
return teamMembersId;
};
};

View File

@ -1,25 +1,69 @@
const models = require('vn-loopback/server/server').models;
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('worker filter()', () => { describe('worker filter()', () => {
it('should return 1 result filtering by id', async() => { const ctx = beforeAll.getCtx();
let result = await app.models.Worker.filter({args: {filter: {}, search: 1}});
expect(result.length).toEqual(1); it('should return 1 result filtering by id', async() => {
expect(result[0].id).toEqual(1); const tx = await models.Worker.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {};
const args = {search: 1};
ctx.args = args;
let result = await app.models.Worker.filter(ctx, filter, options);
expect(result.length).toEqual(1);
expect(result[0].id).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should return 1 result filtering by string', async() => { it('should return 1 result filtering by string', async() => {
let result = await app.models.Worker.filter({args: {filter: {}, search: 'administrativeNick'}}); const tx = await models.Worker.beginTransaction({});
expect(result.length).toEqual(1); try {
expect(result[0].id).toEqual(5); const options = {transaction: tx};
const filter = {};
const args = {search: 'administrativeNick'};
ctx.args = args;
let result = await app.models.Worker.filter(ctx, filter, options);
expect(result.length).toEqual(1);
expect(result[0].id).toEqual(5);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should return 2 results filtering by name', async() => { it('should return 2 result filtering by name', async() => {
let result = await app.models.Worker.filter({args: {filter: {}, firstName: 'agency'}}); const tx = await models.Worker.beginTransaction({});
expect(result.length).toEqual(2); try {
expect(result[0].nickname).toEqual('agencyNick'); const options = {transaction: tx};
expect(result[1].nickname).toEqual('agencyBossNick'); const filter = {};
const args = {firstName: 'agency'};
ctx.args = args;
let result = await app.models.Worker.filter(ctx, filter, options);
expect(result[0].nickname).toEqual('agencyNick');
expect(result[1].nickname).toEqual('agencyBossNick');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
}); });

View File

@ -21,6 +21,7 @@ module.exports = Self => {
require('../methods/worker/isAuthorized')(Self); require('../methods/worker/isAuthorized')(Self);
require('../methods/worker/setPassword')(Self); require('../methods/worker/setPassword')(Self);
require('../methods/worker/getAvailablePda')(Self); require('../methods/worker/getAvailablePda')(Self);
require('../methods/worker/myTeam')(Self);
Self.validateAsync('fi', tinIsValid, { Self.validateAsync('fi', tinIsValid, {
message: 'Invalid TIN' message: 'Invalid TIN'

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-back", "name": "salix-back",
"version": "25.04.0", "version": "25.06.0",
"author": "Verdnatura Levante SL", "author": "Verdnatura Levante SL",
"description": "Salix backend", "description": "Salix backend",
"license": "GPL-3.0", "license": "GPL-3.0",