Merge branch 'dev' into 7821-entryIsBooked

This commit is contained in:
Javi Gallego 2024-10-28 14:46:31 +01:00
commit a432133c18
25 changed files with 244 additions and 29 deletions

View File

@ -0,0 +1,12 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`itemType_afterDelete`
AFTER DELETE ON `itemType`
FOR EACH ROW
BEGIN
INSERT INTO itemTypeLog
SET `action` = 'delete',
`changedModel` = 'ItemType',
`changedModelId` = OLD.id,
`userFk` = account.myUser_getId();
END$$
DELIMITER ;

View File

@ -0,0 +1,8 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`itemType_beforeInsert`
BEFORE INSERT ON `itemType`
FOR EACH ROW
BEGIN
SET NEW.editorFk = account.myUser_getId();
END$$
DELIMITER ;

View File

@ -3,6 +3,7 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`itemType_beforeUpdate`
BEFORE UPDATE ON `itemType` BEFORE UPDATE ON `itemType`
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
SET NEW.editorFk = account.myUser_getId();
IF NEW.itemPackingTypeFk = '' THEN IF NEW.itemPackingTypeFk = '' THEN
SET NEW.itemPackingTypeFk = NULL; SET NEW.itemPackingTypeFk = NULL;

View File

@ -3,7 +3,7 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`productionConfig_afterD
AFTER DELETE ON `productionConfig` AFTER DELETE ON `productionConfig`
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
INSERT INTO productionConfig INSERT INTO productionConfigLog
SET `action` = 'delete', SET `action` = 'delete',
`changedModel` = 'ProductionConfig', `changedModel` = 'ProductionConfig',
`changedModelId` = OLD.id, `changedModelId` = OLD.id,

View File

@ -0,0 +1,27 @@
ALTER TABLE vn.itemType
ADD editorFk int(10) unsigned DEFAULT NULL NULL,
ADD CONSTRAINT itemType_user_FK FOREIGN KEY (editorFk) REFERENCES account.`user`(id);
CREATE TABLE `vn`.`itemTypeLog` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`originFk` int(11) DEFAULT NULL,
`userFk` int(10) unsigned DEFAULT NULL,
`action` set('insert','update','delete') NOT NULL,
`creationDate` timestamp NULL DEFAULT current_timestamp(),
`description` text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
`changedModel` enum('ItemType') NOT NULL DEFAULT 'ItemType',
`oldInstance` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`oldInstance`)),
`newInstance` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`newInstance`)),
`changedModelId` int(11) NOT NULL,
`changedModelValue` varchar(45) DEFAULT NULL,
`summaryId` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `itemTypeLogUserFk_idx` (`userFk`),
KEY `itemTypeLog_changedModel` (`changedModel`,`changedModelId`,`creationDate`),
KEY `itemTypeLog_originFk` (`originFk`,`creationDate`),
KEY `itemTypeLog_creationDate_IDX` (`creationDate` DESC) USING BTREE,
CONSTRAINT `itemTypeLogUserFk` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci `PAGE_COMPRESSED`=1;
INSERT IGNORE INTO salix.ACL (model,property,principalId)
VALUES ('ItemTypeLog','find','employee');

View File

@ -0,0 +1,3 @@
ALTER TABLE vn.itemType
ADD CONSTRAINT itemType_itemPackingType_FK FOREIGN KEY (itemPackingTypeFk)
REFERENCES vn.itemPackingType(code) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -244,6 +244,6 @@
"There are tickets for this area, delete them first": "There are tickets for this area, delete them first", "There are tickets for this area, delete them first": "There are tickets for this area, delete them first",
"Payment method is required": "Payment method is required", "Payment method is required": "Payment method is required",
"You do not have permission to modify the booked field": "You do not have permission to modify the booked field", "You do not have permission to modify the booked field": "You do not have permission to modify the booked field",
"null": "null", "Invalid or expired verification code": "Invalid or expired verification code",
"Invalid or expired verification code": "Invalid or expired verification code" "ticketLostExpedition": "The ticket [{{ticketId}}]({{{ticketUrl}}}) has the following lost expedition:{{ expeditionId }}"
} }

View File

@ -387,9 +387,6 @@
"There are tickets for this area, delete them first": "Hay tickets para esta sección, borralos primero", "There are tickets for this area, delete them first": "Hay tickets para esta sección, borralos primero",
"There is no company associated with that warehouse": "No hay ninguna empresa asociada a ese almacén", "There is no company associated with that warehouse": "No hay ninguna empresa asociada a ese almacén",
"You do not have permission to modify the booked field": "No tienes permisos para modificar el campo contabilizada", "You do not have permission to modify the booked field": "No tienes permisos para modificar el campo contabilizada",
"Entry 99 is not editable": "Entry 99 is not editable", "ticketLostExpedition": "El ticket [{{ticketId}}]({{{ticketUrl}}}) tiene la siguiente expedición perdida:{{ expeditionId }}",
"Entry 9 is not editable": "Entry 9 is not editable", "The web user's email already exists": "El correo del usuario web ya existe"
"Entry must have lines to be marked booked": "Entry must have lines to be marked booked", }
"Entry 10 is not editable": "Entry 10 is not editable",
"Entry 7 is not editable": "Entry 7 is not editable"
}

View File

@ -123,8 +123,8 @@
"Added sale to ticket": "J'ai ajouté la ligne suivante au ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}", "Added sale to ticket": "J'ai ajouté la ligne suivante au ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}",
"Changed sale discount": "J'ai changé le rabais des lignes suivantes du ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Changed sale discount": "J'ai changé le rabais des lignes suivantes du ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Created claim": "J'ai créé la réclamation [{{claimId}}]({{{claimUrl}}}) des lignes suivantes du ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Created claim": "J'ai créé la réclamation [{{claimId}}]({{{claimUrl}}}) des lignes suivantes du ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Changed sale price": " le prix de [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* du ticket [{{ticketId}}]({{{ticketUrl}}})",, "Changed sale price": " le prix de [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* du ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale quantity": "J'ai changé {{changes}} du ticket [{{ticketId}}]({{{ticketUrl}}})", "Changed sale quantity": "J'ai changé {{changes}} du ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changes in sales": "la quantité de {{itemId}} {{concept}} de {{oldQuantity}} ➔ {{newQuantity}}", "Changes in sales": "la quantité de {{itemId}} {{concept}} de {{oldQuantity}} ➔ {{newQuantity}}",
"State": "État", "State": "État",
"regular": "normal", "regular": "normal",
@ -364,5 +364,7 @@
"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}}",
"The web user's email already exists": "L'email de l'internaute existe déjà"
} }

View File

@ -124,7 +124,7 @@
"Changed sale discount": "Desconto da venda alterado no ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Changed sale discount": "Desconto da venda alterado no ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Created claim": "Reclamação criada [{{claimId}}]({{{claimUrl}}}) no ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Created claim": "Reclamação criada [{{claimId}}]({{{claimUrl}}}) no ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Changed sale price": "Preço da venda alterado para [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* no ticket [{{ticketId}}]({{{ticketUrl}}})", "Changed sale price": "Preço da venda alterado para [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* no ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale quantity": "Quantidade da venda alterada para {{changes}} no ticket [{{ticketId}}]({{{ticketUrl}}})", "Changed sale quantity": "Quantidade da venda alterada para {{changes}} no ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changes in sales": " [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}* ", "Changes in sales": " [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}* ",
"State": "Estado", "State": "Estado",
"regular": "normal", "regular": "normal",
@ -361,8 +361,9 @@
"It was not able to create the invoice": "Não foi possível criar a fatura", "It was not able to create the invoice": "Não foi possível criar a fatura",
"The invoices have been created but the PDFs could not be generated": "Foi faturado, mas o PDF não pôde ser gerado", "The invoices have been created but the PDFs could not be generated": "Foi faturado, mas o PDF não pôde ser gerado",
"It has been invoiced but the PDF of refund not be generated": "Foi faturado mas não foi gerado o PDF do reembolso", "It has been invoiced but the PDF of refund not be generated": "Foi faturado mas não foi gerado o PDF do reembolso",
"Original invoice not found": "Fatura original não encontrada", "Original invoice not found": "Fatura original não encontrada",
"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",
"You do not have permission to modify the booked field": "Você não tem permissão para modificar o campo contabilizado" "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."
} }

View File

@ -1,3 +1,4 @@
/* eslint max-len: ["error", { "code": 150 }]*/
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = function(Self) { module.exports = function(Self) {
@ -98,6 +99,8 @@ module.exports = function(Self) {
return client; return client;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
if (e.message && e.message.includes(`Email already exists`)) throw new UserError(`The web user's email already exists`);
throw e; throw e;
} }
}; };

View File

@ -38,13 +38,23 @@ module.exports = Self => {
type: 'integer', type: 'integer',
description: 'Type id', description: 'Type id',
}, },
{
arg: 'producerFk',
type: 'integer',
description: 'Producer id',
},
{
arg: 'instrastatFk',
type: 'string',
description: 'intrastat id',
},
{ {
arg: 'isActive', arg: 'isActive',
type: 'boolean', type: 'boolean',
description: 'Whether the item is or not active', description: 'Whether the item is or not active',
}, },
{ {
arg: 'buyerFk', arg: 'workerFk',
type: 'integer', type: 'integer',
description: 'The buyer of the item', description: 'The buyer of the item',
}, },
@ -126,14 +136,16 @@ module.exports = Self => {
return {'i.stemMultiplier': value}; return {'i.stemMultiplier': value};
case 'categoryFk': case 'categoryFk':
return {'ic.id': value}; return {'ic.id': value};
case 'buyerFk': case 'workerFk':
return {'it.workerFk': value}; return {'it.workerFk': value};
case 'producerFk':
return {'pr.id': value};
case 'supplierFk': case 'supplierFk':
return {'s.id': value}; return {'s.id': value};
case 'origin': case 'origin':
return {'ori.code': value}; return {'ori.code': value};
case 'intrastat': case 'intrastatFk':
return {'intr.description': value}; return {'i.intrastatFk': value};
case 'landed': case 'landed':
return {'lb.landed': value}; return {'lb.landed': value};
} }
@ -172,6 +184,7 @@ module.exports = Self => {
u.name AS userName, u.name AS userName,
ori.code AS origin, ori.code AS origin,
ic.name AS category, ic.name AS category,
i.intrastatFk,
intr.description AS intrastat, intr.description AS intrastat,
b.grouping, b.grouping,
b.packing, b.packing,

View File

@ -86,7 +86,7 @@ describe('item filter()', () => {
try { try {
const filter = {}; const filter = {};
const ctx = {args: {filter: filter, buyerFk: 16}, req: {accessToken: {userId: 1}}}; const ctx = {args: {filter: filter, workerFk: 16}, req: {accessToken: {userId: 1}}};
const result = await models.Item.filter(ctx, filter, options); const result = await models.Item.filter(ctx, filter, options);
expect(result.length).toEqual(2); expect(result.length).toEqual(2);

View File

@ -47,6 +47,9 @@
"ItemType": { "ItemType": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ItemTypeLog": {
"dataSource": "vn"
},
"ItemTypeTag": { "ItemTypeTag": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,9 @@
{
"name": "ItemTypeLog",
"base": "Log",
"options": {
"mysql": {
"table": "itemTypeLog"
}
}
}

View File

@ -29,6 +29,12 @@
}, },
"isLaid": { "isLaid": {
"type": "boolean" "type": "boolean"
},
"maxRefs": {
"type": "string"
},
"isFragile": {
"type": "boolean"
} }
}, },
"relations": { "relations": {

View File

@ -38,5 +38,13 @@
"model": "Sector", "model": "Sector",
"foreignKey": "sectorFk" "foreignKey": "sectorFk"
} }
},
"scope": {
"include": {
"relation": "sector",
"scope": {
"fields": ["id", "description"]
}
}
} }
} }

View File

@ -2,6 +2,7 @@ const models = require('vn-loopback/server/server').models;
describe('expeditionState addExpeditionState()', () => { describe('expeditionState addExpeditionState()', () => {
const ctx = beforeAll.getCtx(); const ctx = beforeAll.getCtx();
beforeAll.mockLoopBackContext();
it('should update the expedition states', async() => { it('should update the expedition states', async() => {
const tx = await models.ExpeditionState.beginTransaction({}); const tx = await models.ExpeditionState.beginTransaction({});
try { try {

View File

@ -48,7 +48,7 @@ module.exports = Self => {
CALL vn.sale_recalcComponent(null); CALL vn.sale_recalcComponent(null);
DROP TEMPORARY TABLE tmp.recalculateSales;`; DROP TEMPORARY TABLE tmp.recalculateSales;`;
const recalculation = await Self.rawSql(query, salesIds, myOptions); const recalculation = await Self.rawSql(query, [salesIds], myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();

View File

@ -85,6 +85,25 @@ describe('sale updatePrice()', () => {
} }
}); });
it('should check if priceFixed has changed', async() => {
const tx = await models.Sale.beginTransaction({});
try {
const options = {transaction: tx};
const price = 3;
const beforeUpdate = await models.Sale.findById(saleId, null, options);
await models.Sale.updatePrice(ctx, saleId, price, options);
const afterUpdate = await models.Sale.findById(saleId, null, options);
expect(beforeUpdate.priceFixed).not.toEqual(afterUpdate.priceFixed);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should set price as a decimal number and check the sale has the mana component changing the salesPersonMana', async() => { it('should set price as a decimal number and check the sale has the mana component changing the salesPersonMana', async() => {
const tx = await models.Sale.beginTransaction({}); const tx = await models.Sale.beginTransaction({});

View File

@ -91,7 +91,21 @@ module.exports = Self => {
value: componentValue value: componentValue
}, myOptions); }, myOptions);
} }
await sale.updateAttributes({price: newPrice}, myOptions);
const [priceFixed] = await Self.rawSql(`
SELECT SUM(value) value
FROM sale s
JOIN saleComponent sc ON sc.saleFk = s.id
JOIN component c ON c.id = sc.componentFk
JOIN componentType ct ON ct.id = c.typeFk
WHERE ct.isBase
AND s.id = ?
`, [id], myOptions);
await sale.updateAttributes({
price: newPrice,
priceFixed: priceFixed.value
}, myOptions);
await Self.rawSql('CALL vn.manaSpellersRequery(?)', [userId], myOptions); await Self.rawSql('CALL vn.manaSpellersRequery(?)', [userId], myOptions);
await Self.rawSql('CALL vn.ticket_recalc(?, NULL)', [sale.ticketFk], myOptions); await Self.rawSql('CALL vn.ticket_recalc(?, NULL)', [sale.ticketFk], myOptions);

View File

@ -1,4 +1,66 @@
const LoopBackContext = require('loopback-context');
module.exports = function(Self) { module.exports = function(Self) {
require('../methods/expedition-state/filter')(Self); require('../methods/expedition-state/filter')(Self);
require('../methods/expedition-state/addExpeditionState')(Self); require('../methods/expedition-state/addExpeditionState')(Self);
Self.observe('before save', async ctx => {
const models = Self.app.models;
const changes = ctx.data || ctx.instance;
const instance = ctx.currentInstance;
const loopBackContext = LoopBackContext.getCurrentContext();
const httpCtx = {req: loopBackContext.active};
const httpRequest = httpCtx.req.http.req;
const $t = httpRequest.__;
const myOptions = {};
if (ctx.options && ctx.options.transaction)
myOptions.transaction = ctx.options.transaction;
const newStateType = changes?.typeFk;
if (newStateType == null) return;
const expeditionId = changes?.expeditionFk || instance?.expeditionFk;
const {code} = await models.ExpeditionStateType.findById(
newStateType,
{
fields: ['code']
},
myOptions);
if (code !== 'LOST') return;
const dataExpedition = await models.Expedition.findById(
expeditionId, {
fields: ['ticketFk'],
include: [{
relation: 'ticket',
scope: {
fields: ['clientFk'],
include: [{
relation: 'client',
scope: {
fields: ['name', 'salesPersonFk']
}
}]
}
}],
}, myOptions);
const salesPersonFk = dataExpedition.ticket()?.client()?.salesPersonFk;
if (salesPersonFk) {
const url = await Self.app.models.Url.getUrl();
const fullUrl = `${url}ticket/${dataExpedition.ticketFk}/expedition`;
const message = $t('ticketLostExpedition', {
ticketId: dataExpedition.ticketFk,
expeditionId: expeditionId,
url: fullUrl
});
await models.Chat.sendCheckingPresence(httpCtx, salesPersonFk, message);
}
});
}; };

View File

@ -19,7 +19,8 @@
"type": "number" "type": "number"
}, },
"typeFk": { "typeFk": {
"type": "number" "type": "number",
"required": true
}, },
"userFk": { "userFk": {
"type": "number" "type": "number"

View File

@ -28,6 +28,9 @@
"discount": { "discount": {
"type": "number" "type": "number"
}, },
"priceFixed": {
"type": "number"
},
"reserved": { "reserved": {
"type": "boolean" "type": "boolean"
}, },

View File

@ -204,6 +204,15 @@
] ]
}, },
"summary": { "summary": {
"fields": [
"id",
"firstName",
"lastName",
"bossFk",
"sex",
"phone",
"mobileExtension"
],
"include": [ "include": [
{ {
"relation": "user", "relation": "user",
@ -247,10 +256,21 @@
} }
}, },
{ {
"relation": "boss" "relation": "boss",
"scope": {
"fields": [
"id",
"name"
]
}
}, },
{ {
"relation": "client" "relation": "client",
"scope": {
"fields": [
"id"
]
}
}, },
{ {
"relation": "sip", "relation": "sip",
@ -272,14 +292,16 @@
"fields": [ "fields": [
"id", "id",
"fiDueDate", "fiDueDate",
"sex",
"seniority", "seniority",
"fi", "fi",
"isFreelance", "isFreelance",
"isSsDiscounted", "isSsDiscounted",
"hasMachineryAuthorized", "hasMachineryAuthorized",
"isDisable", "isDisable",
"birth" "birth",
"educationLevelFk",
"originCountryFk",
"maritalStatus"
] ]
} }
} }