Merge branch 'test' of https://gitea.verdnatura.es/verdnatura/salix into dev
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Alex Moreno 2023-10-23 08:31:37 +02:00
commit dc00b85bec
33 changed files with 475 additions and 460 deletions

View File

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

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

View File

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

View File

@ -1,5 +1,4 @@
const vnModel = require('vn-loopback/common/models/vn-model');
const LoopBackContext = require('loopback-context');
const {Email} = require('vn-print');
const ForbiddenError = require('vn-loopback/util/forbiddenError');
@ -92,11 +91,7 @@ module.exports = function(Self) {
};
Self.on('resetPasswordRequest', async function(info) {
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 = await Self.app.models.Url.getUrl();
const defaultHash = '/reset-password?access_token=$token$';
const recoverHashes = {
@ -112,7 +107,7 @@ module.exports = function(Self) {
const params = {
recipient: info.email,
lang: user.lang,
url: origin + '/#!' + recoverHash
url: url.slice(0, -1) + recoverHash
};
const options = Object.assign({}, info.options);

View File

@ -53,7 +53,8 @@ describe('ticket ticketCalculateClon()', () => {
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 stmt;

View File

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

View File

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

View File

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

View File

@ -56,15 +56,15 @@ module.exports = Self => {
const salesPerson = sale.ticket().client().salesPersonUser();
if (salesPerson) {
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', {
quantity: sale.quantity,
concept: sale.concept,
itemId: sale.itemFk,
ticketId: sale.ticketFk,
nickname: nickname,
ticketUrl: `${origin}/#!/ticket/${sale.ticketFk}/sale`,
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary`
ticketUrl: `${url}ticket/${sale.ticketFk}/sale`,
itemUrl: `${url}item/${sale.itemFk}/summary`
});
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');
describe('Update Claim', () => {
let url;
beforeAll(async() => {
url = await app.models.Url.getUrl();
const activeCtx = {
accessToken: {userId: 9},
http: {
@ -29,7 +31,6 @@ describe('Update Claim', () => {
it(`should throw an error as the user doesn't have rights`, async() => {
const tx = await app.models.Claim.beginTransaction({});
let error;
try {
@ -77,7 +78,7 @@ describe('Update Claim', () => {
const ctx = {
req: {
accessToken: {userId: claimManagerId},
headers: {origin: 'http://localhost'}
headers: {origin: url}
},
args: {
observation: 'valid observation',
@ -118,7 +119,7 @@ describe('Update Claim', () => {
const ctx = {
req: {
accessToken: {userId: claimManagerId},
headers: {origin: 'http://localhost'}
headers: {origin: url}
},
args: {
observation: 'valid observation',

View File

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

View File

@ -250,7 +250,12 @@ module.exports = Self => {
const loopBackContext = LoopBackContext.getCurrentContext();
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 isTaxDataChecked = hasChanges && (changes.isTaxDataChecked || orgData.isTaxDataChecked);
@ -263,7 +268,9 @@ module.exports = Self => {
const sageTransactionTypeChanged = hasChanges && orgData.sageTransactionTypeFk != sageTransactionType;
const cantEditVerifiedData = isTaxDataCheckedChanged && !editVerifiedDataWithoutTaxDataChecked;
const cantChangeSageData = (sageTaxTypeChanged || sageTransactionTypeChanged) && !editVerifiedDataWithoutTaxDataChecked;
const cantChangeSageData = (sageTaxTypeChanged ||
sageTransactionTypeChanged
) && !editVerifiedDataWithoutTaxDataChecked;
if (cantEditVerifiedData || cantChangeSageData)
throw new UserError(`You don't have enough privileges`);
@ -346,8 +353,7 @@ module.exports = Self => {
const httpCtx = {req: loopBackContext.active};
const httpRequest = httpCtx.req.http.req;
const $t = httpRequest.__;
const headers = httpRequest.headers;
const origin = headers.origin;
const url = await Self.app.models.Url.getUrl();
const salesPersonId = instance.salesPersonFk;
@ -366,7 +372,7 @@ module.exports = Self => {
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', {
clientId: instance.id,
clientName: instance.name,
@ -389,8 +395,7 @@ module.exports = Self => {
const httpCtx = {req: loopBackContext.active};
const httpRequest = httpCtx.req.http.req;
const $t = httpRequest.__;
const headers = httpRequest.headers;
const origin = headers.origin;
const url = await Self.app.models.Url.getUrl();
const models = Self.app.models;
let previousWorker = {name: $t('None')};
@ -411,7 +416,7 @@ module.exports = Self => {
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', {
clientId: client.id,
clientName: client.name,

View File

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

View File

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

View File

@ -1,5 +1,3 @@
let UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('deleteSales', {
description: 'Deletes the selected sales',
@ -70,11 +68,11 @@ module.exports = Self => {
const salesPerson = ticket.client().salesPersonUser();
if (salesPerson) {
const origin = ctx.req.headers.origin;
const url = await Self.app.models.Url.getUrl();
const message = $t('Deleted sales from ticket', {
ticketId: ticketId,
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`,
ticketUrl: `${url}ticket/${ticketId}/sale`,
deletions: deletions
});
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 => {
Self.remoteMethodCtx('reserve', {
description: 'Change the state of a ticket',
@ -65,7 +62,8 @@ module.exports = Self => {
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();
if (salesPerson) {
const origin = ctx.req.headers.origin;
const url = await Self.app.models.Url.getUrl();
const message = $t('Changed sale reserved state', {
ticketId: ticketId,
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`,
ticketUrl: `${url}ticket/${ticketId}/sale`,
changes: changesMade
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions);

View File

@ -1,359 +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 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

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

View File

@ -68,16 +68,17 @@ module.exports = Self => {
const salesPerson = sale.ticket().client().salesPersonUser();
if (salesPerson) {
const origin = ctx.req.headers.origin;
const url = await Self.app.models.Url.getUrl();
const message = $t('Changed sale quantity', {
ticketId: sale.ticket().id,
itemId: sale.itemFk,
concept: sale.concept,
oldQuantity: oldQuantity,
newQuantity: newQuantity,
ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`,
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary`
ticketUrl: `${url}ticket/${sale.ticket().id}/sale`,
itemUrl: `${url}item/${sale.itemFk}/summary`
});
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)`;
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 message = $t('Bought units from buy request', {
@ -92,8 +92,8 @@ module.exports = Self => {
concept: sale.concept,
itemId: sale.itemFk,
ticketId: sale.ticketFk,
url: `${origin}/#!/ticket/${sale.ticketFk}/summary`,
urlItem: `${origin}/#!/item/${sale.itemFk}/summary`
url: `${url}ticket/${sale.ticketFk}/summary`,
urlItem: `${url}item/${sale.itemFk}/summary`
});
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);
await request.updateAttributes(params, myOptions);
const origin = ctx.req.headers.origin;
const url = await Self.app.models.Url.getUrl();
const requesterId = request.requesterFk;
const message = $t('Deny buy request', {
ticketId: request.ticketFk,
url: `${origin}/#!/ticket/${request.ticketFk}/request/index`,
url: `${url}ticket/${request.ticketFk}/request/index`,
observation: params.response
});

View File

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

View File

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

View File

@ -25,7 +25,7 @@ module.exports = Self => {
Self.merge = async(ctx, tickets, options) => {
const httpRequest = ctx.req;
const $t = httpRequest.__;
const origin = httpRequest.headers.origin;
const url = await Self.app.models.Url.getUrl();
const models = Self.app.models;
const myOptions = {};
let tx;
@ -40,8 +40,8 @@ module.exports = Self => {
try {
for (let ticket of tickets) {
const originFullPath = `${origin}/#!/ticket/${ticket.originId}/summary`;
const destinationFullPath = `${origin}/#!/ticket/${ticket.destinationId}/summary`;
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)),

View File

@ -48,10 +48,10 @@ module.exports = Self => {
// Send notification to salesPerson
const salesPersonId = ticket.client().salesPersonFk;
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`, {
id: id,
url: `${origin}/#!/ticket/${id}/summary`
url: `${url}ticket/${id}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);
}

View File

@ -119,10 +119,10 @@ module.exports = Self => {
// Send notification to salesPerson
const salesPersonUser = ticket.client().salesPersonUser();
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`, {
id: id,
url: `${origin}/#!/ticket/${id}/summary`
url: `${url}ticket/${id}/summary`
});
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.saleGroupDetail sgd ON sgd.saleGroupFk = sg.id
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();

View File

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

View File

@ -69,6 +69,8 @@ module.exports = Self => {
}, ctx.options);
if (item.family == 'EMB') return;
if (await models.ACL.checkAccessAcl(ctx, 'Sale', 'isInPreparing', '*')) return;
await models.Sale.rawSql(`CALL catalog_calcFromItem(?,?,?,?)`, [
ticket.landed,
ticket.addressFk,

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

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