Merge branch '4707-test-workerTimeControl' of https://gitea.verdnatura.es/verdnatura/salix into 4707-test-workerTimeControl
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Carlos Andrés 2023-10-23 16:58:58 +02:00
commit 2fa2e7f099
40 changed files with 532 additions and 425 deletions

View File

@ -26,15 +26,14 @@ module.exports = Self => {
Self.sendCheckingPresence = async(ctx, recipientId, message) => { Self.sendCheckingPresence = async(ctx, recipientId, message) => {
if (!recipientId) return false; if (!recipientId) return false;
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const sender = await models.VnUser.findById(userId, {fields: ['id']}); const sender = await models.VnUser.findById(userId, {fields: ['id']});
const recipient = await models.VnUser.findById(recipientId, null); const recipient = await models.VnUser.findById(recipientId, null);
// Prevent sending messages to yourself // Prevent sending messages to yourself
if (recipientId == userId) return false; if (recipientId == userId) return false;
if (!recipient) if (!recipient)
throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`); throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`);

View File

@ -26,7 +26,7 @@ module.exports = Self => {
Self.getTickets = async(ctx, id, print, options) => { Self.getTickets = async(ctx, id, print, options) => {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const $t = ctx.req.__; const $t = ctx.req.__;
const myOptions = {}; const myOptions = {};
@ -36,7 +36,6 @@ module.exports = Self => {
myOptions.userId = userId; myOptions.userId = userId;
const promises = []; const promises = [];
const [tickets] = await Self.rawSql(`CALL vn.collection_getTickets(?)`, [id], myOptions); const [tickets] = await Self.rawSql(`CALL vn.collection_getTickets(?)`, [id], myOptions);
const sales = await Self.rawSql(` const sales = await Self.rawSql(`
SELECT s.ticketFk, SELECT s.ticketFk,
@ -86,24 +85,19 @@ module.exports = Self => {
if (tickets && tickets.length) { if (tickets && tickets.length) {
for (const ticket of tickets) { for (const ticket of tickets) {
const ticketId = ticket.ticketFk; const ticketId = ticket.ticketFk;
// SEND ROCKET
if (ticket.observaciones != '') { if (ticket.observaciones != '') {
for (observation of ticket.observaciones.split(' ')) { for (observation of ticket.observaciones.split(' ')) {
if (['#', '@'].includes(observation.charAt(0))) { if (['#', '@'].includes(observation.charAt(0))) {
promises.push(Self.app.models.Chat.send(ctx, observation, promises.push(Self.app.models.Chat.send(ctx, observation,
$t('The ticket is in preparation', { $t('The ticket is in preparation', {
ticketId: ticketId, ticketId: ticketId,
ticketUrl: `${origin}/#!/ticket/${ticketId}/summary`, ticketUrl: `${url}ticket/${ticketId}/summary`,
salesPersonId: ticket.salesPersonFk salesPersonId: ticket.salesPersonFk
}))); })));
} }
} }
} }
// SET COLLECTION
if (sales && sales.length) { if (sales && sales.length) {
// GET BARCODES
const barcodes = await Self.rawSql(` const barcodes = await Self.rawSql(`
SELECT s.id saleFk, b.code, c.id SELECT s.id saleFk, b.code, c.id
FROM vn.sale s FROM vn.sale s
@ -114,13 +108,10 @@ module.exports = Self => {
WHERE s.ticketFk = ? WHERE s.ticketFk = ?
AND tr.landed >= util.VN_CURDATE() - INTERVAL 1 YEAR`, AND tr.landed >= util.VN_CURDATE() - INTERVAL 1 YEAR`,
[ticketId], myOptions); [ticketId], myOptions);
// BINDINGS
ticket.sales = []; ticket.sales = [];
for (const sale of sales) { for (const sale of sales) {
if (sale.ticketFk === ticketId) { if (sale.ticketFk === ticketId) {
sale.Barcodes = []; sale.Barcodes = [];
if (barcodes && barcodes.length) { if (barcodes && barcodes.length) {
for (const barcode of barcodes) { for (const barcode of barcodes) {
if (barcode.saleFk === sale.saleFk) { if (barcode.saleFk === sale.saleFk) {
@ -131,7 +122,6 @@ module.exports = Self => {
} }
} }
} }
ticket.sales.push(sale); ticket.sales.push(sale);
} }
} }
@ -140,7 +130,6 @@ module.exports = Self => {
} }
} }
await Promise.all(promises); await Promise.all(promises);
return collection; return collection;
}; };
}; };

View File

@ -18,7 +18,14 @@ describe('setSaleQuantity()', () => {
it('should change quantity sale', async() => { it('should change quantity sale', async() => {
const tx = await models.Ticket.beginTransaction({}); const tx = await models.Ticket.beginTransaction({});
spyOn(models.Item, 'getVisibleAvailable').and.returnValue((new Promise(resolve => resolve({available: 100})))); spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT 100 as available;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
try { try {
const options = {transaction: tx}; const options = {transaction: tx};

View File

@ -0,0 +1,30 @@
module.exports = Self => {
Self.remoteMethod('getUrl', {
description: 'Returns the colling app name',
accessType: 'READ',
accepts: [
{
arg: 'app',
type: 'string',
required: false
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/getUrl`,
verb: 'get'
}
});
Self.getUrl = async(appName = 'salix') => {
const {url} = await Self.app.models.Url.findOne({
where: {
appName,
enviroment: process.env.NODE_ENV || 'development'
}
});
return url;
};
};

View File

@ -7,17 +7,14 @@ module.exports = Self => {
Self.observe('before save', async function(ctx) { Self.observe('before save', async function(ctx) {
if (!ctx.isNewInstance) return; if (!ctx.isNewInstance) return;
let {message} = ctx.instance; let {message} = ctx.instance;
if (!message) return; if (!message) return;
const parts = message.match(/(?<=\[)[a-zA-Z0-9_\-+!@#$%^&*()={};':"\\|,.<>/?\s]*(?=])/g); const parts = message.match(/(?<=\[)[a-zA-Z0-9_\-+!@#$%^&*()={};':"\\|,.<>/?\s]*(?=])/g);
if (!parts) return; if (!parts) return;
const replacedParts = parts.map(part => { const replacedParts = parts.map(part => {
return part.replace(/[!$%^&*()={};':"\\,.<>/?]/g, ''); return part.replace(/[!$%^&*()={};':"\\,.<>/?]/g, '');
}); });
for (const [index, part] of parts.entries()) for (const [index, part] of parts.entries())
message = message.replace(part, replacedParts[index]); message = message.replace(part, replacedParts[index]);

3
back/models/url.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/url/getUrl')(Self);
};

View File

@ -1,5 +1,4 @@
const vnModel = require('vn-loopback/common/models/vn-model'); const vnModel = require('vn-loopback/common/models/vn-model');
const LoopBackContext = require('loopback-context');
const {Email} = require('vn-print'); const {Email} = require('vn-print');
module.exports = function(Self) { module.exports = function(Self) {
@ -90,11 +89,7 @@ module.exports = function(Self) {
}; };
Self.on('resetPasswordRequest', async function(info) { Self.on('resetPasswordRequest', async function(info) {
const loopBackContext = LoopBackContext.getCurrentContext(); const url = await Self.app.models.Url.getUrl();
const httpCtx = {req: loopBackContext.active};
const httpRequest = httpCtx.req.http.req;
const headers = httpRequest.headers;
const origin = headers.origin;
const defaultHash = '/reset-password?access_token=$token$'; const defaultHash = '/reset-password?access_token=$token$';
const recoverHashes = { const recoverHashes = {
@ -110,7 +105,7 @@ module.exports = function(Self) {
const params = { const params = {
recipient: info.email, recipient: info.email,
lang: user.lang, lang: user.lang,
url: origin + '/#!' + recoverHash url: url.slice(0, -1) + recoverHash
}; };
const options = Object.assign({}, info.options); const options = Object.assign({}, info.options);
@ -177,46 +172,4 @@ module.exports = function(Self) {
Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls = Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls =
Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls
.filter(acl => acl.property != 'changePassword'); .filter(acl => acl.property != 'changePassword');
// FIXME: https://redmine.verdnatura.es/issues/5761
// Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => {
// if (!ctx.args || !ctx.args.data.email) return;
// const loopBackContext = LoopBackContext.getCurrentContext();
// const httpCtx = {req: loopBackContext.active};
// const httpRequest = httpCtx.req.http.req;
// const headers = httpRequest.headers;
// const origin = headers.origin;
// const url = origin.split(':');
// class Mailer {
// async send(verifyOptions, cb) {
// const params = {
// url: verifyOptions.verifyHref,
// recipient: verifyOptions.to,
// lang: ctx.req.getLocale()
// };
// const email = new Email('email-verify', params);
// email.send();
// cb(null, verifyOptions.to);
// }
// }
// const options = {
// type: 'email',
// to: instance.email,
// from: {},
// redirect: `${origin}/#!/account/${instance.id}/basic-data?emailConfirmed`,
// template: false,
// mailer: new Mailer,
// host: url[1].split('/')[2],
// port: url[2],
// protocol: url[0],
// user: Self
// };
// await instance.verify(options);
// });
}; };

View File

@ -53,7 +53,8 @@ describe('ticket ticketCalculateClon()', () => {
expect(result[orderIndex][0].ticketFk).toBeGreaterThan(newestTicketIdInFixtures); expect(result[orderIndex][0].ticketFk).toBeGreaterThan(newestTicketIdInFixtures);
}); });
it('should add the ticket to the order containing the original ticket and generate landed value if it was null', async() => { it('should add the ticket to the order containing the original ' +
'ticket and generate landed value if it was null', async() => {
let stmts = []; let stmts = [];
let stmt; let stmt;

View File

@ -134,14 +134,6 @@ describe('Ticket Edit sale path', () => {
await page.accessToSection('ticket.card.sale'); await page.accessToSection('ticket.card.sale');
}); });
it('should try to add a higher quantity value and then receive an error', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleQuantityCell);
await page.type(selectors.ticketSales.firstSaleQuantity, '11\u000d');
const message = await page.waitForSnackbar();
expect(message.text).toContain('The new quantity should be smaller than the old one');
});
it('should remove 1 from the first sale quantity', async() => { it('should remove 1 from the first sale quantity', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleQuantityCell); await page.waitToClick(selectors.ticketSales.firstSaleQuantityCell);
await page.waitForSelector(selectors.ticketSales.firstSaleQuantity); await page.waitForSelector(selectors.ticketSales.firstSaleQuantity);

View File

@ -13,6 +13,6 @@
"principalType": "ROLE", "principalType": "ROLE",
"principalId": "$everyone", "principalId": "$everyone",
"permission": "ALLOW" "permission": "ALLOW"
} }
] ]
} }

View File

@ -14,7 +14,7 @@
"The default consignee can not be unchecked": "The default consignee can not be unchecked", "The default consignee can not be unchecked": "The default consignee can not be unchecked",
"Enter an integer different to zero": "Enter an integer different to zero", "Enter an integer different to zero": "Enter an integer different to zero",
"Package cannot be blank": "Package cannot be blank", "Package cannot be blank": "Package cannot be blank",
"The new quantity should be smaller than the old one": "The new quantity should be smaller than the old one", "The price of the item changed": "The price of the item changed",
"The sales of this ticket can't be modified": "The sales of this ticket can't be modified", "The sales of this ticket can't be modified": "The sales of this ticket can't be modified",
"Cannot check Equalization Tax in this NIF/CIF": "Cannot check Equalization Tax in this NIF/CIF", "Cannot check Equalization Tax in this NIF/CIF": "Cannot check Equalization Tax in this NIF/CIF",
"You can't create an order for a frozen client": "You can't create an order for a frozen client", "You can't create an order for a frozen client": "You can't create an order for a frozen client",
@ -191,4 +191,4 @@
"Booking completed": "Booking complete", "Booking completed": "Booking complete",
"The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation", "The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation",
"You can only add negative amounts in refund tickets": "You can only add negative amounts in refund tickets" "You can only add negative amounts in refund tickets": "You can only add negative amounts in refund tickets"
} }

View File

@ -35,7 +35,7 @@
"The grade must be an integer greater than or equal to zero": "El grade debe ser un entero mayor o igual a cero", "The grade must be an integer greater than or equal to zero": "El grade debe ser un entero mayor o igual a cero",
"Sample type cannot be blank": "El tipo de plantilla no puede quedar en blanco", "Sample type cannot be blank": "El tipo de plantilla no puede quedar en blanco",
"Description cannot be blank": "Se debe rellenar el campo de texto", "Description cannot be blank": "Se debe rellenar el campo de texto",
"The new quantity should be smaller than the old one": "La nueva cantidad debe de ser menor que la anterior", "The price of the item changed": "El precio del artículo cambió",
"The value should not be greater than 100%": "El valor no debe de ser mayor de 100%", "The value should not be greater than 100%": "El valor no debe de ser mayor de 100%",
"The value should be a number": "El valor debe ser un numero", "The value should be a number": "El valor debe ser un numero",
"This order is not editable": "Esta orden no se puede modificar", "This order is not editable": "Esta orden no se puede modificar",

View File

@ -43,9 +43,8 @@ module.exports = Self => {
Self.claimPickupEmail = async ctx => { Self.claimPickupEmail = async ctx => {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const args = Object.assign({}, ctx.args); const args = Object.assign({}, ctx.args);
const params = { const params = {
@ -70,9 +69,8 @@ module.exports = Self => {
const message = $t('Claim pickup order sent', { const message = $t('Claim pickup order sent', {
claimId: args.id, claimId: args.id,
clientName: claim.client().name, clientName: claim.client().name,
claimUrl: `${origin}/#!/claim/${args.id}/summary`, claimUrl: `${url}claim/${args.id}/summary`,
}); });
const salesPersonId = claim.client().salesPersonFk; const salesPersonId = claim.client().salesPersonFk;
if (salesPersonId) if (salesPersonId)
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);

View File

@ -94,13 +94,13 @@ module.exports = Self => {
const salesPerson = ticket.client().salesPersonUser(); const salesPerson = ticket.client().salesPersonUser();
if (salesPerson) { if (salesPerson) {
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const message = $t('Created claim', { const message = $t('Created claim', {
claimId: newClaim.id, claimId: newClaim.id,
ticketId: ticketId, ticketId: ticketId,
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`, ticketUrl: `${url}ticket/${ticketId}/sale`,
claimUrl: `${origin}/#!/claim/${newClaim.id}/summary`, claimUrl: `${url}claim/${newClaim.id}/summary`,
changes: changesMade changes: changesMade
}); });
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);

View File

@ -56,15 +56,15 @@ module.exports = Self => {
const salesPerson = sale.ticket().client().salesPersonUser(); const salesPerson = sale.ticket().client().salesPersonUser();
if (salesPerson) { if (salesPerson) {
const nickname = address && address.nickname || destination.description; const nickname = address && address.nickname || destination.description;
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const message = $t('Sent units from ticket', { const message = $t('Sent units from ticket', {
quantity: sale.quantity, quantity: sale.quantity,
concept: sale.concept, concept: sale.concept,
itemId: sale.itemFk, itemId: sale.itemFk,
ticketId: sale.ticketFk, ticketId: sale.ticketFk,
nickname: nickname, nickname: nickname,
ticketUrl: `${origin}/#!/ticket/${sale.ticketFk}/sale`, ticketUrl: `${url}ticket/${sale.ticketFk}/sale`,
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary` itemUrl: `${url}item/${sale.itemFk}/summary`
}); });
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
} }

View File

@ -2,7 +2,9 @@ const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('Update Claim', () => { describe('Update Claim', () => {
let url;
beforeAll(async() => { beforeAll(async() => {
url = await app.models.Url.getUrl();
const activeCtx = { const activeCtx = {
accessToken: {userId: 9}, accessToken: {userId: 9},
http: { http: {
@ -29,7 +31,6 @@ describe('Update Claim', () => {
it(`should throw an error as the user doesn't have rights`, async() => { it(`should throw an error as the user doesn't have rights`, async() => {
const tx = await app.models.Claim.beginTransaction({}); const tx = await app.models.Claim.beginTransaction({});
let error; let error;
try { try {
@ -77,7 +78,7 @@ describe('Update Claim', () => {
const ctx = { const ctx = {
req: { req: {
accessToken: {userId: claimManagerId}, accessToken: {userId: claimManagerId},
headers: {origin: 'http://localhost'} headers: {origin: url}
}, },
args: { args: {
observation: 'valid observation', observation: 'valid observation',
@ -118,7 +119,7 @@ describe('Update Claim', () => {
const ctx = { const ctx = {
req: { req: {
accessToken: {userId: claimManagerId}, accessToken: {userId: claimManagerId},
headers: {origin: 'http://localhost'} headers: {origin: url}
}, },
args: { args: {
observation: 'valid observation', observation: 'valid observation',

View File

@ -91,16 +91,16 @@ module.exports = Self => {
// When hasToPickUp has been changed // When hasToPickUp has been changed
if (salesPerson && changedHasToPickUp && updatedClaim.hasToPickUp) if (salesPerson && changedHasToPickUp && updatedClaim.hasToPickUp)
notifyPickUp(ctx, salesPerson.id, claim); await notifyPickUp(ctx, salesPerson.id, claim);
// When claimState has been changed // When claimState has been changed
if (args.claimStateFk) { if (args.claimStateFk) {
const newState = await models.ClaimState.findById(args.claimStateFk, null, myOptions); const newState = await models.ClaimState.findById(args.claimStateFk, null, myOptions);
if (newState.hasToNotify) { if (newState.hasToNotify) {
if (newState.code == 'incomplete') if (newState.code == 'incomplete')
notifyStateChange(ctx, salesPerson.id, claim, newState.code); await notifyStateChange(ctx, salesPerson.id, claim, newState.code);
if (newState.code == 'canceled') if (newState.code == 'canceled')
notifyStateChange(ctx, claim.workerFk, claim, newState.code); await notifyStateChange(ctx, claim.workerFk, claim, newState.code);
} }
} }
@ -115,26 +115,26 @@ module.exports = Self => {
async function notifyStateChange(ctx, workerId, claim, state) { async function notifyStateChange(ctx, workerId, claim, state) {
const models = Self.app.models; const models = Self.app.models;
const origin = ctx.req.headers.origin; const url = await models.Url.getUrl();
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const message = $t(`Claim state has changed to ${state}`, { const message = $t(`Claim state has changed to ${state}`, {
claimId: claim.id, claimId: claim.id,
clientName: claim.client().name, clientName: claim.client().name,
claimUrl: `${origin}/#!/claim/${claim.id}/summary` claimUrl: `${url}claim/${claim.id}/summary`
}); });
await models.Chat.sendCheckingPresence(ctx, workerId, message); await models.Chat.sendCheckingPresence(ctx, workerId, message);
} }
async function notifyPickUp(ctx, workerId, claim) { async function notifyPickUp(ctx, workerId, claim) {
const origin = ctx.req.headers.origin;
const models = Self.app.models; const models = Self.app.models;
const url = await models.Url.getUrl();
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const message = $t('Claim will be picked', { const message = $t('Claim will be picked', {
claimId: claim.id, claimId: claim.id,
clientName: claim.client().name, clientName: claim.client().name,
claimUrl: `${origin}/#!/claim/${claim.id}/summary` claimUrl: `${url}claim/${claim.id}/summary`
}); });
await models.Chat.sendCheckingPresence(ctx, workerId, message); await models.Chat.sendCheckingPresence(ctx, workerId, message);
} }

View File

@ -250,7 +250,12 @@ module.exports = Self => {
const loopBackContext = LoopBackContext.getCurrentContext(); const loopBackContext = LoopBackContext.getCurrentContext();
const accessToken = {req: loopBackContext.active.accessToken}; const accessToken = {req: loopBackContext.active.accessToken};
const editVerifiedDataWithoutTaxDataChecked = models.ACL.checkAccessAcl(accessToken, 'Client', 'editVerifiedDataWithoutTaxDataCheck', 'WRITE'); const editVerifiedDataWithoutTaxDataChecked = models.ACL.checkAccessAcl(
accessToken,
'Client',
'editVerifiedDataWithoutTaxDataCheck',
'WRITE'
);
const hasChanges = orgData && changes; const hasChanges = orgData && changes;
const isTaxDataChecked = hasChanges && (changes.isTaxDataChecked || orgData.isTaxDataChecked); const isTaxDataChecked = hasChanges && (changes.isTaxDataChecked || orgData.isTaxDataChecked);
@ -263,7 +268,9 @@ module.exports = Self => {
const sageTransactionTypeChanged = hasChanges && orgData.sageTransactionTypeFk != sageTransactionType; const sageTransactionTypeChanged = hasChanges && orgData.sageTransactionTypeFk != sageTransactionType;
const cantEditVerifiedData = isTaxDataCheckedChanged && !editVerifiedDataWithoutTaxDataChecked; const cantEditVerifiedData = isTaxDataCheckedChanged && !editVerifiedDataWithoutTaxDataChecked;
const cantChangeSageData = (sageTaxTypeChanged || sageTransactionTypeChanged) && !editVerifiedDataWithoutTaxDataChecked; const cantChangeSageData = (sageTaxTypeChanged ||
sageTransactionTypeChanged
) && !editVerifiedDataWithoutTaxDataChecked;
if (cantEditVerifiedData || cantChangeSageData) if (cantEditVerifiedData || cantChangeSageData)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
@ -346,8 +353,7 @@ module.exports = Self => {
const httpCtx = {req: loopBackContext.active}; const httpCtx = {req: loopBackContext.active};
const httpRequest = httpCtx.req.http.req; const httpRequest = httpCtx.req.http.req;
const $t = httpRequest.__; const $t = httpRequest.__;
const headers = httpRequest.headers; const url = await Self.app.models.Url.getUrl();
const origin = headers.origin;
const salesPersonId = instance.salesPersonFk; const salesPersonId = instance.salesPersonFk;
@ -366,7 +372,7 @@ module.exports = Self => {
await email.send(); await email.send();
} }
const fullUrl = `${origin}/#!/client/${instance.id}/billing-data`; const fullUrl = `${url}client/${instance.id}/billing-data`;
const message = $t('Changed client paymethod', { const message = $t('Changed client paymethod', {
clientId: instance.id, clientId: instance.id,
clientName: instance.name, clientName: instance.name,
@ -389,8 +395,7 @@ module.exports = Self => {
const httpCtx = {req: loopBackContext.active}; const httpCtx = {req: loopBackContext.active};
const httpRequest = httpCtx.req.http.req; const httpRequest = httpCtx.req.http.req;
const $t = httpRequest.__; const $t = httpRequest.__;
const headers = httpRequest.headers; const url = await Self.app.models.Url.getUrl();
const origin = headers.origin;
const models = Self.app.models; const models = Self.app.models;
let previousWorker = {name: $t('None')}; let previousWorker = {name: $t('None')};
@ -411,7 +416,7 @@ module.exports = Self => {
currentWorker.name = worker && worker.user().nickname; currentWorker.name = worker && worker.user().nickname;
} }
const fullUrl = `${origin}/#!/client/${client.id}/basic-data`; const fullUrl = `${url}client/${client.id}/basic-data`;
const message = $t('Client assignment has changed', { const message = $t('Client assignment has changed', {
clientId: client.id, clientId: client.id,
clientName: client.name, clientName: client.name,

View File

@ -57,8 +57,8 @@ module.exports = function(Self) {
const httpRequest = httpCtx.req.http.req; const httpRequest = httpCtx.req.http.req;
const $t = httpRequest.__; const $t = httpRequest.__;
const origin = httpRequest.headers.origin; const url = await Self.app.models.Url.getUrl();
const fullPath = `${origin}/#!/client/${client.id}/credit-insurance/index`; const fullPath = `${url}client/${client.id}/credit-insurance/index`;
const message = $t('MESSAGE_INSURANCE_CHANGE', { const message = $t('MESSAGE_INSURANCE_CHANGE', {
clientId: client.id, clientId: client.id,
clientName: client.name, clientName: client.name,

View File

@ -59,10 +59,10 @@ module.exports = Self => {
}; };
await Self.invoiceEmail(ctx, ref); await Self.invoiceEmail(ctx, ref);
} catch (err) { } catch (err) {
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const message = ctx.req.__('Mail not sent', { const message = ctx.req.__('Mail not sent', {
clientId: client.id, clientId: client.id,
clientUrl: `${origin}/#!/claim/${id}/summary` clientUrl: `${url}claim/${id}/summary`
}); });
const salesPersonId = client.salesPersonFk; const salesPersonId = client.salesPersonFk;

View File

@ -1,5 +1,3 @@
let UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('deleteSales', { Self.remoteMethodCtx('deleteSales', {
description: 'Deletes the selected sales', description: 'Deletes the selected sales',
@ -70,11 +68,11 @@ module.exports = Self => {
const salesPerson = ticket.client().salesPersonUser(); const salesPerson = ticket.client().salesPersonUser();
if (salesPerson) { if (salesPerson) {
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const message = $t('Deleted sales from ticket', { const message = $t('Deleted sales from ticket', {
ticketId: ticketId, ticketId: ticketId,
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`, ticketUrl: `${url}ticket/${ticketId}/sale`,
deletions: deletions deletions: deletions
}); });
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions);

View File

@ -1,6 +1,3 @@
let UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('reserve', { Self.remoteMethodCtx('reserve', {
description: 'Change the state of a ticket', description: 'Change the state of a ticket',
@ -65,7 +62,8 @@ module.exports = Self => {
promises.push(reservedSale); promises.push(reservedSale);
changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${$t('State')}: ${$t(oldState)} ➔ *${$t(newState)}*`; changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity})
${$t('State')}: ${$t(oldState)} *${$t(newState)}*`;
} }
} }
@ -87,11 +85,11 @@ module.exports = Self => {
const salesPerson = ticket.client().salesPersonUser(); const salesPerson = ticket.client().salesPersonUser();
if (salesPerson) { if (salesPerson) {
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const message = $t('Changed sale reserved state', { const message = $t('Changed sale reserved state', {
ticketId: ticketId, ticketId: ticketId,
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`, ticketUrl: `${url}ticket/${ticketId}/sale`,
changes: changesMade changes: changesMade
}); });
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions);

View File

@ -1,239 +0,0 @@
/* eslint max-len: ["error", { "code": 150 }]*/
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('sale updateQuantity()', () => {
const ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
function getActiveCtx(userId) {
return {
active: {
accessToken: {userId},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
}
};
}
it('should throw an error if the quantity is greater than it should be', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(models.Item, 'getVisibleAvailable').and.returnValue((new Promise(resolve => resolve({available: 100}))));
const tx = await models.Sale.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await models.Sale.updateQuantity(ctx, 17, 31, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error('The new quantity should be smaller than the old one'));
});
it('should add quantity if the quantity is greater than it should be and is role advanced', async() => {
const saleId = 17;
const buyerId = 35;
const ctx = {
req: {
accessToken: {userId: buyerId},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const tx = await models.Sale.beginTransaction({});
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(buyerId));
spyOn(models.Item, 'getVisibleAvailable').and.returnValue((new Promise(resolve => resolve({available: 100}))));
try {
const options = {transaction: tx};
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
expect(isRoleAdvanced).toEqual(true);
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(originalLine.quantity).toEqual(30);
const newQuantity = originalLine.quantity + 1;
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should update the quantity of a given sale current line', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9));
const tx = await models.Sale.beginTransaction({});
const saleId = 25;
const newQuantity = 4;
try {
const options = {transaction: tx};
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(originalLine.quantity).toEqual(20);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should throw an error if the quantity is negative and it is not a refund ticket', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const saleId = 17;
const newQuantity = -10;
const tx = await models.Sale.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error('You can only add negative amounts in refund tickets'));
});
it('should update a negative quantity when is a ticket refund', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9));
const tx = await models.Sale.beginTransaction({});
const saleId = 13;
const newQuantity = -10;
try {
const options = {transaction: tx};
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should throw an error if the quantity is less than the minimum quantity of the item', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = minQuantity - 1;
let error;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error('The amount cannot be less than the minimum'));
});
it('should change quantity if has minimum quantity and new quantity is equal than item available', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = minQuantity - 1;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Item, 'getVisibleAvailable').and.returnValue((new Promise(resolve => resolve({available: newQuantity}))));
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,5 +1,3 @@
let UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('updatePrice', { Self.remoteMethodCtx('updatePrice', {
description: 'Changes the price of a sale', description: 'Changes the price of a sale',
@ -100,7 +98,7 @@ module.exports = Self => {
const salesPerson = sale.ticket().client().salesPersonUser(); const salesPerson = sale.ticket().client().salesPersonUser();
if (salesPerson) { if (salesPerson) {
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const message = $t('Changed sale price', { const message = $t('Changed sale price', {
ticketId: sale.ticket().id, ticketId: sale.ticket().id,
itemId: sale.itemFk, itemId: sale.itemFk,
@ -108,8 +106,8 @@ module.exports = Self => {
quantity: sale.quantity, quantity: sale.quantity,
oldPrice: oldPrice, oldPrice: oldPrice,
newPrice: newPrice, newPrice: newPrice,
ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`, ticketUrl: `${url}ticket/${sale.ticket().id}/sale`,
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary` itemUrl: `${url}item/${sale.itemFk}/summary`
}); });
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions);
} }

View File

@ -68,16 +68,17 @@ module.exports = Self => {
const salesPerson = sale.ticket().client().salesPersonUser(); const salesPerson = sale.ticket().client().salesPersonUser();
if (salesPerson) { if (salesPerson) {
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const message = $t('Changed sale quantity', { const message = $t('Changed sale quantity', {
ticketId: sale.ticket().id, ticketId: sale.ticket().id,
itemId: sale.itemFk, itemId: sale.itemFk,
concept: sale.concept, concept: sale.concept,
oldQuantity: oldQuantity, oldQuantity: oldQuantity,
newQuantity: newQuantity, newQuantity: newQuantity,
ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`, ticketUrl: `${url}ticket/${sale.ticket().id}/sale`,
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary` itemUrl: `${url}item/${sale.itemFk}/summary`
}); });
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions);
} }

View File

@ -84,7 +84,7 @@ module.exports = Self => {
const query = `CALL vn.sale_calculateComponent(?, NULL)`; const query = `CALL vn.sale_calculateComponent(?, NULL)`;
await Self.rawSql(query, [sale.id], myOptions); await Self.rawSql(query, [sale.id], myOptions);
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const requesterId = request.requesterFk; const requesterId = request.requesterFk;
const message = $t('Bought units from buy request', { const message = $t('Bought units from buy request', {
@ -92,8 +92,8 @@ module.exports = Self => {
concept: sale.concept, concept: sale.concept,
itemId: sale.itemFk, itemId: sale.itemFk,
ticketId: sale.ticketFk, ticketId: sale.ticketFk,
url: `${origin}/#!/ticket/${sale.ticketFk}/summary`, url: `${url}ticket/${sale.ticketFk}/summary`,
urlItem: `${origin}/#!/item/${sale.itemFk}/summary` urlItem: `${url}item/${sale.itemFk}/summary`
}); });
await models.Chat.sendCheckingPresence(ctx, requesterId, message, myOptions); await models.Chat.sendCheckingPresence(ctx, requesterId, message, myOptions);

View File

@ -50,12 +50,12 @@ module.exports = Self => {
const request = await Self.app.models.TicketRequest.findById(ctx.args.id, null, myOptions); const request = await Self.app.models.TicketRequest.findById(ctx.args.id, null, myOptions);
await request.updateAttributes(params, myOptions); await request.updateAttributes(params, myOptions);
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const requesterId = request.requesterFk; const requesterId = request.requesterFk;
const message = $t('Deny buy request', { const message = $t('Deny buy request', {
ticketId: request.ticketFk, ticketId: request.ticketFk,
url: `${origin}/#!/ticket/${request.ticketFk}/request/index`, url: `${url}ticket/${request.ticketFk}/request/index`,
observation: params.response observation: params.response
}); });

View File

@ -83,11 +83,11 @@ module.exports = Self => {
const salesPerson = ticket.client().salesPersonUser(); const salesPerson = ticket.client().salesPersonUser();
if (salesPerson) { if (salesPerson) {
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const message = $t('Added sale to ticket', { const message = $t('Added sale to ticket', {
ticketId: id, ticketId: id,
ticketUrl: `${origin}/#!/ticket/${id}/sale`, ticketUrl: `${url}ticket/${id}/sale`,
addition: addition addition: addition
}); });
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);

View File

@ -237,7 +237,7 @@ module.exports = Self => {
const salesPersonId = originalTicket.client().salesPersonFk; const salesPersonId = originalTicket.client().salesPersonFk;
if (salesPersonId) { if (salesPersonId) {
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
let changesMade = ''; let changesMade = '';
for (let change in newProperties) { for (let change in newProperties) {
@ -249,7 +249,7 @@ module.exports = Self => {
const message = $t('Changed this data from the ticket', { const message = $t('Changed this data from the ticket', {
ticketId: args.id, ticketId: args.id,
ticketUrl: `${origin}/#!/ticket/${args.id}/sale`, ticketUrl: `${url}ticket/${args.id}/sale`,
changes: changesMade changes: changesMade
}); });
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);

View File

@ -25,7 +25,7 @@ module.exports = Self => {
Self.merge = async(ctx, tickets, options) => { Self.merge = async(ctx, tickets, options) => {
const httpRequest = ctx.req; const httpRequest = ctx.req;
const $t = httpRequest.__; const $t = httpRequest.__;
const origin = httpRequest.headers.origin; const url = await Self.app.models.Url.getUrl();
const models = Self.app.models; const models = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx; let tx;
@ -40,8 +40,8 @@ module.exports = Self => {
try { try {
for (let ticket of tickets) { for (let ticket of tickets) {
const originFullPath = `${origin}/#!/ticket/${ticket.originId}/summary`; const originFullPath = `${url}ticket/${ticket.originId}/summary`;
const destinationFullPath = `${origin}/#!/ticket/${ticket.destinationId}/summary`; const destinationFullPath = `${url}ticket/${ticket.destinationId}/summary`;
const message = $t('Ticket merged', { const message = $t('Ticket merged', {
originDated: dateUtil.toString(new Date(ticket.originShipped)), originDated: dateUtil.toString(new Date(ticket.originShipped)),
destinationDated: dateUtil.toString(new Date(ticket.destinationShipped)), destinationDated: dateUtil.toString(new Date(ticket.destinationShipped)),

View File

@ -48,7 +48,7 @@ module.exports = Self => {
description: `The route id filter` description: `The route id filter`
}], }],
returns: { returns: {
type: 'number', type: 'object',
root: true root: true
}, },
http: { http: {

View File

@ -48,10 +48,10 @@ module.exports = Self => {
// Send notification to salesPerson // Send notification to salesPerson
const salesPersonId = ticket.client().salesPersonFk; const salesPersonId = ticket.client().salesPersonFk;
if (salesPersonId) { if (salesPersonId) {
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const message = $t(`I have restored the ticket id`, { const message = $t(`I have restored the ticket id`, {
id: id, id: id,
url: `${origin}/#!/ticket/${id}/summary` url: `${url}ticket/${id}/summary`
}); });
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);
} }

View File

@ -119,10 +119,10 @@ module.exports = Self => {
// Send notification to salesPerson // Send notification to salesPerson
const salesPersonUser = ticket.client().salesPersonUser(); const salesPersonUser = ticket.client().salesPersonUser();
if (salesPersonUser && sales.length) { if (salesPersonUser && sales.length) {
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const message = $t(`I have deleted the ticket id`, { const message = $t(`I have deleted the ticket id`, {
id: id, id: id,
url: `${origin}/#!/ticket/${id}/summary` url: `${url}ticket/${id}/summary`
}); });
await models.Chat.send(ctx, `@${salesPersonUser.name}`, message); await models.Chat.send(ctx, `@${salesPersonUser.name}`, message);
} }
@ -146,7 +146,8 @@ module.exports = Self => {
JOIN vn.sectorCollection sc ON sc.id = scsg.sectorCollectionFk JOIN vn.sectorCollection sc ON sc.id = scsg.sectorCollectionFk
JOIN vn.saleGroupDetail sgd ON sgd.saleGroupFk = sg.id JOIN vn.saleGroupDetail sgd ON sgd.saleGroupFk = sg.id
JOIN vn.sale s ON s.id = sgd.saleFk JOIN vn.sale s ON s.id = sgd.saleFk
WHERE s.ticketFk = ?;`, [ticket.id], myOptions); WHERE s.ticketFk = ?;`, [ticket.id], myOptions
);
if (tx) await tx.commit(); if (tx) await tx.commit();

View File

@ -165,11 +165,10 @@ module.exports = Self => {
const salesPerson = ticket.client().salesPersonUser(); const salesPerson = ticket.client().salesPersonUser();
if (salesPerson) { if (salesPerson) {
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const message = $t('Changed sale discount', { const message = $t('Changed sale discount', {
ticketId: id, ticketId: id,
ticketUrl: `${origin}/#!/ticket/${id}/sale`, ticketUrl: `${url}ticket/${id}/sale`,
changes: changesMade changes: changesMade
}); });
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions);

View File

@ -31,11 +31,14 @@ module.exports = Self => {
const ticketId = changes?.ticketFk || instance?.ticketFk; const ticketId = changes?.ticketFk || instance?.ticketFk;
const itemId = changes?.itemFk || instance?.itemFk; const itemId = changes?.itemFk || instance?.itemFk;
const oldQuantity = instance?.quantity ?? null;
const quantityAdded = newQuantity - oldQuantity;
const isReduction = oldQuantity && newQuantity <= oldQuantity;
const ticket = await models.Ticket.findById( const ticket = await models.Ticket.findById(
ticketId, ticketId,
{ {
fields: ['id', 'clientFk', 'warehouseFk', 'shipped'], fields: ['id', 'clientFk', 'warehouseFk', 'addressFk', 'agencyModeFk', 'shipped', 'landed'],
include: { include: {
relation: 'client', relation: 'client',
scope: { scope: {
@ -65,28 +68,50 @@ module.exports = Self => {
fields: ['family', 'minQuantity'], fields: ['family', 'minQuantity'],
where: {id: itemId}, where: {id: itemId},
}, ctx.options); }, ctx.options);
if (item.family == 'EMB') return; if (item.family == 'EMB') return;
const itemInfo = await models.Item.getVisibleAvailable( if (await models.ACL.checkAccessAcl(ctx, 'Sale', 'isInPreparing', '*')) return;
itemId,
ticket.warehouseFk,
ticket.shipped,
ctx.options
);
const oldQuantity = instance?.quantity ?? null; await models.Sale.rawSql(`CALL catalog_calcFromItem(?,?,?,?)`, [
const quantityAdded = newQuantity - oldQuantity; ticket.landed,
if (itemInfo.available < quantityAdded) ticket.addressFk,
ticket.agencyModeFk,
itemId
],
ctx.options);
const [itemInfo] = await models.Sale.rawSql(`SELECT available FROM tmp.ticketCalculateItem`, null, ctx.options);
const available = itemInfo?.available;
if ((!isReduction && !available) || available < quantityAdded)
throw new UserError(`This item is not available`); throw new UserError(`This item is not available`);
if (await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*')) return; if (await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*')) return;
if (newQuantity < item.minQuantity && itemInfo.available != newQuantity) if (newQuantity < item.minQuantity && newQuantity != available)
throw new UserError('The amount cannot be less than the minimum'); throw new UserError('The amount cannot be less than the minimum');
if (!ctx.isNewInstance && newQuantity > oldQuantity) if (ctx.isNewInstance || isReduction) return;
throw new UserError('The new quantity should be smaller than the old one');
const [saleGrouping] = await models.Sale.rawSql(`
SELECT t.price newPrice
FROM tmp.ticketComponentPrice t
ORDER BY (t.grouping <= ?) DESC, t.grouping ASC
LIMIT 1`,
[quantityAdded],
ctx.options);
await models.Sale.rawSql(`
DROP TEMPORARY TABLE IF EXISTS
tmp.ticketCalculateItem,
tmp.ticketComponentPrice,
tmp.ticketComponent,
tmp.ticketLot,
tmp.zoneGetShipped;
`, null, ctx.options);
if (!saleGrouping?.newPrice || saleGrouping.newPrice > instance.price)
throw new UserError('The price of the item changed');
}); });
}; };

View File

@ -0,0 +1,361 @@
/* eslint max-len: ["error", { "code": 150 }]*/
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('sale model ', () => {
const ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
function getActiveCtx(userId) {
return {
active: {
accessToken: {userId},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
}
};
}
describe('quantity field ', () => {
it('should add quantity if the quantity is greater than it should be and is role advanced', async() => {
const saleId = 17;
const buyerId = 35;
const ctx = {
req: {
accessToken: {userId: buyerId},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const tx = await models.Sale.beginTransaction({});
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(buyerId));
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT 100 as available;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
try {
const options = {transaction: tx};
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
expect(isRoleAdvanced).toEqual(true);
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(originalLine.quantity).toEqual(30);
const newQuantity = originalLine.quantity + 1;
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should update the quantity of a given sale current line', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9));
const tx = await models.Sale.beginTransaction({});
const saleId = 25;
const newQuantity = 4;
try {
const options = {transaction: tx};
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(originalLine.quantity).toEqual(20);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should throw an error if the quantity is negative and it is not a refund ticket', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const saleId = 17;
const newQuantity = -10;
const tx = await models.Sale.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error('You can only add negative amounts in refund tickets'));
});
it('should update a negative quantity when is a ticket refund', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9));
const tx = await models.Sale.beginTransaction({});
const saleId = 13;
const newQuantity = -10;
try {
const options = {transaction: tx};
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should throw an error if the quantity is less than the minimum quantity of the item', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = minQuantity - 1;
let error;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT 100 as available;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error('The amount cannot be less than the minimum'));
});
it('should change quantity if has minimum quantity and new quantity is equal than item available', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = minQuantity - 1;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT ${newQuantity} as available;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
describe('newPrice', () => {
it('should increase quantity if you have enough available and the new price is the same as the previous one', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = 31;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 7.07 as price;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should increase quantity when the new price is lower than the previous one', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = 31;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 1 as price;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should throw error when increase quantity and the new price is higher than the previous one', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = 31;
let error;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 100000 as price;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error('The price of the item changed'));
});
});
});
});

View File

@ -16,8 +16,8 @@ class Controller extends SearchPanel {
this.$http.get('ItemPackingTypes', {filter}).then(res => { this.$http.get('ItemPackingTypes', {filter}).then(res => {
for (let ipt of res.data) { for (let ipt of res.data) {
itemPackingTypes.push({ itemPackingTypes.push({
code: ipt.code, description: this.$t(ipt.description),
description: this.$t(ipt.description) code: ipt.code
}); });
} }
this.itemPackingTypes = itemPackingTypes; this.itemPackingTypes = itemPackingTypes;

View File

@ -163,26 +163,16 @@ export default class Controller extends Section {
return {'futureId': value}; return {'futureId': value};
case 'liters': case 'liters':
return {'liters': value}; return {'liters': value};
case 'lines':
return {'lines': value};
case 'futureLiters': case 'futureLiters':
return {'futureLiters': value}; return {'futureLiters': value};
case 'lines':
return {'lines': value};
case 'futureLines': case 'futureLines':
return {'futureLines': value}; return {'futureLines': value};
case 'ipt': case 'ipt':
return {or: return {'ipt': {like: `%${value}%`}};
[
{'ipt': {like: `%${value}%`}},
{'ipt': null}
]
};
case 'futureIpt': case 'futureIpt':
return {or: return {'futureIpt': {like: `%${value}%`}};
[
{'futureIpt': {like: `%${value}%`}},
{'futureIpt': null}
]
};
case 'totalWithVat': case 'totalWithVat':
return {'totalWithVat': value}; return {'totalWithVat': value};
case 'futureTotalWithVat': case 'futureTotalWithVat':

View File

@ -109,13 +109,13 @@ module.exports = Self => {
const absenceType = await models.AbsenceType.findById(args.absenceTypeId, null, myOptions); const absenceType = await models.AbsenceType.findById(args.absenceTypeId, null, myOptions);
const account = await models.VnUser.findById(userId, null, myOptions); const account = await models.VnUser.findById(userId, null, myOptions);
const subordinated = await models.VnUser.findById(id, null, myOptions); const subordinated = await models.VnUser.findById(id, null, myOptions);
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const body = $t('Created absence', { const body = $t('Created absence', {
author: account.nickname, author: account.nickname,
employee: subordinated.nickname, employee: subordinated.nickname,
absenceType: absenceType.name, absenceType: absenceType.name,
dated: formatDate(args.dated), dated: formatDate(args.dated),
workerUrl: `${origin}/#!/worker/${id}/calendar` workerUrl: `${url}worker/${id}/calendar`
}); });
await models.Mail.create({ await models.Mail.create({
subject: $t('Absence change notification on the labour calendar'), subject: $t('Absence change notification on the labour calendar'),

View File

@ -60,13 +60,13 @@ module.exports = Self => {
const absenceType = await models.AbsenceType.findById(absence.dayOffTypeFk, null, myOptions); const absenceType = await models.AbsenceType.findById(absence.dayOffTypeFk, null, myOptions);
const account = await models.VnUser.findById(userId, null, myOptions); const account = await models.VnUser.findById(userId, null, myOptions);
const subordinated = await models.VnUser.findById(labour.workerFk, null, myOptions); const subordinated = await models.VnUser.findById(labour.workerFk, null, myOptions);
const origin = ctx.req.headers.origin; const url = await Self.app.models.Url.getUrl();
const body = $t('Deleted absence', { const body = $t('Deleted absence', {
author: account.nickname, author: account.nickname,
employee: subordinated.nickname, employee: subordinated.nickname,
absenceType: absenceType.name, absenceType: absenceType.name,
dated: formatDate(absence.dated), dated: formatDate(absence.dated),
workerUrl: `${origin}/#!/worker/${id}/calendar` workerUrl: `${url}worker/${id}/calendar`
}); });
await models.Mail.create({ await models.Mail.create({
subject: $t('Absence change notification on the labour calendar'), subject: $t('Absence change notification on the labour calendar'),