Merge branch 'dev' into 6287-Ticket.create-no-usarlo
gitea/salix/pipeline/head There was a failure building this commit
Details
gitea/salix/pipeline/head There was a failure building this commit
Details
This commit is contained in:
commit
4d82508645
|
@ -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}`);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,6 +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.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};
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
module.exports = function(Self) {
|
||||||
|
Self.remoteMethod('getByUser', {
|
||||||
|
description: 'returns the starred modules for the current user',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [{
|
||||||
|
arg: 'userId',
|
||||||
|
type: 'number',
|
||||||
|
description: 'The user id',
|
||||||
|
required: true,
|
||||||
|
http: {source: 'path'}
|
||||||
|
}],
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:userId/get-by-user`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.getByUser = async userId => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const appNames = ['hedera'];
|
||||||
|
const filter = {
|
||||||
|
fields: ['appName', 'url'],
|
||||||
|
where: {
|
||||||
|
appName: {inq: appNames},
|
||||||
|
environment: process.env.NODE_ENV ?? 'development',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isWorker = await models.Account.findById(userId, {fields: ['id']});
|
||||||
|
if (!isWorker)
|
||||||
|
return models.Url.find(filter);
|
||||||
|
|
||||||
|
appNames.push('salix');
|
||||||
|
return models.Url.find(filter);
|
||||||
|
};
|
||||||
|
};
|
|
@ -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;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,19 @@
|
||||||
|
const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
describe('getByUser()', () => {
|
||||||
|
const worker = 1;
|
||||||
|
const notWorker = 2;
|
||||||
|
it(`should return only hedera url if not is worker`, async() => {
|
||||||
|
const urls = await models.Url.getByUser(notWorker);
|
||||||
|
|
||||||
|
expect(urls.length).toEqual(1);
|
||||||
|
expect(urls[0].appName).toEqual('hedera');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return more than hedera url`, async() => {
|
||||||
|
const urls = await models.Url.getByUser(worker);
|
||||||
|
|
||||||
|
expect(urls.length).toBeGreaterThan(1);
|
||||||
|
expect(urls.find(url => url.appName == 'salix').appName).toEqual('salix');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,39 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('updateUser', {
|
||||||
|
description: 'Update user data',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'integer',
|
||||||
|
description: 'The user id',
|
||||||
|
required: true,
|
||||||
|
http: {source: 'path'}
|
||||||
|
}, {
|
||||||
|
arg: 'name',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user name',
|
||||||
|
}, {
|
||||||
|
arg: 'nickname',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user nickname',
|
||||||
|
}, {
|
||||||
|
arg: 'email',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user email'
|
||||||
|
}, {
|
||||||
|
arg: 'lang',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user lang'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
http: {
|
||||||
|
path: `/:id/update-user`,
|
||||||
|
verb: 'PATCH'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.updateUser = async(ctx, id, name, nickname, email, lang) => {
|
||||||
|
await Self.userSecurity(ctx, id);
|
||||||
|
await Self.upsertWithWhere({id}, {name, nickname, email, lang});
|
||||||
|
};
|
||||||
|
};
|
|
@ -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]);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const models = require('vn-loopback/server/server').models;
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
const ForbiddenError = require('vn-loopback/util/forbiddenError');
|
||||||
|
|
||||||
describe('loopback model VnUser', () => {
|
describe('loopback model VnUser', () => {
|
||||||
it('should return true if the user has the given role', async() => {
|
it('should return true if the user has the given role', async() => {
|
||||||
|
@ -12,4 +13,42 @@ describe('loopback model VnUser', () => {
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('userSecurity', () => {
|
||||||
|
const itManagementId = 115;
|
||||||
|
const hrId = 37;
|
||||||
|
const employeeId = 1;
|
||||||
|
|
||||||
|
it('should check if you are the same user', async() => {
|
||||||
|
const ctx = {options: {accessToken: {userId: employeeId}}};
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check for higher privileges', async() => {
|
||||||
|
const ctx = {options: {accessToken: {userId: itManagementId}}};
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check if you have medium privileges and the user email is not verified', async() => {
|
||||||
|
const ctx = {options: {accessToken: {userId: hrId}}};
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if you have medium privileges and the users email is verified', async() => {
|
||||||
|
const tx = await models.VnUser.beginTransaction({});
|
||||||
|
const ctx = {options: {accessToken: {userId: hrId}}};
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
const userToUpdate = await models.VnUser.findById(1, null, options);
|
||||||
|
userToUpdate.updateAttribute('emailVerified', 1, options);
|
||||||
|
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId, options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (error) {
|
||||||
|
await tx.rollback();
|
||||||
|
|
||||||
|
expect(error).toEqual(new ForbiddenError());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
require('../methods/url/getByUser')(Self);
|
||||||
|
require('../methods/url/getUrl')(Self);
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
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');
|
||||||
|
const ForbiddenError = require('vn-loopback/util/forbiddenError');
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
module.exports = function(Self) {
|
module.exports = function(Self) {
|
||||||
vnModel(Self);
|
vnModel(Self);
|
||||||
|
@ -12,6 +13,7 @@ module.exports = function(Self) {
|
||||||
require('../methods/vn-user/privileges')(Self);
|
require('../methods/vn-user/privileges')(Self);
|
||||||
require('../methods/vn-user/validate-auth')(Self);
|
require('../methods/vn-user/validate-auth')(Self);
|
||||||
require('../methods/vn-user/renew-token')(Self);
|
require('../methods/vn-user/renew-token')(Self);
|
||||||
|
require('../methods/vn-user/update-user')(Self);
|
||||||
|
|
||||||
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
|
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
|
||||||
|
|
||||||
|
@ -90,11 +92,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 +108,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);
|
||||||
|
@ -178,45 +176,75 @@ 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
|
||||||
.filter(acl => acl.property != 'changePassword');
|
.filter(acl => acl.property != 'changePassword');
|
||||||
|
|
||||||
// FIXME: https://redmine.verdnatura.es/issues/5761
|
Self.userSecurity = async(ctx, userId, options) => {
|
||||||
// Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => {
|
const models = Self.app.models;
|
||||||
// if (!ctx.args || !ctx.args.data.email) return;
|
const accessToken = ctx?.options?.accessToken || LoopBackContext.getCurrentContext().active.accessToken;
|
||||||
|
const ctxToken = {req: {accessToken}};
|
||||||
|
|
||||||
// const loopBackContext = LoopBackContext.getCurrentContext();
|
if (userId === accessToken.userId) return;
|
||||||
// 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 {
|
const myOptions = {};
|
||||||
// async send(verifyOptions, cb) {
|
if (typeof options == 'object')
|
||||||
// const params = {
|
Object.assign(myOptions, options);
|
||||||
// url: verifyOptions.verifyHref,
|
|
||||||
// recipient: verifyOptions.to,
|
|
||||||
// lang: ctx.req.getLocale()
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const email = new Email('email-verify', params);
|
const hasHigherPrivileges = await models.ACL.checkAccessAcl(ctxToken, 'VnUser', 'higherPrivileges', myOptions);
|
||||||
// email.send();
|
if (hasHigherPrivileges) return;
|
||||||
|
|
||||||
// cb(null, verifyOptions.to);
|
const hasMediumPrivileges = await models.ACL.checkAccessAcl(ctxToken, 'VnUser', 'mediumPrivileges', myOptions);
|
||||||
// }
|
const user = await models.VnUser.findById(userId, {fields: ['id', 'emailVerified']}, myOptions);
|
||||||
// }
|
if (!user.emailVerified && hasMediumPrivileges) return;
|
||||||
|
|
||||||
// const options = {
|
throw new ForbiddenError();
|
||||||
// type: 'email',
|
};
|
||||||
// to: instance.email,
|
|
||||||
// from: {},
|
Self.observe('after save', async ctx => {
|
||||||
// redirect: `${origin}/#!/account/${instance.id}/basic-data?emailConfirmed`,
|
const instance = ctx?.instance;
|
||||||
// template: false,
|
const newEmail = instance?.email;
|
||||||
// mailer: new Mailer,
|
const oldEmail = ctx?.hookState?.oldInstance?.email;
|
||||||
// host: url[1].split('/')[2],
|
if (!ctx.isNewInstance && (!newEmail || !oldEmail || newEmail == oldEmail)) return;
|
||||||
// port: url[2],
|
|
||||||
// protocol: url[0],
|
const loopBackContext = LoopBackContext.getCurrentContext();
|
||||||
// user: Self
|
const httpCtx = {req: loopBackContext.active};
|
||||||
// };
|
const httpRequest = httpCtx.req.http.req;
|
||||||
|
const headers = httpRequest.headers;
|
||||||
// await instance.verify(options);
|
const origin = headers.origin;
|
||||||
// });
|
const url = origin.split(':');
|
||||||
|
|
||||||
|
const env = process.env.NODE_ENV;
|
||||||
|
const liliumUrl = await Self.app.models.Url.findOne({
|
||||||
|
where: {and: [
|
||||||
|
{appName: 'lilium'},
|
||||||
|
{environment: env}
|
||||||
|
]}
|
||||||
|
});
|
||||||
|
|
||||||
|
class Mailer {
|
||||||
|
async send(verifyOptions, cb) {
|
||||||
|
const params = {
|
||||||
|
url: verifyOptions.verifyHref,
|
||||||
|
recipient: verifyOptions.to
|
||||||
|
};
|
||||||
|
|
||||||
|
const email = new Email('email-verify', params);
|
||||||
|
email.send();
|
||||||
|
|
||||||
|
cb(null, verifyOptions.to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
type: 'email',
|
||||||
|
to: newEmail,
|
||||||
|
from: {},
|
||||||
|
redirect: `${liliumUrl.url}verifyEmail?userId=${instance.id}`,
|
||||||
|
template: false,
|
||||||
|
mailer: new Mailer,
|
||||||
|
host: url[1].split('/')[2],
|
||||||
|
port: url[2],
|
||||||
|
protocol: url[0],
|
||||||
|
user: Self
|
||||||
|
};
|
||||||
|
|
||||||
|
await instance.verify(options, ctx.options);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,10 +18,7 @@
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"username": {
|
"username": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"mysql": {
|
|
||||||
"columnName": "name"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"roleFk": {
|
"roleFk": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -38,6 +35,12 @@
|
||||||
"active": {
|
"active": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"emailVerified": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"created": {
|
"created": {
|
||||||
"type": "date"
|
"type": "date"
|
||||||
},
|
},
|
||||||
|
@ -137,7 +140,8 @@
|
||||||
"image",
|
"image",
|
||||||
"hasGrant",
|
"hasGrant",
|
||||||
"realm",
|
"realm",
|
||||||
"email"
|
"email",
|
||||||
|
"emailVerified"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||||
|
VALUES
|
||||||
|
('VnUser', 'higherPrivileges', '*', 'ALLOW', 'ROLE', 'itManagement'),
|
||||||
|
('VnUser', 'mediumPrivileges', '*', 'ALLOW', 'ROLE', 'hr'),
|
||||||
|
('VnUser', 'updateUser', '*', 'ALLOW', 'ROLE', 'employee');
|
||||||
|
|
||||||
|
ALTER TABLE `account`.`user` ADD `username` varchar(30) AS (name) VIRTUAL;
|
|
@ -0,0 +1,4 @@
|
||||||
|
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
|
||||||
|
VALUES ('Worker','setPassword','*','ALLOW','ROLE','employee');
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
|
||||||
|
VALUES
|
||||||
|
('hedera', 'test', 'https://test-shop.verdnatura.es/'),
|
||||||
|
('hedera', 'production', 'https://shop.verdnatura.es/');
|
||||||
|
|
||||||
|
INSERT INTO `salix`.`ACL` ( model, property, accessType, permission, principalType, principalId)
|
||||||
|
VALUES('Url', 'getByUser', 'READ', 'ALLOW', 'ROLE', '$everyone');
|
|
@ -0,0 +1,5 @@
|
||||||
|
ALTER TABLE `vn`.`buy` CHANGE `packageFk` `packagingFk` varchar(10)
|
||||||
|
CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '--' NULL;
|
||||||
|
|
||||||
|
ALTER TABLE `vn`.`buy`
|
||||||
|
ADD COLUMN `packageFk` varchar(10) AS (`packagingFk`) VIRTUAL;
|
|
@ -0,0 +1,146 @@
|
||||||
|
CREATE OR REPLACE DEFINER=`root`@`localhost`
|
||||||
|
SQL SECURITY DEFINER
|
||||||
|
VIEW `vn2008`.`Compres`
|
||||||
|
AS SELECT `c`.`id` AS `Id_Compra`,
|
||||||
|
`c`.`entryFk` AS `Id_Entrada`,
|
||||||
|
`c`.`itemFk` AS `Id_Article`,
|
||||||
|
`c`.`buyingValue` AS `Costefijo`,
|
||||||
|
`c`.`quantity` AS `Cantidad`,
|
||||||
|
`c`.`packagingFk` AS `Id_Cubo`,
|
||||||
|
`c`.`stickers` AS `Etiquetas`,
|
||||||
|
`c`.`freightValue` AS `Portefijo`,
|
||||||
|
`c`.`packageValue` AS `Embalajefijo`,
|
||||||
|
`c`.`comissionValue` AS `Comisionfija`,
|
||||||
|
`c`.`packing` AS `Packing`,
|
||||||
|
`c`.`grouping` AS `grouping`,
|
||||||
|
`c`.`groupingMode` AS `caja`,
|
||||||
|
`c`.`location` AS `Nicho`,
|
||||||
|
`c`.`price1` AS `Tarifa1`,
|
||||||
|
`c`.`price2` AS `Tarifa2`,
|
||||||
|
`c`.`price3` AS `Tarifa3`,
|
||||||
|
`c`.`minPrice` AS `PVP`,
|
||||||
|
`c`.`printedStickers` AS `Vida`,
|
||||||
|
`c`.`isChecked` AS `punteo`,
|
||||||
|
`c`.`ektFk` AS `buy_edi_id`,
|
||||||
|
`c`.`created` AS `odbc_date`,
|
||||||
|
`c`.`isIgnored` AS `Novincular`,
|
||||||
|
`c`.`isPickedOff` AS `isPickedOff`,
|
||||||
|
`c`.`workerFk` AS `Id_Trabajador`,
|
||||||
|
`c`.`weight` AS `weight`,
|
||||||
|
`c`.`dispatched` AS `dispatched`,
|
||||||
|
`c`.`containerFk` AS `container_id`,
|
||||||
|
`c`.`itemOriginalFk` AS `itemOriginalFk`
|
||||||
|
FROM `vn`.`buy` `c`;
|
||||||
|
|
||||||
|
CREATE OR REPLACE DEFINER=`root`@`localhost`
|
||||||
|
SQL SECURITY DEFINER
|
||||||
|
VIEW `vn2008`.`buySource`
|
||||||
|
AS SELECT `b`.`entryFk` AS `Id_Entrada`,
|
||||||
|
`b`.`isPickedOff` AS `isPickedOff`,
|
||||||
|
NULL AS `tarifa0`,
|
||||||
|
`e`.`kop` AS `kop`,
|
||||||
|
`b`.`id` AS `Id_Compra`,
|
||||||
|
`i`.`typeFk` AS `tipo_id`,
|
||||||
|
`b`.`itemFk` AS `Id_Article`,
|
||||||
|
`i`.`size` AS `Medida`,
|
||||||
|
`i`.`stems` AS `Tallos`,
|
||||||
|
`b`.`stickers` AS `Etiquetas`,
|
||||||
|
`b`.`packagingFk` AS `Id_Cubo`,
|
||||||
|
`b`.`buyingValue` AS `Costefijo`,
|
||||||
|
`b`.`packing` AS `Packing`,
|
||||||
|
`b`.`grouping` AS `Grouping`,
|
||||||
|
`b`.`quantity` AS `Cantidad`,
|
||||||
|
`b`.`price2` AS `Tarifa2`,
|
||||||
|
`b`.`price3` AS `Tarifa3`,
|
||||||
|
`b`.`isChecked` AS `Punteo`,
|
||||||
|
`b`.`groupingMode` AS `Caja`,
|
||||||
|
`i`.`isToPrint` AS `Imprimir`,
|
||||||
|
`i`.`name` AS `Article`,
|
||||||
|
`vn`.`ink`.`picture` AS `Tinta`,
|
||||||
|
`i`.`originFk` AS `id_origen`,
|
||||||
|
`i`.`minPrice` AS `PVP`,
|
||||||
|
NULL AS `Id_Accion`,
|
||||||
|
`s`.`company_name` AS `pro`,
|
||||||
|
`i`.`hasMinPrice` AS `Min`,
|
||||||
|
`b`.`isIgnored` AS `Novincular`,
|
||||||
|
`b`.`freightValue` AS `Portefijo`,
|
||||||
|
round(`b`.`buyingValue` * `b`.`quantity`, 2) AS `Importe`,
|
||||||
|
`b`.`printedStickers` AS `Vida`,
|
||||||
|
`i`.`comment` AS `reference`,
|
||||||
|
`b`.`workerFk` AS `Id_Trabajador`,
|
||||||
|
`e`.`s1` AS `S1`,
|
||||||
|
`e`.`s2` AS `S2`,
|
||||||
|
`e`.`s3` AS `S3`,
|
||||||
|
`e`.`s4` AS `S4`,
|
||||||
|
`e`.`s5` AS `S5`,
|
||||||
|
`e`.`s6` AS `S6`,
|
||||||
|
0 AS `price_fixed`,
|
||||||
|
`i`.`producerFk` AS `producer_id`,
|
||||||
|
`i`.`subName` AS `tag1`,
|
||||||
|
`i`.`value5` AS `tag2`,
|
||||||
|
`i`.`value6` AS `tag3`,
|
||||||
|
`i`.`value7` AS `tag4`,
|
||||||
|
`i`.`value8` AS `tag5`,
|
||||||
|
`i`.`value9` AS `tag6`,
|
||||||
|
`s`.`company_name` AS `company_name`,
|
||||||
|
`b`.`weight` AS `weightPacking`,
|
||||||
|
`i`.`packingOut` AS `packingOut`,
|
||||||
|
`b`.`itemOriginalFk` AS `itemOriginalFk`,
|
||||||
|
`io`.`longName` AS `itemOriginalName`,
|
||||||
|
`it`.`gramsMax` AS `gramsMax`
|
||||||
|
FROM (
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
`vn`.`item` `i`
|
||||||
|
JOIN `vn`.`itemType` `it` ON(`it`.`id` = `i`.`typeFk`)
|
||||||
|
)
|
||||||
|
LEFT JOIN `vn`.`ink` ON(`vn`.`ink`.`id` = `i`.`inkFk`)
|
||||||
|
)
|
||||||
|
LEFT JOIN `vn`.`buy` `b` ON(`b`.`itemFk` = `i`.`id`)
|
||||||
|
)
|
||||||
|
LEFT JOIN `vn`.`item` `io` ON(`io`.`id` = `b`.`itemOriginalFk`)
|
||||||
|
)
|
||||||
|
LEFT JOIN `edi`.`ekt` `e` ON(`e`.`id` = `b`.`ektFk`)
|
||||||
|
)
|
||||||
|
LEFT JOIN `edi`.`supplier` `s` ON(`e`.`pro` = `s`.`supplier_id`)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OR REPLACE DEFINER=`root`@`localhost`
|
||||||
|
SQL SECURITY DEFINER
|
||||||
|
VIEW `vn`.`awbVolume`
|
||||||
|
AS SELECT `d`.`awbFk` AS `awbFk`,
|
||||||
|
`b`.`stickers` * `i`.`density` * IF(
|
||||||
|
`p`.`volume` > 0,
|
||||||
|
`p`.`volume`,
|
||||||
|
`p`.`width` * `p`.`depth` * IF(`p`.`height` = 0, `i`.`size` + 10, `p`.`height`)
|
||||||
|
) / (`vc`.`aerealVolumetricDensity` * 1000) AS `volume`,
|
||||||
|
`b`.`id` AS `buyFk`
|
||||||
|
FROM (
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
`vn`.`buy` `b`
|
||||||
|
JOIN `vn`.`item` `i` ON(`b`.`itemFk` = `i`.`id`)
|
||||||
|
)
|
||||||
|
JOIN `vn`.`itemType` `it` ON(`i`.`typeFk` = `it`.`id`)
|
||||||
|
)
|
||||||
|
JOIN `vn`.`packaging` `p` ON(`p`.`id` = `b`.`packagingFk`)
|
||||||
|
)
|
||||||
|
JOIN `vn`.`entry` `e` ON(`b`.`entryFk` = `e`.`id`)
|
||||||
|
)
|
||||||
|
JOIN `vn`.`travel` `t` ON(`t`.`id` = `e`.`travelFk`)
|
||||||
|
)
|
||||||
|
JOIN `vn`.`duaEntry` `de` ON(`de`.`entryFk` = `e`.`id`)
|
||||||
|
)
|
||||||
|
JOIN `vn`.`dua` `d` ON(`d`.`id` = `de`.`duaFk`)
|
||||||
|
)
|
||||||
|
JOIN `vn`.`volumeConfig` `vc`
|
||||||
|
)
|
||||||
|
WHERE `t`.`shipped` > makedate(year(`util`.`VN_CURDATE`()) - 1, 1);
|
|
@ -0,0 +1,26 @@
|
||||||
|
ALTER TABLE `vn`.`zoneIncluded`
|
||||||
|
ADD COLUMN `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
|
||||||
|
DROP PRIMARY KEY,
|
||||||
|
DROP FOREIGN KEY `zoneFk2`,
|
||||||
|
DROP FOREIGN KEY `zoneGeoFk2`,
|
||||||
|
DROP KEY `geoFk_idx`,
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD CONSTRAINT `zoneIncluded_FK_1` FOREIGN KEY (zoneFk) REFERENCES `vn`.`zone`(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
ADD CONSTRAINT `zoneIncluded_FK_2` FOREIGN KEY (geoFk) REFERENCES `vn`.`zoneGeo`(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
ADD CONSTRAINT `unique_zone_geo` UNIQUE (`zoneFk`, `geoFk`);
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `vn`.`zoneIncluded_afterDelete`;
|
||||||
|
USE `vn`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`zoneIncluded_afterDelete`
|
||||||
|
AFTER DELETE ON `zoneIncluded`
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO zoneLog
|
||||||
|
SET `action` = 'delete',
|
||||||
|
`changedModel` = 'zoneIncluded',
|
||||||
|
`changedModelId` = OLD.zoneFk,
|
||||||
|
`userFk` = account.myUser_getId();
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,57 @@
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`buy_afterUpdate`
|
||||||
|
AFTER UPDATE ON `buy`
|
||||||
|
FOR EACH ROW
|
||||||
|
trig: BEGIN
|
||||||
|
DECLARE vLanded DATE;
|
||||||
|
DECLARE vBuyerFk INT;
|
||||||
|
DECLARE vIsBuyerToBeEmailed BOOL;
|
||||||
|
DECLARE vItemName VARCHAR(50);
|
||||||
|
|
||||||
|
IF @isModeInventory OR @isTriggerDisabled THEN
|
||||||
|
LEAVE trig;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF !(NEW.id <=> OLD.id)
|
||||||
|
OR !(NEW.entryFk <=> OLD.entryFk)
|
||||||
|
OR !(NEW.itemFk <=> OLD.itemFk)
|
||||||
|
OR !(NEW.quantity <=> OLD.quantity)
|
||||||
|
OR !(NEW.created <=> OLD.created) THEN
|
||||||
|
CALL stock.log_add('buy', NEW.id, OLD.id);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
CALL buy_afterUpsert(NEW.id);
|
||||||
|
|
||||||
|
SELECT w.isBuyerToBeEmailed, t.landed
|
||||||
|
INTO vIsBuyerToBeEmailed, vLanded
|
||||||
|
FROM entry e
|
||||||
|
JOIN travel t ON t.id = e.travelFk
|
||||||
|
JOIN warehouse w ON w.id = t.warehouseInFk
|
||||||
|
WHERE e.id = NEW.entryFk;
|
||||||
|
|
||||||
|
SELECT it.workerFk, i.longName
|
||||||
|
INTO vBuyerFk, vItemName
|
||||||
|
FROM itemCategory k
|
||||||
|
JOIN itemType it ON it.categoryFk = k.id
|
||||||
|
JOIN item i ON i.typeFk = it.id
|
||||||
|
WHERE i.id = OLD.itemFk;
|
||||||
|
|
||||||
|
IF vIsBuyerToBeEmailed AND
|
||||||
|
vBuyerFk != account.myUser_getId() AND
|
||||||
|
vLanded = util.VN_CURDATE() THEN
|
||||||
|
IF !(NEW.itemFk <=> OLD.itemFk) OR
|
||||||
|
!(NEW.quantity <=> OLD.quantity) OR
|
||||||
|
!(NEW.packing <=> OLD.packing) OR
|
||||||
|
!(NEW.grouping <=> OLD.grouping) OR
|
||||||
|
!(NEW.packagingFk <=> OLD.packagingFk) OR
|
||||||
|
!(NEW.weight <=> OLD.weight) THEN
|
||||||
|
CALL vn.mail_insert(
|
||||||
|
CONCAT(account.user_getNameFromId(vBuyerFk),'@verdnatura.es'),
|
||||||
|
CONCAT(account.myUser_getName(),'@verdnatura.es'),
|
||||||
|
CONCAT('E ', NEW.entryFk ,' Se ha modificado item ', NEW.itemFk, ' ', vItemName),
|
||||||
|
'Este email se ha generado automáticamente'
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END IF;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
File diff suppressed because it is too large
Load Diff
|
@ -1454,7 +1454,7 @@ INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `itemFk`, `itemTypeF
|
||||||
('HankPym', YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK), 1), 'Miscellaneous Accessories', 6, 1, '186', '0', '0.0'),
|
('HankPym', YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK), 1), 'Miscellaneous Accessories', 6, 1, '186', '0', '0.0'),
|
||||||
('HankPym', YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK), 1), 'Adhesives', 7, 1, '277', '0', '0.0');
|
('HankPym', YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK), 1), 'Adhesives', 7, 1, '277', '0', '0.0');
|
||||||
|
|
||||||
INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packageFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`, `printedStickers`,`isChecked`,`isIgnored`,`weight`, `created`)
|
INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packagingFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`, `printedStickers`,`isChecked`,`isIgnored`,`weight`, `created`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0, 1, 0, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH)),
|
(1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0, 1, 0, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH)),
|
||||||
(2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0, 1, 0, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
|
(2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0, 1, 0, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
|
||||||
|
@ -2867,6 +2867,7 @@ INSERT INTO `vn`.`profileType` (`id`, `name`)
|
||||||
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
|
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
|
||||||
VALUES
|
VALUES
|
||||||
('lilium', 'development', 'http://localhost:9000/#/'),
|
('lilium', 'development', 'http://localhost:9000/#/'),
|
||||||
|
('hedera', 'development', 'http://localhost:9090/'),
|
||||||
('salix', 'development', 'http://localhost:5000/#!/');
|
('salix', 'development', 'http://localhost:5000/#!/');
|
||||||
|
|
||||||
INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)
|
INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)
|
||||||
|
|
|
@ -30434,6 +30434,7 @@ CREATE TABLE `item` (
|
||||||
`editorFk` int(10) unsigned DEFAULT NULL,
|
`editorFk` int(10) unsigned DEFAULT NULL,
|
||||||
`recycledPlastic` int(11) DEFAULT NULL,
|
`recycledPlastic` int(11) DEFAULT NULL,
|
||||||
`nonRecycledPlastic` int(11) DEFAULT NULL,
|
`nonRecycledPlastic` int(11) DEFAULT NULL,
|
||||||
|
`minQuantity` int(10) unsigned DEFAULT NULL COMMENT 'Cantidad mínima para una línea de venta',
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `item_supplyResponseFk_idx` (`supplyResponseFk`),
|
UNIQUE KEY `item_supplyResponseFk_idx` (`supplyResponseFk`),
|
||||||
KEY `Color` (`inkFk`),
|
KEY `Color` (`inkFk`),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -1192,7 +1192,7 @@ export default {
|
||||||
secondBuyPacking: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.packing"]',
|
secondBuyPacking: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.packing"]',
|
||||||
secondBuyWeight: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.weight"]',
|
secondBuyWeight: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.weight"]',
|
||||||
secondBuyStickers: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.stickers"]',
|
secondBuyStickers: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.stickers"]',
|
||||||
secondBuyPackage: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-autocomplete[ng-model="buy.packageFk"]',
|
secondBuyPackage: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-autocomplete[ng-model="buy.packagingFk"]',
|
||||||
secondBuyQuantity: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.quantity"]',
|
secondBuyQuantity: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.quantity"]',
|
||||||
secondBuyItem: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-autocomplete[ng-model="buy.itemFk"]',
|
secondBuyItem: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-autocomplete[ng-model="buy.itemFk"]',
|
||||||
importButton: 'vn-entry-buy-index vn-icon[icon="publish"]',
|
importButton: 'vn-entry-buy-index vn-icon[icon="publish"]',
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -1,20 +1,5 @@
|
||||||
import getBrowser from '../../helpers/puppeteer';
|
import getBrowser from '../../helpers/puppeteer';
|
||||||
|
|
||||||
const $ = {
|
|
||||||
saveButton: 'vn-supplier-fiscal-data button[type="submit"]',
|
|
||||||
};
|
|
||||||
const $inputs = {
|
|
||||||
province: 'vn-supplier-fiscal-data [name="province"]',
|
|
||||||
country: 'vn-supplier-fiscal-data [name="country"]',
|
|
||||||
postcode: 'vn-supplier-fiscal-data [name="postcode"]',
|
|
||||||
city: 'vn-supplier-fiscal-data [name="city"]',
|
|
||||||
socialName: 'vn-supplier-fiscal-data [name="socialName"]',
|
|
||||||
taxNumber: 'vn-supplier-fiscal-data [name="taxNumber"]',
|
|
||||||
account: 'vn-supplier-fiscal-data [name="account"]',
|
|
||||||
sageWithholding: 'vn-supplier-fiscal-data [ng-model="$ctrl.supplier.sageWithholdingFk"]',
|
|
||||||
sageTaxType: 'vn-supplier-fiscal-data [ng-model="$ctrl.supplier.sageTaxTypeFk"]'
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Supplier fiscal data path', () => {
|
describe('Supplier fiscal data path', () => {
|
||||||
let browser;
|
let browser;
|
||||||
let page;
|
let page;
|
||||||
|
@ -30,7 +15,7 @@ describe('Supplier fiscal data path', () => {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should attempt to edit the fiscal data and check data is saved', async() => {
|
it('should attempt to edit the fiscal data and check data iss saved', async() => {
|
||||||
await page.accessToSection('supplier.card.fiscalData');
|
await page.accessToSection('supplier.card.fiscalData');
|
||||||
|
|
||||||
const form = 'vn-supplier-fiscal-data form';
|
const form = 'vn-supplier-fiscal-data form';
|
||||||
|
@ -40,16 +25,16 @@ describe('Supplier fiscal data path', () => {
|
||||||
postcode: null,
|
postcode: null,
|
||||||
city: 'Valencia',
|
city: 'Valencia',
|
||||||
socialName: 'Farmer King SL',
|
socialName: 'Farmer King SL',
|
||||||
taxNumber: 'Wrong tax number',
|
taxNumber: '12345678Z',
|
||||||
account: '0123456789',
|
account: '0123456789',
|
||||||
sageWithholding: 'retencion estimacion objetiva',
|
sageWithholding: 'retencion estimacion objetiva',
|
||||||
sageTaxType: 'operaciones no sujetas'
|
sageTaxType: 'operaciones no sujetas'
|
||||||
};
|
};
|
||||||
|
|
||||||
const errorMessage = await page.sendForm(form, values);
|
const errorMessage = await page.sendForm(form, {
|
||||||
const message = await page.sendForm(form, {
|
taxNumber: 'Wrong tax number'
|
||||||
taxNumber: '12345678Z'
|
|
||||||
});
|
});
|
||||||
|
const message = await page.sendForm(form, values);
|
||||||
|
|
||||||
await page.reloadSection('supplier.card.fiscalData');
|
await page.reloadSection('supplier.card.fiscalData');
|
||||||
const formValues = await page.fetchForm(form, Object.keys(values));
|
const formValues = await page.fetchForm(form, Object.keys(values));
|
||||||
|
|
|
@ -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",
|
||||||
|
@ -189,5 +189,6 @@
|
||||||
"The sales do not exists": "The sales do not exists",
|
"The sales do not exists": "The sales do not exists",
|
||||||
"Ticket without Route": "Ticket without route",
|
"Ticket without Route": "Ticket without route",
|
||||||
"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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
@ -320,5 +320,7 @@
|
||||||
"The response is not a PDF": "La respuesta no es un PDF",
|
"The response is not a PDF": "La respuesta no es un PDF",
|
||||||
"Ticket without Route": "Ticket sin ruta",
|
"Ticket without Route": "Ticket sin ruta",
|
||||||
"Booking completed": "Reserva completada",
|
"Booking completed": "Reserva completada",
|
||||||
"The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación"
|
"The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación",
|
||||||
|
"The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mímina",
|
||||||
|
"quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mímina"
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,5 +21,16 @@
|
||||||
"model": "VnUser",
|
"model": "VnUser",
|
||||||
"foreignKey": "account"
|
"foreignKey": "account"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"acls": [{
|
||||||
|
"accessType": "READ",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$owner",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
}, {
|
||||||
|
"accessType": "WRITE",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$owner",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
<mg-ajax path="VnUsers/{{patch.params.id}}/update-user" options="vnPatch"></mg-ajax>
|
||||||
<vn-watcher
|
<vn-watcher
|
||||||
vn-id="watcher"
|
vn-id="watcher"
|
||||||
url="VnUsers"
|
|
||||||
data="$ctrl.user"
|
data="$ctrl.user"
|
||||||
id-value="$ctrl.$params.id"
|
form="form"
|
||||||
form="form">
|
save="patch">
|
||||||
</vn-watcher>
|
</vn-watcher>
|
||||||
<form
|
<form
|
||||||
name="form"
|
name="form"
|
||||||
|
|
|
@ -18,5 +18,8 @@ ngModule.component('vnUserBasicData', {
|
||||||
controller: Controller,
|
controller: Controller,
|
||||||
require: {
|
require: {
|
||||||
card: '^vnUserCard'
|
card: '^vnUserCard'
|
||||||
|
},
|
||||||
|
bindings: {
|
||||||
|
user: '<'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -78,7 +78,9 @@
|
||||||
"state": "account.card.basicData",
|
"state": "account.card.basicData",
|
||||||
"component": "vn-user-basic-data",
|
"component": "vn-user-basic-data",
|
||||||
"description": "Basic data",
|
"description": "Basic data",
|
||||||
"acl": ["itManagement"]
|
"params": {
|
||||||
|
"user": "$ctrl.user"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url" : "/log",
|
"url" : "/log",
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ describe('claim regularizeClaim()', () => {
|
||||||
|
|
||||||
claimEnds = await importTicket(ticketId, claimId, userId, options);
|
claimEnds = await importTicket(ticketId, claimId, userId, options);
|
||||||
|
|
||||||
for (claimEnd of claimEnds)
|
for (const claimEnd of claimEnds)
|
||||||
await claimEnd.updateAttributes({claimDestinationFk: trashDestination}, options);
|
await claimEnd.updateAttributes({claimDestinationFk: trashDestination}, options);
|
||||||
|
|
||||||
let claimBefore = await models.Claim.findById(claimId, null, options);
|
let claimBefore = await models.Claim.findById(claimId, null, options);
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ Search claim by id or client name: Buscar reclamaciones por identificador o nomb
|
||||||
Claim deleted!: Reclamación eliminada!
|
Claim deleted!: Reclamación eliminada!
|
||||||
claim: reclamación
|
claim: reclamación
|
||||||
Photos: Fotos
|
Photos: Fotos
|
||||||
|
Development: Trazabilidad
|
||||||
Go to the claim: Ir a la reclamación
|
Go to the claim: Ir a la reclamación
|
||||||
Sale tracking: Líneas preparadas
|
Sale tracking: Líneas preparadas
|
||||||
Ticket tracking: Estados del ticket
|
Ticket tracking: Estados del ticket
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -15,4 +15,4 @@ columns:
|
||||||
weight: weight
|
weight: weight
|
||||||
entryFk: entry
|
entryFk: entry
|
||||||
itemFk: item
|
itemFk: item
|
||||||
packageFk: package
|
packagingFk: package
|
||||||
|
|
|
@ -15,4 +15,4 @@ columns:
|
||||||
weight: peso
|
weight: peso
|
||||||
entryFk: entrada
|
entryFk: entrada
|
||||||
itemFk: artículo
|
itemFk: artículo
|
||||||
packageFk: paquete
|
packagingFk: paquete
|
||||||
|
|
|
@ -80,7 +80,7 @@ module.exports = Self => {
|
||||||
comissionValue: buyUltimate.comissionValue,
|
comissionValue: buyUltimate.comissionValue,
|
||||||
packageValue: buyUltimate.packageValue,
|
packageValue: buyUltimate.packageValue,
|
||||||
location: buyUltimate.location,
|
location: buyUltimate.location,
|
||||||
packageFk: buyUltimate.packageFk,
|
packagingFk: buyUltimate.packagingFk,
|
||||||
price1: buyUltimate.price1,
|
price1: buyUltimate.price1,
|
||||||
price2: buyUltimate.price2,
|
price2: buyUltimate.price2,
|
||||||
price3: buyUltimate.price3,
|
price3: buyUltimate.price3,
|
||||||
|
|
|
@ -44,7 +44,7 @@ module.exports = Self => {
|
||||||
'grouping',
|
'grouping',
|
||||||
'groupingMode',
|
'groupingMode',
|
||||||
'quantity',
|
'quantity',
|
||||||
'packageFk',
|
'packagingFk',
|
||||||
'weight',
|
'weight',
|
||||||
'buyingValue',
|
'buyingValue',
|
||||||
'price2',
|
'price2',
|
||||||
|
|
|
@ -108,7 +108,7 @@ module.exports = Self => {
|
||||||
packing: buy.packing,
|
packing: buy.packing,
|
||||||
grouping: buy.grouping,
|
grouping: buy.grouping,
|
||||||
buyingValue: buy.buyingValue,
|
buyingValue: buy.buyingValue,
|
||||||
packageFk: buy.packageFk,
|
packagingFk: buy.packagingFk,
|
||||||
groupingMode: lastBuy.groupingMode,
|
groupingMode: lastBuy.groupingMode,
|
||||||
weight: lastBuy.weight
|
weight: lastBuy.weight
|
||||||
});
|
});
|
||||||
|
|
|
@ -39,7 +39,7 @@ module.exports = Self => {
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
|
|
||||||
if (packaging)
|
if (packaging)
|
||||||
buy.packageFk = packaging.id;
|
buy.packagingFk = packaging.id;
|
||||||
|
|
||||||
const reference = await models.ItemMatchProperties.findOne({
|
const reference = await models.ItemMatchProperties.findOne({
|
||||||
fields: ['itemFk'],
|
fields: ['itemFk'],
|
||||||
|
|
|
@ -153,8 +153,7 @@ module.exports = Self => {
|
||||||
const date = Date.vnNew();
|
const date = Date.vnNew();
|
||||||
date.setHours(0, 0, 0, 0);
|
date.setHours(0, 0, 0, 0);
|
||||||
stmt = new ParameterizedSQL(`
|
stmt = new ParameterizedSQL(`
|
||||||
SELECT
|
SELECT i.image,
|
||||||
i.image,
|
|
||||||
i.id AS itemFk,
|
i.id AS itemFk,
|
||||||
i.size,
|
i.size,
|
||||||
i.weightByPiece,
|
i.weightByPiece,
|
||||||
|
@ -197,7 +196,7 @@ module.exports = Self => {
|
||||||
b.price3,
|
b.price3,
|
||||||
b.ektFk,
|
b.ektFk,
|
||||||
b.weight,
|
b.weight,
|
||||||
b.packageFk,
|
b.packagingFk,
|
||||||
lb.landing
|
lb.landing
|
||||||
FROM cache.last_buy lb
|
FROM cache.last_buy lb
|
||||||
LEFT JOIN cache.visible v ON v.item_id = lb.item_id
|
LEFT JOIN cache.visible v ON v.item_id = lb.item_id
|
||||||
|
|
|
@ -33,7 +33,7 @@ describe('entry import()', () => {
|
||||||
packing: 1,
|
packing: 1,
|
||||||
size: 1,
|
size: 1,
|
||||||
volume: 1200,
|
volume: 1200,
|
||||||
packageFk: '94'
|
packagingFk: '94'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
itemFk: 4,
|
itemFk: 4,
|
||||||
|
@ -43,7 +43,7 @@ describe('entry import()', () => {
|
||||||
packing: 1,
|
packing: 1,
|
||||||
size: 25,
|
size: 25,
|
||||||
volume: 1125,
|
volume: 1125,
|
||||||
packageFk: '94'
|
packagingFk: '94'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,12 @@ describe('entry importBuysPreview()', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the buys with the calculated packageFk', async() => {
|
it('should return the buys with the calculated packagingFk', async() => {
|
||||||
const tx = await models.Entry.beginTransaction({});
|
const tx = await models.Entry.beginTransaction({});
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const expectedPackageFk = '3';
|
const expectedPackagingFk = '3';
|
||||||
const buys = [
|
const buys = [
|
||||||
{
|
{
|
||||||
itemFk: 1,
|
itemFk: 1,
|
||||||
|
@ -39,7 +39,7 @@ describe('entry importBuysPreview()', () => {
|
||||||
const randomIndex = Math.floor(Math.random() * result.length);
|
const randomIndex = Math.floor(Math.random() * result.length);
|
||||||
const buy = result[randomIndex];
|
const buy = result[randomIndex];
|
||||||
|
|
||||||
expect(buy.packageFk).toEqual(expectedPackageFk);
|
expect(buy.packagingFk).toEqual(expectedPackagingFk);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -103,7 +103,7 @@
|
||||||
"package": {
|
"package": {
|
||||||
"type": "belongsTo",
|
"type": "belongsTo",
|
||||||
"model": "Packaging",
|
"model": "Packaging",
|
||||||
"foreignKey": "packageFk"
|
"foreignKey": "packagingFk"
|
||||||
},
|
},
|
||||||
"worker": {
|
"worker": {
|
||||||
"type": "belongsTo",
|
"type": "belongsTo",
|
||||||
|
|
|
@ -83,14 +83,14 @@
|
||||||
<td center>{{::buy.packing | dashIfEmpty}}</td>
|
<td center>{{::buy.packing | dashIfEmpty}}</td>
|
||||||
<td center>{{::buy.grouping | dashIfEmpty}}</td>
|
<td center>{{::buy.grouping | dashIfEmpty}}</td>
|
||||||
<td>{{::buy.buyingValue | currency: 'EUR':2}}</td>
|
<td>{{::buy.buyingValue | currency: 'EUR':2}}</td>
|
||||||
<td center title="{{::buy.packageFk | dashIfEmpty}}">
|
<td center title="{{::buy.packagingFk | dashIfEmpty}}">
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
vn-one
|
vn-one
|
||||||
url="Packagings"
|
url="Packagings"
|
||||||
show-field="id"
|
show-field="id"
|
||||||
value-field="id"
|
value-field="id"
|
||||||
where="{isBox: true}"
|
where="{isBox: true}"
|
||||||
ng-model="buy.packageFk">
|
ng-model="buy.packagingFk">
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -88,12 +88,12 @@
|
||||||
<td center>
|
<td center>
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
vn-one
|
vn-one
|
||||||
title="{{::buy.packageFk | dashIfEmpty}}"
|
title="{{::buy.packagingFk | dashIfEmpty}}"
|
||||||
url="Packagings"
|
url="Packagings"
|
||||||
show-field="id"
|
show-field="id"
|
||||||
value-field="id"
|
value-field="id"
|
||||||
where="{freightItemFk: true}"
|
where="{freightItemFk: true}"
|
||||||
ng-model="buy.packageFk"
|
ng-model="buy.packagingFk"
|
||||||
on-change="$ctrl.saveBuy(buy)">
|
on-change="$ctrl.saveBuy(buy)">
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Section from 'salix/components/section';
|
||||||
|
|
||||||
export default class Controller extends Section {
|
export default class Controller extends Section {
|
||||||
saveBuy(buy) {
|
saveBuy(buy) {
|
||||||
const missingData = !buy.itemFk || !buy.quantity || !buy.packageFk;
|
const missingData = !buy.itemFk || !buy.quantity || !buy.packagingFk;
|
||||||
if (missingData) return;
|
if (missingData) return;
|
||||||
|
|
||||||
let options;
|
let options;
|
||||||
|
|
|
@ -17,7 +17,7 @@ describe('Entry buy', () => {
|
||||||
|
|
||||||
describe('saveBuy()', () => {
|
describe('saveBuy()', () => {
|
||||||
it(`should call the buys patch route if the received buy has an ID`, () => {
|
it(`should call the buys patch route if the received buy has an ID`, () => {
|
||||||
const buy = {id: 1, itemFk: 1, quantity: 1, packageFk: 1};
|
const buy = {id: 1, itemFk: 1, quantity: 1, packagingFk: 1};
|
||||||
|
|
||||||
const query = `Buys/${buy.id}`;
|
const query = `Buys/${buy.id}`;
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@
|
||||||
<th field="weight">
|
<th field="weight">
|
||||||
<span translate>Weight</span>
|
<span translate>Weight</span>
|
||||||
</th>
|
</th>
|
||||||
<th field="packageFk">
|
<th field="packagingFk">
|
||||||
<span translate>Package</span>
|
<span translate>Package</span>
|
||||||
</th>
|
</th>
|
||||||
<th field="packingOut">
|
<th field="packingOut">
|
||||||
|
@ -207,7 +207,7 @@
|
||||||
<td number>{{::buy.minPrice | currency: 'EUR':3}}</td>
|
<td number>{{::buy.minPrice | currency: 'EUR':3}}</td>
|
||||||
<td>{{::buy.ektFk | dashIfEmpty}}</td>
|
<td>{{::buy.ektFk | dashIfEmpty}}</td>
|
||||||
<td>{{::buy.weight}}</td>
|
<td>{{::buy.weight}}</td>
|
||||||
<td>{{::buy.packageFk}}</td>
|
<td>{{::buy.packagingFk}}</td>
|
||||||
<td>{{::buy.packingOut}}</td>
|
<td>{{::buy.packingOut}}</td>
|
||||||
<td>{{::buy.landing | date: 'dd/MM/yyyy'}}</td>
|
<td>{{::buy.landing | date: 'dd/MM/yyyy'}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default class Controller extends Section {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'packageFk',
|
field: 'packagingFk',
|
||||||
autocomplete: {
|
autocomplete: {
|
||||||
url: 'Packagings',
|
url: 'Packagings',
|
||||||
showField: 'id'
|
showField: 'id'
|
||||||
|
@ -133,7 +133,7 @@ export default class Controller extends Section {
|
||||||
case 'price3':
|
case 'price3':
|
||||||
case 'ektFk':
|
case 'ektFk':
|
||||||
case 'weight':
|
case 'weight':
|
||||||
case 'packageFk':
|
case 'packagingFk':
|
||||||
return {[`b.${param}`]: value};
|
return {[`b.${param}`]: value};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th translate center field="quantity">Quantity</th>
|
<th translate center field="quantity">Quantity</th>
|
||||||
<th translate center field="sticker">Stickers</th>
|
<th translate center field="sticker">Stickers</th>
|
||||||
<th translate center field="packageFk">Package</th>
|
<th translate center field="packagingFk">Package</th>
|
||||||
<th translate center field="weight">Weight</th>
|
<th translate center field="weight">Weight</th>
|
||||||
<th translate center field="packing">Packing</th>
|
<th translate center field="packing">Packing</th>
|
||||||
<th translate center field="grouping">Grouping</th>
|
<th translate center field="grouping">Grouping</th>
|
||||||
|
@ -118,7 +118,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td center title="{{::line.quantity}}">{{::line.quantity}}</td>
|
<td center title="{{::line.quantity}}">{{::line.quantity}}</td>
|
||||||
<td center title="{{::line.stickers | dashIfEmpty}}">{{::line.stickers | dashIfEmpty}}</td>
|
<td center title="{{::line.stickers | dashIfEmpty}}">{{::line.stickers | dashIfEmpty}}</td>
|
||||||
<td center title="{{::line.packageFk | dashIfEmpty}}">{{::line.packageFk | dashIfEmpty}}</td>
|
<td center title="{{::line.packagingFk | dashIfEmpty}}">{{::line.packagingFk | dashIfEmpty}}</td>
|
||||||
<td center title="{{::line.weight}}">{{::line.weight}}</td>
|
<td center title="{{::line.weight}}">{{::line.weight}}</td>
|
||||||
<td center>
|
<td center>
|
||||||
<vn-chip class="transparent" translate-attr="line.groupingMode == 2 ? {title: 'Minimun amount'} : {title: 'Packing'}" ng-class="{'message': line.groupingMode == 2}">
|
<vn-chip class="transparent" translate-attr="line.groupingMode == 2 ? {title: 'Minimun amount'} : {title: 'Packing'}" ng-class="{'message': line.groupingMode == 2}">
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,7 @@ module.exports = Self => {
|
||||||
Object.assign(myOptions, options);
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
const stmt = new ParameterizedSQL(
|
const stmt = new ParameterizedSQL(
|
||||||
`SELECT
|
`SELECT w.id AS warehouseFk,
|
||||||
w.id AS warehouseFk,
|
|
||||||
w.name AS warehouse,
|
w.name AS warehouse,
|
||||||
tr.landed,
|
tr.landed,
|
||||||
b.id AS buyFk,
|
b.id AS buyFk,
|
||||||
|
@ -53,7 +52,7 @@ module.exports = Self => {
|
||||||
b.freightValue,
|
b.freightValue,
|
||||||
b.comissionValue,
|
b.comissionValue,
|
||||||
b.packageValue,
|
b.packageValue,
|
||||||
b.packageFk ,
|
b.packagingFk ,
|
||||||
s.id AS supplierFk,
|
s.id AS supplierFk,
|
||||||
s.name AS supplier
|
s.name AS supplier
|
||||||
FROM itemType it
|
FROM itemType it
|
||||||
|
@ -66,7 +65,8 @@ module.exports = Self => {
|
||||||
LEFT JOIN warehouse w ON w.id = tr.warehouseInFk
|
LEFT JOIN warehouse w ON w.id = tr.warehouseInFk
|
||||||
LEFT JOIN origin o ON o.id = i.originFk
|
LEFT JOIN origin o ON o.id = i.originFk
|
||||||
) ON it.id = i.typeFk
|
) ON it.id = i.typeFk
|
||||||
LEFT JOIN edi.ekt ek ON b.ektFk = ek.id`);
|
LEFT JOIN edi.ekt ek ON b.ektFk = ek.id`
|
||||||
|
);
|
||||||
stmt.merge(conn.makeSuffix(filter));
|
stmt.merge(conn.makeSuffix(filter));
|
||||||
|
|
||||||
return conn.executeStmt(stmt, myOptions);
|
return conn.executeStmt(stmt, myOptions);
|
||||||
|
|
|
@ -131,6 +131,9 @@
|
||||||
"nonRecycledPlastic": {
|
"nonRecycledPlastic": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
|
"minQuantity": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"packingOut": {
|
"packingOut": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
|
@ -154,6 +157,10 @@
|
||||||
"mysql":{
|
"mysql":{
|
||||||
"columnName": "doPhoto"
|
"columnName": "doPhoto"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"minQuantity": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Min quantity"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"relations": {
|
"relations": {
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
rule
|
rule
|
||||||
info="Full name calculates based on tags 1-3. Is not recommended to change it manually">
|
info="Full name calculates based on tags 1-3. Is not recommended to change it manually">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
url="ItemTypes"
|
url="ItemTypes"
|
||||||
label="Type"
|
label="Type"
|
||||||
|
@ -50,6 +52,30 @@
|
||||||
</div>
|
</div>
|
||||||
</tpl-item>
|
</tpl-item>
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Generic"
|
||||||
|
url="Items/withName"
|
||||||
|
ng-model="$ctrl.item.genericFk"
|
||||||
|
vn-name="generic"
|
||||||
|
show-field="name"
|
||||||
|
value-field="id"
|
||||||
|
search-function="$ctrl.itemSearchFunc($search)"
|
||||||
|
order="id DESC"
|
||||||
|
tabindex="1">
|
||||||
|
<tpl-item>
|
||||||
|
<div>{{::name}}</div>
|
||||||
|
<div class="text-caption text-secondary">
|
||||||
|
#{{::id}}
|
||||||
|
</div>
|
||||||
|
</tpl-item>
|
||||||
|
<append>
|
||||||
|
<vn-icon-button
|
||||||
|
icon="filter_alt"
|
||||||
|
vn-click-stop="$ctrl.showFilterDialog($ctrl.item)"
|
||||||
|
vn-tooltip="Filter...">
|
||||||
|
</vn-icon-button>
|
||||||
|
</append>
|
||||||
|
</vn-autocomplete>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
|
@ -128,30 +154,13 @@
|
||||||
ng-model="$ctrl.item.stemMultiplier"
|
ng-model="$ctrl.item.stemMultiplier"
|
||||||
vn-name="stemMultiplier">
|
vn-name="stemMultiplier">
|
||||||
</vn-input-number>
|
</vn-input-number>
|
||||||
<vn-autocomplete
|
<vn-input-number
|
||||||
label="Generic"
|
min="1"
|
||||||
url="Items/withName"
|
label="Minimum sales quantity"
|
||||||
ng-model="$ctrl.item.genericFk"
|
ng-model="$ctrl.item.minQuantity"
|
||||||
vn-name="generic"
|
vn-name="minQuantity"
|
||||||
show-field="name"
|
rule>
|
||||||
value-field="id"
|
</vn-input-number>
|
||||||
search-function="$ctrl.itemSearchFunc($search)"
|
|
||||||
order="id DESC"
|
|
||||||
tabindex="1">
|
|
||||||
<tpl-item>
|
|
||||||
<div>{{::name}}</div>
|
|
||||||
<div class="text-caption text-secondary">
|
|
||||||
#{{::id}}
|
|
||||||
</div>
|
|
||||||
</tpl-item>
|
|
||||||
<append>
|
|
||||||
<vn-icon-button
|
|
||||||
icon="filter_alt"
|
|
||||||
vn-click-stop="$ctrl.showFilterDialog($ctrl.item)"
|
|
||||||
vn-tooltip="Filter...">
|
|
||||||
</vn-icon-button>
|
|
||||||
</append>
|
|
||||||
</vn-autocomplete>
|
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-input-number
|
<vn-input-number
|
||||||
|
|
|
@ -16,3 +16,4 @@ This item does need a photo: Este artículo necesita una foto
|
||||||
Do photo: Hacer foto
|
Do photo: Hacer foto
|
||||||
Recycled Plastic: Plástico reciclado
|
Recycled Plastic: Plástico reciclado
|
||||||
Non recycled plastic: Plástico no reciclado
|
Non recycled plastic: Plástico no reciclado
|
||||||
|
Minimum sales quantity: Cantidad mínima de venta
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
<vn-th field="quantity" number>Quantity</vn-th>
|
<vn-th field="quantity" number>Quantity</vn-th>
|
||||||
<vn-th number class="expendable">Cost</vn-th>
|
<vn-th number class="expendable">Cost</vn-th>
|
||||||
<vn-th number>Kg.</vn-th>
|
<vn-th number>Kg.</vn-th>
|
||||||
<vn-th field="packageFk" number>Cube</vn-th>
|
<vn-th field="packagingFk" number>Cube</vn-th>
|
||||||
<vn-th field="supplierFk" class="expendable">Provider</vn-th>
|
<vn-th field="supplierFk" class="expendable">Provider</vn-th>
|
||||||
</vn-tr>
|
</vn-tr>
|
||||||
</vn-thead>
|
</vn-thead>
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td number>{{::entry.weight | dashIfEmpty}}</vn-td>
|
<vn-td number>{{::entry.weight | dashIfEmpty}}</vn-td>
|
||||||
<vn-td number>{{::entry.packageFk | dashIfEmpty}}</vn-td>
|
<vn-td number>{{::entry.packagingFk | dashIfEmpty}}</vn-td>
|
||||||
<vn-td class="expendable" title="{{::entry.supplier | dashIfEmpty}}">{{::entry.supplier | dashIfEmpty}}</vn-td>
|
<vn-td class="expendable" title="{{::entry.supplier | dashIfEmpty}}">{{::entry.supplier | dashIfEmpty}}</vn-td>
|
||||||
</vn-tr>
|
</vn-tr>
|
||||||
</vn-tbody>
|
</vn-tbody>
|
||||||
|
|
|
@ -71,7 +71,7 @@ class Controller extends Section {
|
||||||
switch (param) {
|
switch (param) {
|
||||||
case 'id':
|
case 'id':
|
||||||
case 'quantity':
|
case 'quantity':
|
||||||
case 'packageFk':
|
case 'packagingFk':
|
||||||
return {[`b.${param}`]: value};
|
return {[`b.${param}`]: value};
|
||||||
case 'supplierFk':
|
case 'supplierFk':
|
||||||
return {[`s.id`]: value};
|
return {[`s.id`]: value};
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
<vn-th field="ticketFk" number>Ticket ID</vn-th>
|
<vn-th field="ticketFk" number>Ticket ID</vn-th>
|
||||||
<vn-th field="shipped" expand>Shipped</vn-th>
|
<vn-th field="shipped" expand>Shipped</vn-th>
|
||||||
<vn-th field="description" filter-enabled="false" expand>Description</vn-th>
|
<vn-th field="description" filter-enabled="false" expand>Description</vn-th>
|
||||||
|
<vn-th field="requesterFk" >Requester</vn-th>
|
||||||
<vn-th field="quantity" number editable>Requested</vn-th>
|
<vn-th field="quantity" number editable>Requested</vn-th>
|
||||||
<vn-th field="price" number>Price</vn-th>
|
<vn-th field="price" number>Price</vn-th>
|
||||||
<vn-th field="attenderName">Atender</vn-th>
|
<vn-th field="attenderName">Atender</vn-th>
|
||||||
|
@ -51,6 +52,13 @@
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td title="{{::request.description}}" expand>{{::request.description}}</vn-td>
|
<vn-td title="{{::request.description}}" expand>{{::request.description}}</vn-td>
|
||||||
|
<vn-td number>
|
||||||
|
<span
|
||||||
|
class="link"
|
||||||
|
ng-click="workerDescriptor.show($event, request.requesterFk)">
|
||||||
|
{{::request.requesterName}}
|
||||||
|
</span>
|
||||||
|
</vn-td>
|
||||||
<vn-td number>{{::request.quantity}}</vn-td>
|
<vn-td number>{{::request.quantity}}</vn-td>
|
||||||
<vn-td number>{{::request.price | currency: 'EUR':2}}</vn-td>
|
<vn-td number>{{::request.price | currency: 'EUR':2}}</vn-td>
|
||||||
<vn-td>
|
<vn-td>
|
||||||
|
|
|
@ -128,6 +128,9 @@
|
||||||
<vn-label-value label="Non recycled plastic"
|
<vn-label-value label="Non recycled plastic"
|
||||||
value="{{$ctrl.summary.item.nonRecycledPlastic}}">
|
value="{{$ctrl.summary.item.nonRecycledPlastic}}">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Minimum sales quantity"
|
||||||
|
value="{{$ctrl.summary.item.minQuantity}}">
|
||||||
|
</vn-label-value>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
<vn-one name="tags">
|
<vn-one name="tags">
|
||||||
<h4 ng-show="$ctrl.isBuyer || $ctrl.isReplenisher">
|
<h4 ng-show="$ctrl.isBuyer || $ctrl.isReplenisher">
|
||||||
|
|
|
@ -2,3 +2,4 @@ Barcode: Códigos de barras
|
||||||
Other data: Otros datos
|
Other data: Otros datos
|
||||||
Go to the item: Ir al artículo
|
Go to the item: Ir al artículo
|
||||||
WarehouseFk: Calculado sobre el almacén de {{ warehouseName }}
|
WarehouseFk: Calculado sobre el almacén de {{ warehouseName }}
|
||||||
|
Minimum sales quantity: Cantidad mínima de venta
|
||||||
|
|
|
@ -100,8 +100,7 @@ module.exports = Self => {
|
||||||
));
|
));
|
||||||
|
|
||||||
stmt = new ParameterizedSQL(`
|
stmt = new ParameterizedSQL(`
|
||||||
SELECT
|
SELECT i.id,
|
||||||
i.id,
|
|
||||||
i.name,
|
i.name,
|
||||||
i.subName,
|
i.subName,
|
||||||
i.image,
|
i.image,
|
||||||
|
@ -116,15 +115,17 @@ module.exports = Self => {
|
||||||
i.stars,
|
i.stars,
|
||||||
tci.price,
|
tci.price,
|
||||||
tci.available,
|
tci.available,
|
||||||
w.lastName AS lastName,
|
w.lastName,
|
||||||
w.firstName,
|
w.firstName,
|
||||||
tci.priceKg,
|
tci.priceKg,
|
||||||
ink.hex
|
ink.hex,
|
||||||
|
i.minQuantity
|
||||||
FROM tmp.ticketCalculateItem tci
|
FROM tmp.ticketCalculateItem tci
|
||||||
JOIN vn.item i ON i.id = tci.itemFk
|
JOIN vn.item i ON i.id = tci.itemFk
|
||||||
JOIN vn.itemType it ON it.id = i.typeFk
|
JOIN vn.itemType it ON it.id = i.typeFk
|
||||||
JOIN vn.worker w on w.id = it.workerFk
|
JOIN vn.worker w on w.id = it.workerFk
|
||||||
LEFT JOIN vn.ink ON ink.id = i.inkFk`);
|
LEFT JOIN vn.ink ON ink.id = i.inkFk
|
||||||
|
`);
|
||||||
|
|
||||||
// Apply order by tag
|
// Apply order by tag
|
||||||
if (orderBy.isTag) {
|
if (orderBy.isTag) {
|
||||||
|
|
|
@ -37,9 +37,24 @@
|
||||||
value="{{::item.value7}}">
|
value="{{::item.value7}}">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
</div>
|
</div>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-one>
|
||||||
<vn-rating ng-if="::item.stars"
|
<vn-rating ng-if="::item.stars"
|
||||||
ng-model="::item.stars">
|
ng-model="::item.stars"/>
|
||||||
</vn-rating>
|
</vn-one>
|
||||||
|
<vn-horizontal
|
||||||
|
class="text-right text-caption alert vn-mr-xs"
|
||||||
|
ng-if="::item.minQuantity">
|
||||||
|
<vn-one>
|
||||||
|
<vn-icon
|
||||||
|
icon="production_quantity_limits"
|
||||||
|
translate-attr="{title: 'Minimal quantity'}"
|
||||||
|
class="text-subtitle1">
|
||||||
|
</vn-icon>
|
||||||
|
</vn-one>
|
||||||
|
{{::item.minQuantity}}
|
||||||
|
</vn-horizontal>
|
||||||
|
</vn-horizontal>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<div class="price">
|
<div class="price">
|
||||||
<vn-one>
|
<vn-one>
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
Order created: Orden creada
|
Order created: Orden creada
|
||||||
|
Minimal quantity: Cantidad mínima
|
|
@ -44,4 +44,7 @@ vn-order-catalog {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.alert {
|
||||||
|
color: $color-alert;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -55,7 +55,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
const refoundZoneId = refundAgencyMode.zones()[0].id;
|
const refoundZoneId = refundAgencyMode.zones()[0].id;
|
||||||
|
|
||||||
if (salesIds) {
|
if (salesIds.length) {
|
||||||
const salesFilter = {
|
const salesFilter = {
|
||||||
where: {id: {inq: salesIds}},
|
where: {id: {inq: salesIds}},
|
||||||
include: {
|
include: {
|
||||||
|
@ -91,16 +91,14 @@ module.exports = Self => {
|
||||||
await models.SaleComponent.create(components, myOptions);
|
await models.SaleComponent.create(components, myOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!refundTicket) {
|
if (!refundTicket) {
|
||||||
const servicesFilter = {
|
const servicesFilter = {
|
||||||
where: {id: {inq: servicesIds}}
|
where: {id: {inq: servicesIds}}
|
||||||
};
|
};
|
||||||
const services = await models.TicketService.find(servicesFilter, myOptions);
|
const services = await models.TicketService.find(servicesFilter, myOptions);
|
||||||
const ticketsIds = [...new Set(services.map(service => service.ticketFk))];
|
const firstTicketId = services[0].ticketFk;
|
||||||
|
|
||||||
const now = Date.vnNew();
|
const now = Date.vnNew();
|
||||||
const [firstTicketId] = ticketsIds;
|
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions, ctx);
|
refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions, ctx);
|
||||||
|
@ -114,8 +112,8 @@ module.exports = Self => {
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
await models.TicketService.create({
|
await models.TicketService.create({
|
||||||
description: service.description,
|
description: service.description,
|
||||||
quantity: service.quantity,
|
quantity: - service.quantity,
|
||||||
price: - service.price,
|
price: service.price,
|
||||||
taxClassFk: service.taxClassFk,
|
taxClassFk: service.taxClassFk,
|
||||||
ticketFk: refundTicket.id,
|
ticketFk: refundTicket.id,
|
||||||
ticketServiceTypeFk: service.ticketServiceTypeFk,
|
ticketServiceTypeFk: service.ticketServiceTypeFk,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -1,162 +0,0 @@
|
||||||
const models = require('vn-loopback/server/server').models;
|
|
||||||
const LoopBackContext = require('loopback-context');
|
|
||||||
|
|
||||||
describe('sale updateQuantity()', () => {
|
|
||||||
beforeAll(async() => {
|
|
||||||
const activeCtx = {
|
|
||||||
accessToken: {userId: 9},
|
|
||||||
http: {
|
|
||||||
req: {
|
|
||||||
headers: {origin: 'http://localhost'}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
|
||||||
active: activeCtx
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const ctx = {
|
|
||||||
req: {
|
|
||||||
accessToken: {userId: 9},
|
|
||||||
headers: {origin: 'localhost:5000'},
|
|
||||||
__: () => {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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'},
|
|
||||||
__: () => {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const tx = await models.Sale.beginTransaction({});
|
|
||||||
|
|
||||||
let error;
|
|
||||||
try {
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
await models.Sale.updateQuantity(ctx, 17, 99, 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 tx = await models.Sale.beginTransaction({});
|
|
||||||
const saleId = 17;
|
|
||||||
const buyerId = 35;
|
|
||||||
const ctx = {
|
|
||||||
req: {
|
|
||||||
accessToken: {userId: buyerId},
|
|
||||||
headers: {origin: 'localhost:5000'},
|
|
||||||
__: () => {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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() => {
|
|
||||||
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'},
|
|
||||||
__: () => {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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() => {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
let UserError = require('vn-loopback/util/user-error');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('updateQuantity', {
|
Self.remoteMethodCtx('updateQuantity', {
|
||||||
|
@ -64,32 +63,22 @@ module.exports = Self => {
|
||||||
|
|
||||||
const sale = await models.Sale.findById(id, filter, myOptions);
|
const sale = await models.Sale.findById(id, filter, myOptions);
|
||||||
|
|
||||||
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
|
|
||||||
if (newQuantity > sale.quantity && !isRoleAdvanced)
|
|
||||||
throw new UserError('The new quantity should be smaller than the old one');
|
|
||||||
|
|
||||||
const ticketRefund = await models.TicketRefund.findOne({
|
|
||||||
where: {refundTicketFk: sale.ticketFk},
|
|
||||||
fields: ['id']}
|
|
||||||
, myOptions);
|
|
||||||
if (newQuantity < 0 && !ticketRefund)
|
|
||||||
throw new UserError('You can only add negative amounts in refund tickets');
|
|
||||||
|
|
||||||
const oldQuantity = sale.quantity;
|
const oldQuantity = sale.quantity;
|
||||||
const result = await sale.updateAttributes({quantity: newQuantity}, myOptions);
|
const result = await sale.updateAttributes({quantity: newQuantity}, myOptions);
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,8 @@ module.exports = Self => {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return {'tr.isOk': null};
|
return {'tr.isOk': null};
|
||||||
|
case 'accepted':
|
||||||
|
return {'tr.isOk': 1};
|
||||||
default:
|
default:
|
||||||
return {'tr.isOk': value};
|
return {'tr.isOk': value};
|
||||||
}
|
}
|
||||||
|
@ -122,8 +124,7 @@ module.exports = Self => {
|
||||||
filter = mergeFilters(filter, {where});
|
filter = mergeFilters(filter, {where});
|
||||||
|
|
||||||
const stmt = new ParameterizedSQL(
|
const stmt = new ParameterizedSQL(
|
||||||
`SELECT
|
`SELECT tr.id,
|
||||||
tr.id,
|
|
||||||
tr.ticketFk,
|
tr.ticketFk,
|
||||||
tr.quantity,
|
tr.quantity,
|
||||||
tr.price,
|
tr.price,
|
||||||
|
@ -133,18 +134,19 @@ module.exports = Self => {
|
||||||
tr.saleFk,
|
tr.saleFk,
|
||||||
tr.requesterFk,
|
tr.requesterFk,
|
||||||
tr.isOk,
|
tr.isOk,
|
||||||
s.quantity AS saleQuantity,
|
s.quantity saleQuantity,
|
||||||
s.itemFk,
|
s.itemFk,
|
||||||
i.name AS itemDescription,
|
i.name itemDescription,
|
||||||
t.shipped,
|
t.shipped,
|
||||||
DATE(t.shipped) AS shippedDate,
|
DATE(t.shipped) shippedDate,
|
||||||
t.nickname,
|
t.nickname,
|
||||||
t.warehouseFk,
|
t.warehouseFk,
|
||||||
t.clientFk,
|
t.clientFk,
|
||||||
w.name AS warehouse,
|
w.name warehouse,
|
||||||
u.nickname AS salesPersonNickname,
|
u.nickname salesPersonNickname,
|
||||||
ua.name AS attenderName,
|
ua.name attenderName,
|
||||||
c.salesPersonFk
|
c.salesPersonFk,
|
||||||
|
ua2.name requesterName
|
||||||
FROM ticketRequest tr
|
FROM ticketRequest tr
|
||||||
LEFT JOIN ticketWeekly tw on tw.ticketFk = tr.ticketFk
|
LEFT JOIN ticketWeekly tw on tw.ticketFk = tr.ticketFk
|
||||||
LEFT JOIN ticket t ON t.id = tr.ticketFk
|
LEFT JOIN ticket t ON t.id = tr.ticketFk
|
||||||
|
@ -155,7 +157,8 @@ module.exports = Self => {
|
||||||
LEFT JOIN worker wk ON wk.id = c.salesPersonFk
|
LEFT JOIN worker wk ON wk.id = c.salesPersonFk
|
||||||
LEFT JOIN account.user u ON u.id = wk.id
|
LEFT JOIN account.user u ON u.id = wk.id
|
||||||
LEFT JOIN worker wka ON wka.id = tr.attenderFk
|
LEFT JOIN worker wka ON wka.id = tr.attenderFk
|
||||||
LEFT JOIN account.user ua ON ua.id = wka.id`);
|
LEFT JOIN account.user ua ON ua.id = wka.id
|
||||||
|
LEFT JOIN account.user ua2 ON ua2.id = tr.requesterFk`);
|
||||||
stmt.merge(conn.makeSuffix(filter));
|
stmt.merge(conn.makeSuffix(filter));
|
||||||
|
|
||||||
return conn.executeStmt(stmt, myOptions);
|
return conn.executeStmt(stmt, myOptions);
|
||||||
|
|
|
@ -63,17 +63,6 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
|
|
||||||
const itemInfo = await models.Item.getVisibleAvailable(
|
|
||||||
itemId,
|
|
||||||
ticket.warehouseFk,
|
|
||||||
ticket.shipped,
|
|
||||||
myOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
const isPackaging = item.family == 'EMB';
|
|
||||||
if (!isPackaging && itemInfo.available < quantity)
|
|
||||||
throw new UserError(`This item is not available`);
|
|
||||||
|
|
||||||
const newSale = await models.Sale.create({
|
const newSale = await models.Sale.create({
|
||||||
ticketFk: id,
|
ticketFk: id,
|
||||||
itemFk: item.id,
|
itemFk: item.id,
|
||||||
|
@ -94,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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
require('../methods/sale/getClaimableFromTicket')(Self);
|
require('../methods/sale/getClaimableFromTicket')(Self);
|
||||||
require('../methods/sale/reserve')(Self);
|
require('../methods/sale/reserve')(Self);
|
||||||
|
@ -13,4 +16,100 @@ module.exports = Self => {
|
||||||
Self.validatesPresenceOf('concept', {
|
Self.validatesPresenceOf('concept', {
|
||||||
message: `Concept cannot be blank`
|
message: `Concept cannot be blank`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Self.observe('before save', async ctx => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const changes = ctx.data || ctx.instance;
|
||||||
|
const instance = ctx.currentInstance;
|
||||||
|
|
||||||
|
const newQuantity = changes?.quantity;
|
||||||
|
if (newQuantity == null) return;
|
||||||
|
|
||||||
|
const loopBackContext = LoopBackContext.getCurrentContext();
|
||||||
|
ctx.req = loopBackContext.active;
|
||||||
|
if (await models.ACL.checkAccessAcl(ctx, 'Sale', 'canForceQuantity', 'WRITE')) return;
|
||||||
|
|
||||||
|
const ticketId = changes?.ticketFk || instance?.ticketFk;
|
||||||
|
const itemId = changes?.itemFk || instance?.itemFk;
|
||||||
|
const oldQuantity = instance?.quantity ?? null;
|
||||||
|
const quantityAdded = newQuantity - oldQuantity;
|
||||||
|
|
||||||
|
const ticket = await models.Ticket.findById(
|
||||||
|
ticketId,
|
||||||
|
{
|
||||||
|
fields: ['id', 'clientFk', 'warehouseFk', 'addressFk', 'agencyModeFk', 'shipped', 'landed'],
|
||||||
|
include: {
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'clientTypeFk'],
|
||||||
|
include: {
|
||||||
|
relation: 'type',
|
||||||
|
scope: {
|
||||||
|
fields: ['code', 'description']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ctx.options);
|
||||||
|
if (ticket?.client()?.type()?.code === 'loses') return;
|
||||||
|
|
||||||
|
const isRefund = await models.TicketRefund.findOne({
|
||||||
|
fields: ['id'],
|
||||||
|
where: {refundTicketFk: ticketId}
|
||||||
|
}, ctx.options);
|
||||||
|
if (isRefund) return;
|
||||||
|
|
||||||
|
if (newQuantity < 0)
|
||||||
|
throw new UserError('You can only add negative amounts in refund tickets');
|
||||||
|
|
||||||
|
const item = await models.Item.findOne({
|
||||||
|
fields: ['family', 'minQuantity'],
|
||||||
|
where: {id: itemId},
|
||||||
|
}, 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,
|
||||||
|
ticket.agencyModeFk,
|
||||||
|
itemId
|
||||||
|
],
|
||||||
|
ctx.options);
|
||||||
|
|
||||||
|
const [itemInfo] = await models.Sale.rawSql(`SELECT available FROM tmp.ticketCalculateItem`, null, ctx.options);
|
||||||
|
|
||||||
|
if (!itemInfo?.available || itemInfo.available < quantityAdded)
|
||||||
|
throw new UserError(`This item is not available`);
|
||||||
|
|
||||||
|
if (await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*')) return;
|
||||||
|
|
||||||
|
if (newQuantity < item.minQuantity && newQuantity != itemInfo?.available)
|
||||||
|
throw new UserError('The amount cannot be less than the minimum');
|
||||||
|
|
||||||
|
if (ctx.isNewInstance || newQuantity <= oldQuantity) return;
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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;
|
||||||
|
|
|
@ -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':
|
||||||
|
|
|
@ -18,3 +18,4 @@ Multiple invoice: Factura múltiple
|
||||||
Make invoice...: Crear factura...
|
Make invoice...: Crear factura...
|
||||||
Invoice selected tickets: Facturar tickets seleccionados
|
Invoice selected tickets: Facturar tickets seleccionados
|
||||||
Are you sure to invoice tickets: ¿Seguro que quieres facturar {{ticketsAmount}} tickets?
|
Are you sure to invoice tickets: ¿Seguro que quieres facturar {{ticketsAmount}} tickets?
|
||||||
|
Rounding: Redondeo
|
||||||
|
|
|
@ -311,7 +311,7 @@
|
||||||
clear-disabled="true"
|
clear-disabled="true"
|
||||||
suffix="%">
|
suffix="%">
|
||||||
</vn-input-number>
|
</vn-input-number>
|
||||||
<vn-vertical ng-if="$ctrl.usesMana && $ctrl.currentWorkerMana != 0">
|
<vn-vertical ng-if="$ctrl.usesMana">
|
||||||
<vn-radio
|
<vn-radio
|
||||||
label="Promotion mana"
|
label="Promotion mana"
|
||||||
val="mana"
|
val="mana"
|
||||||
|
|
|
@ -97,14 +97,6 @@ class Controller extends Section {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.getUsesMana();
|
this.getUsesMana();
|
||||||
this.getCurrentWorkerMana();
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentWorkerMana() {
|
|
||||||
this.$http.get(`WorkerManas/getCurrentWorkerMana`)
|
|
||||||
.then(res => {
|
|
||||||
this.currentWorkerMana = res.data;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getUsesMana() {
|
getUsesMana() {
|
||||||
|
|
|
@ -120,12 +120,10 @@ describe('Ticket', () => {
|
||||||
const expectedAmount = 250;
|
const expectedAmount = 250;
|
||||||
$httpBackend.expect('GET', 'Tickets/1/getSalesPersonMana').respond(200, expectedAmount);
|
$httpBackend.expect('GET', 'Tickets/1/getSalesPersonMana').respond(200, expectedAmount);
|
||||||
$httpBackend.expect('GET', 'Sales/usesMana').respond(200);
|
$httpBackend.expect('GET', 'Sales/usesMana').respond(200);
|
||||||
$httpBackend.expect('GET', 'WorkerManas/getCurrentWorkerMana').respond(200, expectedAmount);
|
|
||||||
controller.getMana();
|
controller.getMana();
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
expect(controller.edit.mana).toEqual(expectedAmount);
|
expect(controller.edit.mana).toEqual(expectedAmount);
|
||||||
expect(controller.currentWorkerMana).toEqual(expectedAmount);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
disabled="watcher.dataChanged() || !$ctrl.checkeds.length"
|
disabled="watcher.dataChanged() || !$ctrl.checkeds.length"
|
||||||
label="Pay"
|
label="Pay"
|
||||||
ng-click="$ctrl.createRefund()"
|
ng-click="$ctrl.createRefund()"
|
||||||
vn-acl="invoicing, claimManager, salesAssistant"
|
vn-acl="invoicing, claimManager, salesAssistant, buyer"
|
||||||
vn-acl-action="remove">
|
vn-acl-action="remove">
|
||||||
</vn-button>
|
</vn-button>
|
||||||
</vn-button-bar>
|
</vn-button-bar>
|
||||||
|
@ -37,7 +37,9 @@
|
||||||
<vn-check
|
<vn-check
|
||||||
tabindex="1"
|
tabindex="1"
|
||||||
on-change="$ctrl.addChecked(service.id)"
|
on-change="$ctrl.addChecked(service.id)"
|
||||||
disabled="!service.id">
|
disabled="!service.id"
|
||||||
|
vn-acl="invoicing, claimManager, salesAssistant, buyer"
|
||||||
|
vn-acl-action="remove">
|
||||||
</vn-check>
|
</vn-check>
|
||||||
<vn-autocomplete vn-two vn-focus
|
<vn-autocomplete vn-two vn-focus
|
||||||
data="ticketServiceTypes"
|
data="ticketServiceTypes"
|
||||||
|
@ -68,7 +70,8 @@
|
||||||
vn-one
|
vn-one
|
||||||
label="Price"
|
label="Price"
|
||||||
ng-model="service.price"
|
ng-model="service.price"
|
||||||
step="0.01">
|
step="0.01"
|
||||||
|
min="0">
|
||||||
</vn-input-number>
|
</vn-input-number>
|
||||||
<vn-auto>
|
<vn-auto>
|
||||||
<vn-icon-button
|
<vn-icon-button
|
||||||
|
|
|
@ -132,12 +132,18 @@ module.exports = Self => {
|
||||||
s.nickname AS cargoSupplierNickname,
|
s.nickname AS cargoSupplierNickname,
|
||||||
s.name AS supplierName,
|
s.name AS supplierName,
|
||||||
CAST(SUM(b.weight * b.stickers) as DECIMAL(10,0)) as loadedKg,
|
CAST(SUM(b.weight * b.stickers) as DECIMAL(10,0)) as loadedKg,
|
||||||
CAST(SUM(vc.aerealVolumetricDensity * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000) as DECIMAL(10,0)) as volumeKg
|
CAST(
|
||||||
|
SUM(
|
||||||
|
vc.aerealVolumetricDensity *
|
||||||
|
b.stickers *
|
||||||
|
IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000
|
||||||
|
) as DECIMAL(10,0)
|
||||||
|
) as volumeKg
|
||||||
FROM travel t
|
FROM travel t
|
||||||
LEFT JOIN supplier s ON s.id = t.cargoSupplierFk
|
LEFT JOIN supplier s ON s.id = t.cargoSupplierFk
|
||||||
LEFT JOIN entry e ON e.travelFk = t.id
|
LEFT JOIN entry e ON e.travelFk = t.id
|
||||||
LEFT JOIN buy b ON b.entryFk = e.id
|
LEFT JOIN buy b ON b.entryFk = e.id
|
||||||
LEFT JOIN packaging pkg ON pkg.id = b.packageFk
|
LEFT JOIN packaging pkg ON pkg.id = b.packagingFk
|
||||||
LEFT JOIN item i ON i.id = b.itemFk
|
LEFT JOIN item i ON i.id = b.itemFk
|
||||||
LEFT JOIN itemType it ON it.id = i.typeFk
|
LEFT JOIN itemType it ON it.id = i.typeFk
|
||||||
JOIN warehouse w ON w.id = t.warehouseInFk
|
JOIN warehouse w ON w.id = t.warehouseInFk
|
||||||
|
@ -169,11 +175,17 @@ module.exports = Self => {
|
||||||
e.evaNotes,
|
e.evaNotes,
|
||||||
e.invoiceAmount,
|
e.invoiceAmount,
|
||||||
CAST(SUM(b.weight * b.stickers) AS DECIMAL(10,0)) as loadedkg,
|
CAST(SUM(b.weight * b.stickers) AS DECIMAL(10,0)) as loadedkg,
|
||||||
CAST(SUM(vc.aerealVolumetricDensity * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000) AS DECIMAL(10,0)) as volumeKg
|
CAST(
|
||||||
|
SUM(
|
||||||
|
vc.aerealVolumetricDensity *
|
||||||
|
b.stickers *
|
||||||
|
IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000
|
||||||
|
) AS DECIMAL(10,0)
|
||||||
|
) as volumeKg
|
||||||
FROM tmp.travel tr
|
FROM tmp.travel tr
|
||||||
JOIN entry e ON e.travelFk = tr.id
|
JOIN entry e ON e.travelFk = tr.id
|
||||||
JOIN buy b ON b.entryFk = e.id
|
JOIN buy b ON b.entryFk = e.id
|
||||||
JOIN packaging pkg ON pkg.id = b.packageFk
|
JOIN packaging pkg ON pkg.id = b.packagingFk
|
||||||
JOIN item i ON i.id = b.itemFk
|
JOIN item i ON i.id = b.itemFk
|
||||||
JOIN itemType it ON it.id = i.typeFk
|
JOIN itemType it ON it.id = i.typeFk
|
||||||
JOIN supplier s ON s.id = e.supplierFk
|
JOIN supplier s ON s.id = e.supplierFk
|
||||||
|
|
|
@ -47,7 +47,7 @@ module.exports = Self => {
|
||||||
LEFT JOIN vn.buy b ON b.entryFk = e.id
|
LEFT JOIN vn.buy b ON b.entryFk = e.id
|
||||||
LEFT JOIN vn.supplier s ON e.supplierFk = s.id
|
LEFT JOIN vn.supplier s ON e.supplierFk = s.id
|
||||||
JOIN vn.item i ON i.id = b.itemFk
|
JOIN vn.item i ON i.id = b.itemFk
|
||||||
LEFT JOIN vn.packaging p ON p.id = b.packageFk
|
LEFT JOIN vn.packaging p ON p.id = b.packagingFk
|
||||||
JOIN vn.packaging pcc ON pcc.id = 'cc'
|
JOIN vn.packaging pcc ON pcc.id = 'cc'
|
||||||
JOIN vn.packaging ppallet ON ppallet.id = 'pallet 100'
|
JOIN vn.packaging ppallet ON ppallet.id = 'pallet 100'
|
||||||
JOIN vn.packagingConfig pconfig
|
JOIN vn.packagingConfig pconfig
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
module.exports = Self => {
|
|
||||||
Self.remoteMethodCtx('getCurrentWorkerMana', {
|
|
||||||
description: 'Returns the mana of the logged worker',
|
|
||||||
accessType: 'READ',
|
|
||||||
accepts: [],
|
|
||||||
returns: {
|
|
||||||
type: 'number',
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/getCurrentWorkerMana`,
|
|
||||||
verb: 'GET'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.getCurrentWorkerMana = async ctx => {
|
|
||||||
let userId = ctx.req.accessToken.userId;
|
|
||||||
|
|
||||||
let workerMana = await Self.app.models.WorkerMana.findOne({
|
|
||||||
where: {workerFk: userId},
|
|
||||||
fields: 'amount'
|
|
||||||
});
|
|
||||||
|
|
||||||
return workerMana ? workerMana.amount : 0;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,15 +0,0 @@
|
||||||
const app = require('vn-loopback/server/server');
|
|
||||||
|
|
||||||
describe('workerMana getCurrentWorkerMana()', () => {
|
|
||||||
it('should get the mana of the logged worker', async() => {
|
|
||||||
let mana = await app.models.WorkerMana.getCurrentWorkerMana({req: {accessToken: {userId: 18}}});
|
|
||||||
|
|
||||||
expect(mana).toEqual(124);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 0 if the user doesnt uses mana', async() => {
|
|
||||||
let mana = await app.models.WorkerMana.getCurrentWorkerMana({req: {accessToken: {userId: 9}}});
|
|
||||||
|
|
||||||
expect(mana).toEqual(0);
|
|
||||||
});
|
|
||||||
});
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue