Merge branch 'dev' into 5941-send-to-support
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
47b2aa0ee7
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -14,6 +14,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
|
||||
|
||||
## [2330.01] - 2023-07-27
|
||||
|
||||
### Added
|
||||
- (Artículos -> Vista Previa) Añadido campo "Plástico reciclado"
|
||||
- (Rutas -> Troncales) Nueva sección
|
||||
- (Tickets -> Opciones) Opción establecer peso
|
||||
- (Clientes -> SMS) Nueva sección
|
||||
|
||||
### Changed
|
||||
- (General -> Iconos) Añadidos nuevos iconos
|
||||
- (Clientes -> Razón social) Nuevas restricciones por pais
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
## [2328.01] - 2023-07-13
|
||||
|
||||
### Added
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
const axios = require('axios');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('checkFile', {
|
||||
Self.remoteMethod('checkFile', {
|
||||
description: 'Check if exist docuware file',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
|
@ -17,12 +15,16 @@ module.exports = Self => {
|
|||
required: true,
|
||||
description: 'The fileCabinet name'
|
||||
},
|
||||
{
|
||||
arg: 'filter',
|
||||
type: 'object',
|
||||
description: 'The filter'
|
||||
},
|
||||
{
|
||||
arg: 'signed',
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
description: 'If pdf is necessary to be signed'
|
||||
}
|
||||
},
|
||||
],
|
||||
returns: {
|
||||
type: 'object',
|
||||
|
@ -34,7 +36,7 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.checkFile = async function(ctx, id, fileCabinet, signed) {
|
||||
Self.checkFile = async function(id, fileCabinet, filter, signed) {
|
||||
const models = Self.app.models;
|
||||
const action = 'find';
|
||||
|
||||
|
@ -45,39 +47,34 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
const searchFilter = {
|
||||
condition: [
|
||||
{
|
||||
DBName: docuwareInfo.findById,
|
||||
|
||||
Value: [id]
|
||||
}
|
||||
],
|
||||
sortOrder: [
|
||||
{
|
||||
Field: 'FILENAME',
|
||||
Direction: 'Desc'
|
||||
}
|
||||
]
|
||||
};
|
||||
if (!filter) {
|
||||
filter = {
|
||||
condition: [
|
||||
{
|
||||
DBName: docuwareInfo.findById,
|
||||
Value: [id]
|
||||
}
|
||||
],
|
||||
sortOrder: [
|
||||
{
|
||||
Field: 'FILENAME',
|
||||
Direction: 'Desc'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
if (signed) {
|
||||
filter.condition.push({
|
||||
DBName: 'ESTADO',
|
||||
Value: ['Firmado']
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const options = await Self.getOptions();
|
||||
|
||||
const fileCabinetId = await Self.getFileCabinet(fileCabinet);
|
||||
const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId);
|
||||
|
||||
const response = await axios.post(
|
||||
`${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`,
|
||||
searchFilter,
|
||||
options.headers
|
||||
);
|
||||
const [documents] = response.data.Items;
|
||||
const response = await Self.get(fileCabinet, filter);
|
||||
const [documents] = response.Items;
|
||||
if (!documents) return false;
|
||||
|
||||
const state = documents.Fields.find(field => field.FieldName == 'ESTADO');
|
||||
if (signed && state.Item != 'Firmado') return false;
|
||||
|
||||
return {id: documents.Id};
|
||||
} catch (error) {
|
||||
return false;
|
||||
|
|
|
@ -1,59 +1,6 @@
|
|||
const axios = require('axios');
|
||||
|
||||
module.exports = Self => {
|
||||
/**
|
||||
* Returns the dialog id
|
||||
*
|
||||
* @param {string} code - The fileCabinet name
|
||||
* @param {string} action - The fileCabinet name
|
||||
* @param {string} fileCabinetId - Optional The fileCabinet name
|
||||
* @return {number} - The fileCabinet id
|
||||
*/
|
||||
Self.getDialog = async(code, action, fileCabinetId) => {
|
||||
const docuwareInfo = await Self.app.models.Docuware.findOne({
|
||||
where: {
|
||||
code: code,
|
||||
action: action
|
||||
}
|
||||
});
|
||||
if (!fileCabinetId) fileCabinetId = await Self.getFileCabinet(code);
|
||||
|
||||
const options = await Self.getOptions();
|
||||
|
||||
if (!process.env.NODE_ENV)
|
||||
return Math.round();
|
||||
|
||||
const response = await axios.get(`${options.url}/FileCabinets/${fileCabinetId}/dialogs`, options.headers);
|
||||
const dialogs = response.data.Dialog;
|
||||
const dialogId = dialogs.find(dialogs => dialogs.DisplayName === docuwareInfo.dialogName).Id;
|
||||
|
||||
return dialogId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the fileCabinetId
|
||||
*
|
||||
* @param {string} code - The fileCabinet code
|
||||
* @return {number} - The fileCabinet id
|
||||
*/
|
||||
Self.getFileCabinet = async code => {
|
||||
const options = await Self.getOptions();
|
||||
const docuwareInfo = await Self.app.models.Docuware.findOne({
|
||||
where: {
|
||||
code: code
|
||||
}
|
||||
});
|
||||
|
||||
if (!process.env.NODE_ENV)
|
||||
return Math.round();
|
||||
|
||||
const fileCabinetResponse = await axios.get(`${options.url}/FileCabinets`, options.headers);
|
||||
const fileCabinets = fileCabinetResponse.data.FileCabinet;
|
||||
const fileCabinetId = fileCabinets.find(fileCabinet => fileCabinet.Name === docuwareInfo.fileCabinetName).Id;
|
||||
|
||||
return fileCabinetId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns basic headers
|
||||
*
|
||||
|
@ -75,4 +22,139 @@ module.exports = Self => {
|
|||
headers
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the dialog id
|
||||
*
|
||||
* @param {string} code - The fileCabinet name
|
||||
* @param {string} action - The fileCabinet name
|
||||
* @param {string} fileCabinetId - Optional The fileCabinet name
|
||||
* @return {number} - The fileCabinet id
|
||||
*/
|
||||
Self.getDialog = async(code, action, fileCabinetId) => {
|
||||
if (!process.env.NODE_ENV)
|
||||
return Math.floor(Math.random() + 100);
|
||||
|
||||
const docuwareInfo = await Self.app.models.Docuware.findOne({
|
||||
where: {
|
||||
code,
|
||||
action
|
||||
}
|
||||
});
|
||||
if (!fileCabinetId) fileCabinetId = await Self.getFileCabinet(code);
|
||||
|
||||
const options = await Self.getOptions();
|
||||
|
||||
const response = await axios.get(`${options.url}/FileCabinets/${fileCabinetId}/dialogs`, options.headers);
|
||||
const dialogs = response.data.Dialog;
|
||||
const dialogId = dialogs.find(dialogs => dialogs.DisplayName === docuwareInfo.dialogName).Id;
|
||||
|
||||
return dialogId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the fileCabinetId
|
||||
*
|
||||
* @param {string} code - The fileCabinet code
|
||||
* @return {number} - The fileCabinet id
|
||||
*/
|
||||
Self.getFileCabinet = async code => {
|
||||
if (!process.env.NODE_ENV)
|
||||
return Math.floor(Math.random() + 100);
|
||||
|
||||
const options = await Self.getOptions();
|
||||
const docuwareInfo = await Self.app.models.Docuware.findOne({
|
||||
where: {
|
||||
code
|
||||
}
|
||||
});
|
||||
|
||||
const fileCabinetResponse = await axios.get(`${options.url}/FileCabinets`, options.headers);
|
||||
const fileCabinets = fileCabinetResponse.data.FileCabinet;
|
||||
const fileCabinetId = fileCabinets.find(fileCabinet => fileCabinet.Name === docuwareInfo.fileCabinetName).Id;
|
||||
|
||||
return fileCabinetId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns docuware data
|
||||
*
|
||||
* @param {string} code - The fileCabinet code
|
||||
* @param {object} filter - The filter for docuware
|
||||
* @param {object} parse - The fields parsed
|
||||
* @return {object} - The data
|
||||
*/
|
||||
Self.get = async(code, filter, parse) => {
|
||||
if (!process.env.NODE_ENV) return;
|
||||
|
||||
const options = await Self.getOptions();
|
||||
const fileCabinetId = await Self.getFileCabinet(code);
|
||||
const dialogId = await Self.getDialog(code, 'find', fileCabinetId);
|
||||
|
||||
const data = await axios.post(
|
||||
`${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`,
|
||||
filter,
|
||||
options.headers
|
||||
);
|
||||
return parser(data.data, parse);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns docuware data
|
||||
*
|
||||
* @param {string} code - The fileCabinet code
|
||||
* @param {any} id - The id of docuware
|
||||
* @param {object} parse - The fields parsed
|
||||
* @return {object} - The data
|
||||
*/
|
||||
Self.getById = async(code, id, parse) => {
|
||||
if (!process.env.NODE_ENV) return;
|
||||
|
||||
const docuwareInfo = await Self.app.models.Docuware.findOne({
|
||||
fields: ['findById'],
|
||||
where: {
|
||||
code,
|
||||
action: 'find'
|
||||
}
|
||||
});
|
||||
const filter = {
|
||||
condition: [
|
||||
{
|
||||
DBName: docuwareInfo.findById,
|
||||
Value: [id]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return Self.get(code, filter, parse);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns docuware data filtered
|
||||
*
|
||||
* @param {array} data - The data
|
||||
* @param {object} parse - The fields parsed
|
||||
* @return {object} - The data parsed
|
||||
*/
|
||||
function parser(data, parse) {
|
||||
if (!(data && data.Items)) return data;
|
||||
|
||||
const parsed = [];
|
||||
for (item of data.Items) {
|
||||
const itemParsed = {};
|
||||
item.Fields.map(field => {
|
||||
if (field.ItemElementName.includes('Date')) field.Item = toDate(field.Item);
|
||||
if (!parse) return itemParsed[field.FieldLabel] = field.Item;
|
||||
if (parse[field.FieldLabel])
|
||||
itemParsed[parse[field.FieldLabel]] = field.Item;
|
||||
});
|
||||
parsed.push(itemParsed);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function toDate(value) {
|
||||
if (!value) return;
|
||||
return new Date(Number(value.substring(6, 19)));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@ const axios = require('axios');
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('download', {
|
||||
Self.remoteMethod('download', {
|
||||
description: 'Download an docuware PDF',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
|
@ -16,8 +16,12 @@ module.exports = Self => {
|
|||
{
|
||||
arg: 'fileCabinet',
|
||||
type: 'string',
|
||||
description: 'The file cabinet',
|
||||
http: {source: 'path'}
|
||||
description: 'The file cabinet'
|
||||
},
|
||||
{
|
||||
arg: 'filter',
|
||||
type: 'object',
|
||||
description: 'The filter'
|
||||
}
|
||||
],
|
||||
returns: [
|
||||
|
@ -36,14 +40,15 @@ module.exports = Self => {
|
|||
}
|
||||
],
|
||||
http: {
|
||||
path: `/:id/download/:fileCabinet`,
|
||||
path: `/:id/download`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.download = async function(ctx, id, fileCabinet) {
|
||||
Self.download = async function(id, fileCabinet, filter) {
|
||||
const models = Self.app.models;
|
||||
const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, true);
|
||||
|
||||
const docuwareFile = await models.Docuware.checkFile(id, fileCabinet, filter);
|
||||
if (!docuwareFile) throw new UserError('The DOCUWARE PDF document does not exists');
|
||||
|
||||
const fileCabinetId = await Self.getFileCabinet(fileCabinet);
|
||||
|
|
|
@ -1,57 +1,15 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const axios = require('axios');
|
||||
|
||||
describe('docuware download()', () => {
|
||||
const ticketId = 1;
|
||||
const userId = 9;
|
||||
const ctx = {
|
||||
req: {
|
||||
|
||||
accessToken: {userId: userId},
|
||||
headers: {origin: 'http://localhost:5000'},
|
||||
}
|
||||
};
|
||||
|
||||
const docuwareModel = models.Docuware;
|
||||
const fileCabinetName = 'deliveryNote';
|
||||
|
||||
beforeAll(() => {
|
||||
spyOn(docuwareModel, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
|
||||
spyOn(docuwareModel, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
|
||||
});
|
||||
|
||||
it('should return false if there are no documents', async() => {
|
||||
const response = {
|
||||
data: {
|
||||
Items: []
|
||||
}
|
||||
};
|
||||
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
|
||||
spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve({Items: []}))));
|
||||
|
||||
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false if the document is unsigned', async() => {
|
||||
const response = {
|
||||
data: {
|
||||
Items: [
|
||||
{
|
||||
Id: 1,
|
||||
Fields: [
|
||||
{
|
||||
FieldName: 'ESTADO',
|
||||
Item: 'Unsigned'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
|
||||
|
||||
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
|
||||
const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
@ -59,23 +17,21 @@ describe('docuware download()', () => {
|
|||
it('should return the document data', async() => {
|
||||
const docuwareId = 1;
|
||||
const response = {
|
||||
data: {
|
||||
Items: [
|
||||
{
|
||||
Id: docuwareId,
|
||||
Fields: [
|
||||
{
|
||||
FieldName: 'ESTADO',
|
||||
Item: 'Firmado'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Items: [
|
||||
{
|
||||
Id: docuwareId,
|
||||
Fields: [
|
||||
{
|
||||
FieldName: 'ESTADO',
|
||||
Item: 'Firmado'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
|
||||
spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve(response))));
|
||||
|
||||
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
|
||||
const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true);
|
||||
|
||||
expect(result.id).toEqual(docuwareId);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
const axios = require('axios');
|
||||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('Docuware core', () => {
|
||||
beforeAll(() => {
|
||||
process.env.NODE_ENV = 'testing';
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
delete process.env.NODE_ENV;
|
||||
});
|
||||
|
||||
describe('getOptions()', () => {
|
||||
it('should return url and headers', async() => {
|
||||
const result = await models.Docuware.getOptions();
|
||||
|
||||
expect(result.url).toBeDefined();
|
||||
expect(result.headers).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDialog()', () => {
|
||||
it('should return dialogId', async() => {
|
||||
const dialogs = {
|
||||
data: {
|
||||
Dialog: [
|
||||
{
|
||||
DisplayName: 'find',
|
||||
Id: 'getDialogTest'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs)));
|
||||
const result = await models.Docuware.getDialog('deliveryNote', 'find', 'randomFileCabinetId');
|
||||
|
||||
expect(result).toEqual('getDialogTest');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFileCabinet()', () => {
|
||||
it('should return fileCabinetId', async() => {
|
||||
const code = 'deliveryNote';
|
||||
const docuwareInfo = await models.Docuware.findOne({
|
||||
where: {
|
||||
code
|
||||
}
|
||||
});
|
||||
const dialogs = {
|
||||
data: {
|
||||
FileCabinet: [
|
||||
{
|
||||
Name: docuwareInfo.fileCabinetName,
|
||||
Id: 'getFileCabinetTest'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs)));
|
||||
const result = await models.Docuware.getFileCabinet(code);
|
||||
|
||||
expect(result).toEqual('getFileCabinetTest');
|
||||
});
|
||||
});
|
||||
|
||||
describe('get()', () => {
|
||||
it('should return data without parse', async() => {
|
||||
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
|
||||
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
|
||||
const data = {
|
||||
data: {
|
||||
id: 1
|
||||
}
|
||||
};
|
||||
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
|
||||
const result = await models.Docuware.get('deliveryNote');
|
||||
|
||||
expect(result.id).toEqual(1);
|
||||
});
|
||||
|
||||
it('should return data with parse', async() => {
|
||||
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
|
||||
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
|
||||
const data = {
|
||||
data: {
|
||||
Items: [{
|
||||
Fields: [
|
||||
{
|
||||
ItemElementName: 'integer',
|
||||
FieldLabel: 'firstRequiredField',
|
||||
Item: 1
|
||||
},
|
||||
{
|
||||
ItemElementName: 'string',
|
||||
FieldLabel: 'secondRequiredField',
|
||||
Item: 'myName'
|
||||
},
|
||||
{
|
||||
ItemElementName: 'integer',
|
||||
FieldLabel: 'notRequiredField',
|
||||
Item: 2
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
};
|
||||
const parse = {
|
||||
'firstRequiredField': 'id',
|
||||
'secondRequiredField': 'name',
|
||||
};
|
||||
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
|
||||
const [result] = await models.Docuware.get('deliveryNote', null, parse);
|
||||
|
||||
expect(result.id).toEqual(1);
|
||||
expect(result.name).toEqual('myName');
|
||||
expect(result.notRequiredField).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getById()', () => {
|
||||
it('should return data', async() => {
|
||||
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
|
||||
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
|
||||
const data = {
|
||||
data: {
|
||||
id: 1
|
||||
}
|
||||
};
|
||||
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
|
||||
const result = await models.Docuware.getById('deliveryNote', 1);
|
||||
|
||||
expect(result.id).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -39,7 +39,7 @@ describe('docuware download()', () => {
|
|||
spyOn(docuwareModel, 'checkFile').and.returnValue({});
|
||||
spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true}));
|
||||
|
||||
const result = await models.Docuware.download(ctx, ticketId, fileCabinetName);
|
||||
const result = await models.Docuware.download(ticketId, fileCabinetName);
|
||||
|
||||
expect(result[1]).toEqual('application/pdf');
|
||||
expect(result[2]).toEqual(`filename="${ticketId}.pdf"`);
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('addAlias', {
|
||||
description: 'Add an alias if the user has the grant',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'ctx',
|
||||
type: 'Object',
|
||||
http: {source: 'context'}
|
||||
},
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'The user id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'mailAlias',
|
||||
type: 'number',
|
||||
description: 'The new alias for user',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: `/:id/addAlias`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.addAlias = async function(ctx, id, mailAlias, options) {
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions);
|
||||
|
||||
if (!user.hasGrant)
|
||||
throw new UserError(`You don't have grant privilege`);
|
||||
|
||||
const account = await models.Account.findById(userId, {
|
||||
fields: ['id'],
|
||||
include: {
|
||||
relation: 'aliases',
|
||||
scope: {
|
||||
fields: ['mailAlias']
|
||||
}
|
||||
}
|
||||
}, myOptions);
|
||||
|
||||
const aliases = account.aliases().map(alias => alias.mailAlias);
|
||||
|
||||
const hasAlias = aliases.includes(mailAlias);
|
||||
if (!hasAlias)
|
||||
throw new UserError(`You cannot assign an alias that you are not assigned to`);
|
||||
|
||||
return models.MailAliasAccount.create({
|
||||
mailAlias: mailAlias,
|
||||
account: id
|
||||
}, myOptions);
|
||||
};
|
||||
};
|
|
@ -1,55 +0,0 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('removeAlias', {
|
||||
description: 'Remove alias if the user has the grant',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'ctx',
|
||||
type: 'Object',
|
||||
http: {source: 'context'}
|
||||
},
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'The user id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'mailAlias',
|
||||
type: 'number',
|
||||
description: 'The alias to delete',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: `/:id/removeAlias`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.removeAlias = async function(ctx, id, mailAlias, options) {
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const canRemoveAlias = await models.ACL.checkAccessAcl(ctx, 'VnUser', 'canRemoveAlias', 'WRITE');
|
||||
|
||||
if (userId != id && !canRemoveAlias) throw new UserError(`You don't have grant privilege`);
|
||||
|
||||
const mailAliasAccount = await models.MailAliasAccount.findOne({
|
||||
where: {
|
||||
mailAlias: mailAlias,
|
||||
account: id
|
||||
}
|
||||
}, myOptions);
|
||||
|
||||
await mailAliasAccount.destroy(myOptions);
|
||||
};
|
||||
};
|
|
@ -18,6 +18,9 @@
|
|||
},
|
||||
"expired": {
|
||||
"type": "date"
|
||||
},
|
||||
"supplierAccountFk": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"scope": {
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
},
|
||||
"isUeeMember": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isSocialNameUnique": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
|
@ -39,4 +42,4 @@
|
|||
"permission": "ALLOW"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,5 +28,12 @@
|
|||
"findById": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"dmsType": {
|
||||
"type": "belongsTo",
|
||||
"model": "DmsType",
|
||||
"foreignKey": "dmsTypeFk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,6 @@ module.exports = function(Self) {
|
|||
require('../methods/vn-user/privileges')(Self);
|
||||
require('../methods/vn-user/validate-auth')(Self);
|
||||
require('../methods/vn-user/renew-token')(Self);
|
||||
require('../methods/vn-user/addAlias')(Self);
|
||||
require('../methods/vn-user/removeAlias')(Self);
|
||||
|
||||
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('VnUser', 'addAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee');
|
||||
|
||||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('VnUser', 'removeAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee');
|
||||
|
||||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('VnUser', 'canRemoveAlias', 'WRITE', 'ALLOW', 'ROLE', 'itManagement');
|
|
@ -0,0 +1,8 @@
|
|||
DELETE FROM `salix`.`ACL` WHERE model = 'MailAliasAccount';
|
||||
|
||||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('MailAliasAccount', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('MailAliasAccount', 'create', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('MailAliasAccount', 'deleteById', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('MailAliasAccount', 'canEditAlias', 'WRITE', 'ALLOW', 'ROLE', 'itManagement');
|
|
@ -0,0 +1,4 @@
|
|||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('WorkerDisableExcluded', '*', 'READ', 'ALLOW', 'ROLE', 'itManagement'),
|
||||
('WorkerDisableExcluded', '*', 'WRITE', 'ALLOW', 'ROLE', 'itManagement');
|
|
@ -0,0 +1,15 @@
|
|||
CREATE TABLE `vn`.`clientSms` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`clientFk` int(11) NOT NULL,
|
||||
`smsFk` mediumint(8) unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `clientSms_FK` (`clientFk`),
|
||||
KEY `clientSms_FK_1` (`smsFk`),
|
||||
CONSTRAINT `clientSms_FK` FOREIGN KEY (`clientFk`) REFERENCES `client` (`id`) ON UPDATE CASCADE,
|
||||
CONSTRAINT `clientSms_FK_1` FOREIGN KEY (`smsFk`) REFERENCES `sms` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||
VALUES
|
||||
('ClientSms', 'find', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('ClientSms', 'create', 'WRITE', 'ALLOW', 'ROLE', 'employee');
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE `vn`.`company` MODIFY COLUMN sage200Company int(2) DEFAULT 10 NOT NULL;
|
|
@ -0,0 +1,3 @@
|
|||
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
|
||||
VALUES
|
||||
('Vehicle','sorted','WRITE','ALLOW','employee');
|
|
@ -0,0 +1,64 @@
|
|||
|
||||
DELIMITER $$
|
||||
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemShelving_inventory`(vParkingFromFk VARCHAR(8), vParkingToFk VARCHAR(8))
|
||||
BEGIN
|
||||
/**
|
||||
* Devuelve un listado de ubicaciones a revisar
|
||||
*
|
||||
* @param vParkingFromFk Parking de partida, identificador de parking
|
||||
* @param vParkingToFk Parking de llegada, identificador de parking
|
||||
*/
|
||||
|
||||
DECLARE vSectorFk INT;
|
||||
DECLARE vPickingOrderFrom INT;
|
||||
DECLARE vPickingOrderTo INT;
|
||||
|
||||
SELECT p.sectorFk, p.pickingOrder INTO vSectorFk, vPickingOrderFrom
|
||||
FROM parking p
|
||||
WHERE p.code = vParkingFromFk COLLATE 'utf8mb3_general_ci';
|
||||
|
||||
SELECT p.pickingOrder INTO vPickingOrderTo
|
||||
FROM parking p
|
||||
WHERE p.code = vParkingToFk COLLATE 'utf8mb3_general_ci';
|
||||
|
||||
CALL visible_getMisfit(vSectorFk);
|
||||
|
||||
SELECT ish.id,
|
||||
p.pickingOrder,
|
||||
p.code parking,
|
||||
ish.shelvingFk,
|
||||
ish.itemFk,
|
||||
i.longName,
|
||||
ish.visible,
|
||||
p.sectorFk,
|
||||
it.workerFk buyer,
|
||||
CONCAT('http:',ic.url, '/catalog/1600x900/',i.image) urlImage,
|
||||
ish.isChecked,
|
||||
CASE
|
||||
WHEN s.notPrepared > sm.parked THEN 0
|
||||
WHEN sm.visible > sm.parked THEN 1
|
||||
ELSE 2
|
||||
END priority
|
||||
FROM itemShelving ish
|
||||
JOIN item i ON i.id = ish.itemFk
|
||||
JOIN itemType it ON it.id = i.typeFk
|
||||
JOIN tmp.stockMisfit sm ON sm.itemFk = ish.itemFk
|
||||
JOIN shelving sh ON sh.code = ish.shelvingFk
|
||||
JOIN parking p ON p.id = sh.parkingFk
|
||||
JOIN (SELECT s.itemFk, sum(s.quantity) notPrepared
|
||||
FROM sale s
|
||||
JOIN ticket t ON t.id = s.ticketFk
|
||||
JOIN warehouse w ON w.id = t.warehouseFk
|
||||
JOIN config c ON c.mainWarehouseFk = w.id
|
||||
WHERE t.shipped BETWEEN util.VN_CURDATE()
|
||||
AND util.dayEnd(util.VN_CURDATE())
|
||||
AND s.isPicked = FALSE
|
||||
GROUP BY s.itemFk) s ON s.itemFk = i.id
|
||||
JOIN hedera.imageConfig ic
|
||||
WHERE p.pickingOrder BETWEEN vPickingOrderFrom AND vPickingOrderTo
|
||||
AND p.sectorFk = vSectorFk
|
||||
ORDER BY p.pickingOrder;
|
||||
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE `vn`.`country`
|
||||
ADD COLUMN `isSocialNameUnique` tinyint(1) NOT NULL DEFAULT 1;
|
|
@ -6,5 +6,3 @@ ALTER TABLE `vn`.`roadmap` CHANGE name name varchar(45) CHARACTER SET utf8mb3 CO
|
|||
ALTER TABLE `vn`.`roadmap` MODIFY COLUMN etd datetime NOT NULL;
|
||||
|
||||
ALTER TABLE `vn`.`expeditionTruck` COMMENT='Distintas paradas que hacen los trocales';
|
||||
ALTER TABLE `vn`.`expeditionTruck` DROP FOREIGN KEY expeditionTruck_FK_2;
|
||||
ALTER TABLE `vn`.`expeditionTruck` ADD CONSTRAINT expeditionTruck_FK_2 FOREIGN KEY (roadmapFk) REFERENCES vn.roadmap(id) ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -0,0 +1,2 @@
|
|||
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`)
|
||||
VALUES ('Ticket','transferClient','WRITE','ALLOW','ROLE','administrative');
|
|
@ -0,0 +1,10 @@
|
|||
ALTER TABLE `vn`.`docuware` ADD dmsTypeFk INT(11) DEFAULT NULL NULL;
|
||||
ALTER TABLE `vn`.`docuware` ADD CONSTRAINT docuware_FK FOREIGN KEY (dmsTypeFk) REFERENCES `vn`.`dmsType`(id) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
INSERT INTO `vn`.`docuware` (code, fileCabinetName, `action`, dialogName, findById, dmsTypeFk)
|
||||
VALUES
|
||||
('hr', 'RRHH', 'find', 'Búsqueda', 'N__DOCUMENTO', NULL); -- set dmsTypeFk 3 when deploy in production
|
||||
|
||||
INSERT INTO `salix`.`url` (appName, environment, url)
|
||||
VALUES
|
||||
('docuware', 'production', 'https://verdnatura.docuware.cloud/DocuWare/Platform/');
|
||||
|
|
@ -37,7 +37,7 @@ ALTER TABLE `vn`.`ticket` AUTO_INCREMENT = 1;
|
|||
|
||||
INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`)
|
||||
VALUES
|
||||
('DEFAULT_TOKEN', '1209600', util.VN_CURDATE(), 66);
|
||||
('DEFAULT_TOKEN', '1209600', CURDATE(), 66);
|
||||
|
||||
INSERT INTO `salix`.`printConfig` (`id`, `itRecipient`, `incidencesEmail`)
|
||||
VALUES
|
||||
|
@ -2953,3 +2953,8 @@ INSERT INTO `vn`.`invoiceInSerial` (`code`, `description`, `cplusTerIdNifFk`, `t
|
|||
('E', 'Midgard', 1, 'CEE'),
|
||||
('R', 'Jotunheim', 1, 'NATIONAL'),
|
||||
('W', 'Vanaheim', 1, 'WORLD');
|
||||
|
||||
|
||||
INSERT INTO `hedera`.`imageConfig` (`id`, `maxSize`, `useXsendfile`, `url`)
|
||||
VALUES
|
||||
(1, 0, 0, 'marvel.com');
|
||||
|
|
|
@ -77831,7 +77831,7 @@ BEGIN
|
|||
LEAVE cur1Loop;
|
||||
END IF;
|
||||
|
||||
CALL zone_getLeaves2(vZoneFk, NULL, NULL);
|
||||
CALL zone_getLeaves(vZoneFk, NULL, NULL, TRUE);
|
||||
|
||||
myLoop: LOOP
|
||||
SET vGeoFk = NULL;
|
||||
|
@ -77844,7 +77844,7 @@ BEGIN
|
|||
LEAVE myLoop;
|
||||
END IF;
|
||||
|
||||
CALL zone_getLeaves2(vZoneFk, vGeoFk, NULL);
|
||||
CALL zone_getLeaves(vZoneFk, vGeoFk, NULL, TRUE);
|
||||
UPDATE tmp.zoneNodes
|
||||
SET isChecked = TRUE
|
||||
WHERE geoFk = vGeoFk;
|
||||
|
@ -78130,55 +78130,58 @@ DELIMITER ;
|
|||
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
|
||||
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
|
||||
DELIMITER ;;
|
||||
CREATE DEFINER=`root`@`localhost` PROCEDURE `zone_getLeaves`(vSelf INT, vParentFk INT, vSearch VARCHAR(255))
|
||||
BEGIN
|
||||
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getLeaves`(
|
||||
vSelf INT,
|
||||
vParentFk INT,
|
||||
vSearch VARCHAR(255),
|
||||
vHasInsert BOOL
|
||||
)
|
||||
BEGIN
|
||||
/**
|
||||
* Devuelve las ubicaciones incluidas en la ruta y que sean hijos de parentFk.
|
||||
* @param vSelf Id de la zona
|
||||
* @param vParentFk Id del geo a calcular
|
||||
* @param vSearch cadena a buscar
|
||||
* @param vSearch Cadena a buscar
|
||||
* @param vHasInsert Indica si inserta en tmp.zoneNodes
|
||||
* Optional @table tmp.zoneNodes(geoFk, name, parentFk, sons, isChecked, zoneFk)
|
||||
*/
|
||||
DECLARE vIsNumber BOOL;
|
||||
DECLARE vIsSearch BOOL DEFAULT vSearch IS NOT NULL AND vSearch != '';
|
||||
DECLARE vIsSearch BOOL DEFAULT vSearch IS NOT NULL AND vSearch <> '';
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tNodes;
|
||||
CREATE TEMPORARY TABLE tNodes
|
||||
CREATE OR REPLACE TEMPORARY TABLE tNodes
|
||||
(UNIQUE (id))
|
||||
ENGINE = MEMORY
|
||||
SELECT id
|
||||
FROM zoneGeo
|
||||
SELECT id
|
||||
FROM zoneGeo
|
||||
LIMIT 0;
|
||||
|
||||
IF vIsSearch THEN
|
||||
SET vIsNumber = vSearch REGEXP '^[0-9]+$';
|
||||
|
||||
|
||||
INSERT INTO tNodes
|
||||
SELECT id
|
||||
SELECT id
|
||||
FROM zoneGeo
|
||||
WHERE (vIsNumber AND `name` = vSearch)
|
||||
OR (!vIsNumber AND `name` LIKE CONCAT('%', vSearch, '%'))
|
||||
LIMIT 1000;
|
||||
|
||||
|
||||
ELSEIF vParentFk IS NULL THEN
|
||||
INSERT INTO tNodes
|
||||
SELECT geoFk
|
||||
SELECT geoFk
|
||||
FROM zoneIncluded
|
||||
WHERE zoneFk = vSelf;
|
||||
END IF;
|
||||
|
||||
IF vParentFk IS NULL THEN
|
||||
DROP TEMPORARY TABLE IF EXISTS tChilds;
|
||||
CREATE TEMPORARY TABLE tChilds
|
||||
CREATE OR REPLACE TEMPORARY TABLE tChilds
|
||||
(INDEX(id))
|
||||
ENGINE = MEMORY
|
||||
SELECT id
|
||||
FROM tNodes;
|
||||
SELECT id FROM tNodes;
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tParents;
|
||||
CREATE TEMPORARY TABLE tParents
|
||||
CREATE OR REPLACE TEMPORARY TABLE tParents
|
||||
(INDEX(id))
|
||||
ENGINE = MEMORY
|
||||
SELECT id
|
||||
FROM zoneGeo
|
||||
LIMIT 0;
|
||||
SELECT id FROM zoneGeo LIMIT 0;
|
||||
|
||||
myLoop: LOOP
|
||||
DELETE FROM tParents;
|
||||
|
@ -78186,43 +78189,67 @@ BEGIN
|
|||
SELECT parentFk id
|
||||
FROM zoneGeo g
|
||||
JOIN tChilds c ON c.id = g.id
|
||||
WHERE g.parentFk IS NOT NULL;
|
||||
|
||||
WHERE g.parentFk IS NOT NULL;
|
||||
|
||||
INSERT IGNORE INTO tNodes
|
||||
SELECT id
|
||||
FROM tParents;
|
||||
|
||||
IF ROW_COUNT() = 0 THEN
|
||||
SELECT id FROM tParents;
|
||||
|
||||
IF NOT ROW_COUNT() THEN
|
||||
LEAVE myLoop;
|
||||
END IF;
|
||||
|
||||
|
||||
DELETE FROM tChilds;
|
||||
INSERT INTO tChilds
|
||||
SELECT id
|
||||
FROM tParents;
|
||||
SELECT id FROM tParents;
|
||||
END LOOP;
|
||||
|
||||
|
||||
DROP TEMPORARY TABLE tChilds, tParents;
|
||||
END IF;
|
||||
|
||||
IF !vIsSearch THEN
|
||||
IF NOT vIsSearch THEN
|
||||
INSERT IGNORE INTO tNodes
|
||||
SELECT id
|
||||
SELECT id
|
||||
FROM zoneGeo
|
||||
WHERE parentFk <=> vParentFk;
|
||||
END IF;
|
||||
|
||||
SELECT g.id,
|
||||
g.name,
|
||||
g.parentFk,
|
||||
g.sons,
|
||||
isIncluded selected
|
||||
FROM zoneGeo g
|
||||
JOIN tNodes n ON n.id = g.id
|
||||
LEFT JOIN zoneIncluded i ON i.geoFk = g.id AND i.zoneFk = vSelf
|
||||
ORDER BY `depth`, selected DESC, name;
|
||||
CREATE OR REPLACE TEMPORARY TABLE tZones
|
||||
SELECT g.id,
|
||||
g.name,
|
||||
g.parentFk,
|
||||
g.sons,
|
||||
NOT g.sons OR `type` = 'country' isChecked,
|
||||
i.isIncluded selected,
|
||||
g.`depth`,
|
||||
vSelf
|
||||
FROM zoneGeo g
|
||||
JOIN tNodes n ON n.id = g.id
|
||||
LEFT JOIN zoneIncluded i ON i.geoFk = g.id
|
||||
AND i.zoneFk = vSelf
|
||||
ORDER BY g.`depth`, selected DESC, g.name;
|
||||
|
||||
DROP TEMPORARY TABLE tNodes;
|
||||
IF vHasInsert THEN
|
||||
INSERT IGNORE INTO tmp.zoneNodes(geoFk, name, parentFk, sons, isChecked, zoneFk)
|
||||
SELECT id,
|
||||
name,
|
||||
parentFk,
|
||||
sons,
|
||||
isChecked,
|
||||
vSelf
|
||||
FROM tZones
|
||||
WHERE selected
|
||||
OR (selected IS NULL AND vParentFk IS NOT NULL);
|
||||
ELSE
|
||||
SELECT id,
|
||||
name,
|
||||
parentFk,
|
||||
sons,
|
||||
selected
|
||||
FROM tZones
|
||||
ORDER BY `depth`, selected DESC, name;
|
||||
END IF;
|
||||
|
||||
DROP TEMPORARY TABLE tNodes, tZones;
|
||||
END ;;
|
||||
DELIMITER ;
|
||||
/*!50003 SET sql_mode = @saved_sql_mode */ ;
|
||||
|
@ -78540,7 +78567,7 @@ BEGIN
|
|||
INDEX(geoFk))
|
||||
ENGINE = MEMORY;
|
||||
|
||||
CALL zone_getLeaves2(vSelf, NULL , NULL);
|
||||
CALL zone_getLeaves(vSelf, NULL , NULL, TRUE);
|
||||
|
||||
UPDATE tmp.zoneNodes zn
|
||||
SET isChecked = 0
|
||||
|
@ -78553,7 +78580,7 @@ BEGIN
|
|||
WHERE NOT isChecked
|
||||
LIMIT 1;
|
||||
|
||||
CALL zone_getLeaves2(vSelf, vGeoFk, NULL);
|
||||
CALL zone_getLeaves(vSelf, vGeoFk, NULL, TRUE);
|
||||
UPDATE tmp.zoneNodes
|
||||
SET isChecked = TRUE
|
||||
WHERE geoFk = vGeoFk;
|
||||
|
|
|
@ -66,7 +66,11 @@ export default class App {
|
|||
|
||||
return this.logger.$http.get('Urls/findOne', {filter})
|
||||
.then(res => {
|
||||
return res.data.url + route;
|
||||
if (res && res.data)
|
||||
return res.data.url + route;
|
||||
})
|
||||
.catch(() => {
|
||||
this.showError('Direction not found');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,4 +3,5 @@ Could not contact the server: No se ha podido contactar con el servidor, asegura
|
|||
Please enter your username: Por favor introduce tu nombre de usuario
|
||||
It seems that the server has fall down: Parece que el servidor se ha caído, espera unos minutos e inténtalo de nuevo
|
||||
Session has expired: Tu sesión ha expirado, por favor vuelve a iniciar sesión
|
||||
Access denied: Acción no permitida
|
||||
Access denied: Acción no permitida
|
||||
Direction not found: Dirección no encontrada
|
||||
|
|
|
@ -305,5 +305,7 @@
|
|||
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado",
|
||||
"Valid priorities": "Prioridades válidas: %d",
|
||||
"Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}",
|
||||
"You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado"
|
||||
"The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias",
|
||||
"You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado",
|
||||
"This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
|
||||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.observe('before save', async ctx => {
|
||||
const changes = ctx.currentInstance || ctx.instance;
|
||||
|
||||
await Self.hasGrant(ctx, changes.mailAlias);
|
||||
});
|
||||
|
||||
Self.observe('before delete', async ctx => {
|
||||
const mailAliasAccount = await Self.findById(ctx.where.id);
|
||||
|
||||
await Self.hasGrant(ctx, mailAliasAccount.mailAlias);
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks if current user has
|
||||
* grant to add/remove alias
|
||||
*
|
||||
* @param {Object} ctx - Request context
|
||||
* @param {Interger} mailAlias - mailAlias id
|
||||
* @return {Boolean} True for user with grant
|
||||
*/
|
||||
Self.hasGrant = async function(ctx, mailAlias) {
|
||||
const models = Self.app.models;
|
||||
const accessToken = {req: {accessToken: ctx.options.accessToken}};
|
||||
const userId = accessToken.req.accessToken.userId;
|
||||
|
||||
const canEditAlias = await models.ACL.checkAccessAcl(accessToken, 'MailAliasAccount', 'canEditAlias', 'WRITE');
|
||||
if (canEditAlias) return true;
|
||||
|
||||
const user = await models.VnUser.findById(userId, {fields: ['hasGrant']});
|
||||
if (!user.hasGrant)
|
||||
throw new UserError(`You don't have grant privilege`);
|
||||
|
||||
const account = await models.Account.findById(userId, {
|
||||
fields: ['id'],
|
||||
include: {
|
||||
relation: 'aliases',
|
||||
scope: {
|
||||
fields: ['mailAlias']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const aliases = account.aliases().map(alias => alias.mailAlias);
|
||||
|
||||
const hasAlias = aliases.includes(mailAlias);
|
||||
if (!hasAlias)
|
||||
throw new UserError(`You cannot assign/remove an alias that you are not assigned to`);
|
||||
|
||||
return true;
|
||||
};
|
||||
};
|
|
@ -21,11 +21,12 @@ export default class Controller extends Section {
|
|||
}
|
||||
|
||||
onAddClick() {
|
||||
this.addData = {account: this.$params.id};
|
||||
this.$.dialog.show();
|
||||
}
|
||||
|
||||
onAddSave() {
|
||||
return this.$http.post(`VnUsers/${this.$params.id}/addAlias`, this.addData)
|
||||
return this.$http.post(`MailAliasAccounts`, this.addData)
|
||||
.then(() => this.refresh())
|
||||
.then(() => this.vnApp.showSuccess(
|
||||
this.$t('Subscribed to alias!'))
|
||||
|
@ -33,12 +34,11 @@ export default class Controller extends Section {
|
|||
}
|
||||
|
||||
onRemove(row) {
|
||||
const params = {
|
||||
mailAlias: row.mailAlias
|
||||
};
|
||||
return this.$http.post(`VnUsers/${this.$params.id}/removeAlias`, params)
|
||||
.then(() => this.refresh())
|
||||
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
|
||||
return this.$http.delete(`MailAliasAccounts/${row.id}`)
|
||||
.then(() => {
|
||||
this.$.data.splice(this.$.data.indexOf(row), 1);
|
||||
this.vnApp.showSuccess(this.$t('Unsubscribed from alias!'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,9 +25,8 @@ describe('component vnUserAliases', () => {
|
|||
describe('onAddSave()', () => {
|
||||
it('should add the new row', () => {
|
||||
controller.addData = {account: 1};
|
||||
controller.$params = {id: 1};
|
||||
|
||||
$httpBackend.expectPOST('VnUsers/1/addAlias').respond();
|
||||
$httpBackend.expectPOST('MailAliasAccounts').respond();
|
||||
$httpBackend.expectGET('MailAliasAccounts').respond('foo');
|
||||
controller.onAddSave();
|
||||
$httpBackend.flush();
|
||||
|
@ -42,14 +41,12 @@ describe('component vnUserAliases', () => {
|
|||
{id: 1, alias: 'foo'},
|
||||
{id: 2, alias: 'bar'}
|
||||
];
|
||||
controller.$params = {id: 1};
|
||||
|
||||
$httpBackend.expectPOST('VnUsers/1/removeAlias').respond();
|
||||
$httpBackend.expectGET('MailAliasAccounts').respond(controller.$.data[1]);
|
||||
$httpBackend.expectDELETE('MailAliasAccounts/1').respond();
|
||||
controller.onRemove(controller.$.data[0]);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.$.data).toEqual({id: 2, alias: 'bar'});
|
||||
expect(controller.$.data).toEqual([{id: 2, alias: 'bar'}]);
|
||||
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = function(Self) {
|
||||
Self.remoteMethodCtx('canBeInvoiced', {
|
||||
description: 'Change property isEqualizated in all client addresses',
|
||||
|
@ -9,6 +11,12 @@ module.exports = function(Self) {
|
|||
required: true,
|
||||
description: 'Client id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'companyFk',
|
||||
description: 'The company id',
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
|
@ -22,18 +30,29 @@ module.exports = function(Self) {
|
|||
}
|
||||
});
|
||||
|
||||
Self.canBeInvoiced = async(id, options) => {
|
||||
Self.canBeInvoiced = async(id, companyFk, options) => {
|
||||
const models = Self.app.models;
|
||||
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const client = await models.Client.findById(id, {
|
||||
fields: ['id', 'isTaxDataChecked', 'hasToInvoice']
|
||||
fields: ['id', 'isTaxDataChecked', 'hasToInvoice', 'payMethodFk'],
|
||||
include:
|
||||
{
|
||||
relation: 'payMethod',
|
||||
scope: {
|
||||
fields: ['code']
|
||||
}
|
||||
}
|
||||
}, myOptions);
|
||||
|
||||
const company = await models.Company.findById(companyFk, {fields: ['supplierAccountFk']}, myOptions);
|
||||
|
||||
if (client.payMethod().code === 'wireTransfer' && !company.supplierAccountFk)
|
||||
throw new UserError('The company has not informed the supplier account for bank transfers');
|
||||
|
||||
if (client.isTaxDataChecked && client.hasToInvoice)
|
||||
return true;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ module.exports = Self => {
|
|||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'The ticket id',
|
||||
description: 'The client id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
|
@ -33,6 +33,12 @@ module.exports = Self => {
|
|||
Self.sendSms = async(ctx, id, destination, message) => {
|
||||
const models = Self.app.models;
|
||||
const sms = await models.Sms.send(ctx, destination, message);
|
||||
|
||||
await models.ClientSms.create({
|
||||
clientFk: id,
|
||||
smsFk: sms.id
|
||||
});
|
||||
|
||||
return sms;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ const LoopBackContext = require('loopback-context');
|
|||
describe('client canBeInvoiced()', () => {
|
||||
const userId = 19;
|
||||
const clientId = 1101;
|
||||
const companyId = 442;
|
||||
const activeCtx = {
|
||||
accessToken: {userId: userId}
|
||||
};
|
||||
|
@ -23,7 +24,7 @@ describe('client canBeInvoiced()', () => {
|
|||
const client = await models.Client.findById(clientId, null, options);
|
||||
await client.updateAttribute('isTaxDataChecked', false, options);
|
||||
|
||||
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options);
|
||||
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options);
|
||||
|
||||
expect(canBeInvoiced).toEqual(false);
|
||||
|
||||
|
@ -43,7 +44,7 @@ describe('client canBeInvoiced()', () => {
|
|||
const client = await models.Client.findById(clientId, null, options);
|
||||
await client.updateAttribute('hasToInvoice', false, options);
|
||||
|
||||
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options);
|
||||
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options);
|
||||
|
||||
expect(canBeInvoiced).toEqual(false);
|
||||
|
||||
|
@ -60,7 +61,7 @@ describe('client canBeInvoiced()', () => {
|
|||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options);
|
||||
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options);
|
||||
|
||||
expect(canBeInvoiced).toEqual(true);
|
||||
|
||||
|
|
|
@ -70,11 +70,12 @@ module.exports = Self => {
|
|||
c.creditInsurance,
|
||||
d.defaulterSinced,
|
||||
cn.country,
|
||||
c.countryFk,
|
||||
pm.name payMethod
|
||||
FROM vn.defaulter d
|
||||
JOIN vn.client c ON c.id = d.clientFk
|
||||
JOIN vn.country cn ON cn.id = c.countryFk
|
||||
JOIN vn.payMethod pm ON pm.id = c.payMethodFk
|
||||
JOIN vn.payMethod pm ON pm.id = c.payMethodFk
|
||||
LEFT JOIN vn.clientObservation co ON co.clientFk = c.id
|
||||
LEFT JOIN account.user u ON u.id = c.salesPersonFk
|
||||
LEFT JOIN account.user uw ON uw.id = co.workerFk
|
||||
|
|
|
@ -95,6 +95,9 @@
|
|||
"ClientSample": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ClientSms": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Sms": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "ClientSms",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "clientSms"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"clientFk": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"sms": {
|
||||
"type": "belongsTo",
|
||||
"model": "Sms",
|
||||
"foreignKey": "smsFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,7 +41,18 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
async function socialNameIsUnique(err, done) {
|
||||
if (!this.countryFk)
|
||||
return done();
|
||||
|
||||
const filter = {
|
||||
include: {
|
||||
relation: 'country',
|
||||
scope: {
|
||||
fields: {
|
||||
isSocialNameUnique: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where: {
|
||||
and: [
|
||||
{socialName: this.socialName},
|
||||
|
@ -50,9 +61,13 @@ module.exports = Self => {
|
|||
]
|
||||
}
|
||||
};
|
||||
const client = await Self.app.models.Client.findOne(filter);
|
||||
if (client)
|
||||
|
||||
const client = await Self.app.models.Country.findById(this.countryFk, {fields: ['isSocialNameUnique']});
|
||||
const existingClient = await Self.findOne(filter);
|
||||
|
||||
if (existingClient && (existingClient.country().isSocialNameUnique || client.isSocialNameUnique))
|
||||
err();
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
|
|
|
@ -181,6 +181,11 @@
|
|||
"model": "Country",
|
||||
"foreignKey": "countryFk"
|
||||
},
|
||||
"isSocialNameUnique": {
|
||||
"type": "belongsTo",
|
||||
"model": "Country",
|
||||
"foreignKey": "countryFk"
|
||||
},
|
||||
"contactChannel": {
|
||||
"type": "belongsTo",
|
||||
"model": "ContactChannel",
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"country": {
|
||||
"type": "belongsTo",
|
||||
"model": "Country",
|
||||
"foreignKey": "country"
|
||||
"foreignKey": "countryFk"
|
||||
},
|
||||
"payMethod": {
|
||||
"type": "belongsTo",
|
||||
|
@ -41,4 +41,4 @@
|
|||
"foreignKey": "payMethod"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
<th field="salesPersonFk">
|
||||
<span translate>Comercial</span>
|
||||
</th>
|
||||
<th field="country">
|
||||
<th field="countryFk">
|
||||
<span translate>Country</span>
|
||||
</th>
|
||||
<th field="payMethod"
|
||||
|
@ -132,7 +132,7 @@
|
|||
</td>
|
||||
<td>
|
||||
{{::defaulter.payMethod}}
|
||||
</td>
|
||||
</td>
|
||||
<td>{{::defaulter.amount | currency: 'EUR': 2}}</td>
|
||||
<td>
|
||||
<span
|
||||
|
|
|
@ -31,10 +31,11 @@ export default class Controller extends Section {
|
|||
valueField: 'id',
|
||||
}
|
||||
}, {
|
||||
field: 'country',
|
||||
field: 'countryFk',
|
||||
autocomplete: {
|
||||
url: 'Countries',
|
||||
showField: 'country',
|
||||
valueField: 'country'
|
||||
valueField: 'id'
|
||||
}
|
||||
}, {
|
||||
field: 'payMethodFk',
|
||||
|
@ -167,7 +168,7 @@ export default class Controller extends Section {
|
|||
case 'amount':
|
||||
case 'clientFk':
|
||||
case 'workerFk':
|
||||
case 'country':
|
||||
case 'countryFk':
|
||||
case 'payMethod':
|
||||
case 'salesPersonFk':
|
||||
return {[`d.${param}`]: value};
|
||||
|
|
|
@ -48,4 +48,5 @@ import './notification';
|
|||
import './unpaid';
|
||||
import './extended-list';
|
||||
import './credit-management';
|
||||
import './sms';
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
{"state": "client.card.recovery.index", "icon": "icon-recovery"},
|
||||
{"state": "client.card.webAccess", "icon": "cloud"},
|
||||
{"state": "client.card.log", "icon": "history"},
|
||||
{"state": "client.card.sms", "icon": "sms"},
|
||||
{
|
||||
"description": "Credit management",
|
||||
"icon": "monetization_on",
|
||||
|
@ -373,6 +374,12 @@
|
|||
"component": "vn-client-log",
|
||||
"description": "Log"
|
||||
},
|
||||
{
|
||||
"url" : "/sms",
|
||||
"state": "client.card.sms",
|
||||
"component": "vn-client-sms",
|
||||
"description": "Sms"
|
||||
},
|
||||
{
|
||||
"url": "/dms",
|
||||
"state": "client.card.dms",
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="ClientSms"
|
||||
link="{clientFk: $ctrl.$params.id}"
|
||||
filter="::$ctrl.filter"
|
||||
data="clientSmsList"
|
||||
limit="20"
|
||||
auto-load="true">
|
||||
</vn-crud-model>
|
||||
<vn-data-viewer model="model">
|
||||
<vn-card class="vn-w-md">
|
||||
<vn-table model="model" auto-load="false">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th field="senderFk">Sender</vn-th>
|
||||
<vn-th field="destination" number>Destination</vn-th>
|
||||
<vn-th field="message">Message</vn-th>
|
||||
<vn-th field="status">Status</vn-th>
|
||||
<vn-th field="created" expand>Created</vn-th>
|
||||
</vn-tr>
|
||||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr ng-repeat="clientSms in clientSmsList">
|
||||
<vn-td>
|
||||
<span class="link" ng-click="workerDescriptor.show($event, clientSms.sms.senderFk)">
|
||||
{{::clientSms.sms.sender.name}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td number expand>{{::clientSms.sms.destination}}</vn-td>
|
||||
<vn-td>{{::clientSms.sms.message}}</vn-td>
|
||||
<vn-td>{{::clientSms.sms.status}}</vn-td>
|
||||
<vn-td shrink-datetime>{{::clientSms.sms.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</vn-card>
|
||||
</vn-data-viewer>
|
||||
<vn-worker-descriptor-popover
|
||||
vn-id="worker-descriptor">
|
||||
</vn-worker-descriptor-popover>
|
|
@ -0,0 +1,39 @@
|
|||
import ngModule from '../module';
|
||||
import Section from 'salix/components/section';
|
||||
|
||||
export default class Controller extends Section {
|
||||
constructor($element, $) {
|
||||
super($element, $);
|
||||
|
||||
this.filter = {
|
||||
fields: ['id', 'smsFk'],
|
||||
include: {
|
||||
relation: 'sms',
|
||||
scope: {
|
||||
fields: [
|
||||
'senderFk',
|
||||
'sender',
|
||||
'destination',
|
||||
'message',
|
||||
'statusCode',
|
||||
'status',
|
||||
'created'],
|
||||
include: {
|
||||
relation: 'sender',
|
||||
scope: {
|
||||
fields: ['name']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.vnComponent('vnClientSms', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
client: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
Sender: Remitente
|
||||
Number sender: Número remitente
|
|
@ -1,3 +1,5 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
require('../methods/invoice-in/filter')(Self);
|
||||
require('../methods/invoice-in/summary')(Self);
|
||||
|
@ -7,4 +9,9 @@ module.exports = Self => {
|
|||
require('../methods/invoice-in/invoiceInPdf')(Self);
|
||||
require('../methods/invoice-in/invoiceInEmail')(Self);
|
||||
require('../methods/invoice-in/getSerial')(Self);
|
||||
Self.rewriteDbError(function(err) {
|
||||
if (err.code === 'ER_ROW_IS_REFERENCED_2' && err.sqlMessage.includes('vehicleInvoiceIn'))
|
||||
return new UserError(`This invoice has a linked vehicle.`);
|
||||
return err;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('getInventory', {
|
||||
description: 'Get list of itemShelving to review between two parking code',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'parkingFrom',
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Parking code from'
|
||||
},
|
||||
{
|
||||
arg: 'parkingTo',
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Parking code to'
|
||||
}],
|
||||
returns: {
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/getInventory`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.getInventory = async(parkingFrom, parkingTo, options) => {
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const [result] = await Self.rawSql(`CALL vn.itemShelving_inventory(?, ?)`, [parkingFrom, parkingTo], myOptions);
|
||||
|
||||
return result;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('itemShelving getInventory()', () => {
|
||||
it('should return a list of itemShelvings', async() => {
|
||||
const tx = await models.ItemShelving.beginTransaction({});
|
||||
|
||||
let response;
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
await models.ItemShelving.rawSql(`
|
||||
UPDATE vn.config
|
||||
SET mainWarehouseFk=1
|
||||
WHERE id=1
|
||||
`, null, options);
|
||||
response = await models.ItemShelving.getInventory('100-01', 'LR-02-3', options);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
|
||||
expect(response.length).toEqual(2);
|
||||
});
|
||||
});
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/item-shelving/deleteItemShelvings')(Self);
|
||||
require('../methods/item-shelving/getInventory')(Self);
|
||||
};
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
},
|
||||
"isChecked": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"visible": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
show-field="id"
|
||||
value-field="id"
|
||||
search-function="$ctrl.itemSearchFunc($search)"
|
||||
on-change="$ctrl.upsertPrice(price, true)"
|
||||
ng-change="$ctrl.upsertPrice(price, true)"
|
||||
order="id DESC"
|
||||
tabindex="1">
|
||||
<tpl-item>
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('sorted', {
|
||||
description: 'Sort the vehicles by warehouse',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'warehouseFk',
|
||||
type: 'number'
|
||||
}],
|
||||
returns: {
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/sorted`,
|
||||
verb: `POST`
|
||||
}
|
||||
});
|
||||
|
||||
Self.sorted = async warehouseFk => {
|
||||
return Self.rawSql(`
|
||||
SELECT v.id, v.warehouseFk, v.numberPlate, w.name
|
||||
FROM vehicle v
|
||||
JOIN warehouse w ON w.id = v.warehouseFk
|
||||
ORDER BY v.warehouseFk = ? DESC, w.id, v.numberPlate ASC;
|
||||
`, [warehouseFk]);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/vehicle/sorted')(Self);
|
||||
};
|
|
@ -23,19 +23,21 @@
|
|||
</tpl-item>
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
label="Vehicle"
|
||||
ng-model="$ctrl.route.vehicleFk"
|
||||
url="Vehicles"
|
||||
data="$ctrl.vehicles"
|
||||
show-field="numberPlate"
|
||||
value-field="id"
|
||||
label="Vehicle"
|
||||
order="false"
|
||||
vn-name="vehicle">
|
||||
<tpl-item>{{::numberPlate}} - {{::name}}</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-date-picker
|
||||
label="Created"
|
||||
ng-model="$ctrl.route.created"
|
||||
vn-name="created">
|
||||
vn-name="created">
|
||||
</vn-date-picker>
|
||||
<vn-autocomplete
|
||||
ng-model="$ctrl.route.agencyModeFk"
|
||||
|
|
|
@ -2,6 +2,13 @@ import ngModule from '../module';
|
|||
import Section from 'salix/components/section';
|
||||
|
||||
class Controller extends Section {
|
||||
$onInit() {
|
||||
this.$http.post(`Vehicles/sorted`, {warehouseFk: this.vnConfig.warehouseFk})
|
||||
.then(res => {
|
||||
this.vehicles = res.data;
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
this.$.watcher.submit().then(() =>
|
||||
this.card.reload()
|
||||
|
|
|
@ -68,9 +68,9 @@ module.exports = Self => {
|
|||
};
|
||||
const country = await Self.app.models.Country.findOne(filter);
|
||||
const code = country ? country.code.toLowerCase() : null;
|
||||
const countryCode = this.nif.toLowerCase().substring(0, 2);
|
||||
const countryCode = this.nif?.toLowerCase().substring(0, 2);
|
||||
|
||||
if (!this.nif || !validateTin(this.nif, code) || (this.isVies && countryCode == code))
|
||||
if (!validateTin(this.nif, code) || (this.isVies && countryCode == code))
|
||||
err();
|
||||
done();
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
async function hasSupplierSameName(err, done) {
|
||||
if (!this.name || !this.countryFk) done();
|
||||
if (!this.name || !this.countryFk) return done();
|
||||
const supplier = await Self.app.models.Supplier.findOne(
|
||||
{
|
||||
where: {
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('docuwareDownload', {
|
||||
description: 'Download a ticket delivery note document',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
description: 'The document id',
|
||||
http: {source: 'path'}
|
||||
}
|
||||
],
|
||||
returns: [
|
||||
{
|
||||
arg: 'body',
|
||||
type: 'file',
|
||||
root: true
|
||||
}, {
|
||||
arg: 'Content-Type',
|
||||
type: 'String',
|
||||
http: {target: 'header'}
|
||||
}, {
|
||||
arg: 'Content-Disposition',
|
||||
type: 'String',
|
||||
http: {target: 'header'}
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: `/:id/docuwareDownload`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.docuwareDownload = async id => {
|
||||
const filter = {
|
||||
condition: [
|
||||
{
|
||||
DBName: docuwareInfo.findById,
|
||||
Value: [id]
|
||||
},
|
||||
{
|
||||
DBName: 'ESTADO',
|
||||
Value: ['Firmado']
|
||||
}
|
||||
],
|
||||
sortOrder: [
|
||||
{
|
||||
Field: 'FILENAME',
|
||||
Direction: 'Desc'
|
||||
}
|
||||
]
|
||||
};
|
||||
return Self.app.models.Docuware.download(id, 'deliveryNote', filter);
|
||||
};
|
||||
};
|
|
@ -14,7 +14,7 @@ module.exports = function(Self) {
|
|||
{
|
||||
arg: 'companyFk',
|
||||
description: 'The company id',
|
||||
type: 'string',
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
|
@ -67,7 +67,7 @@ module.exports = function(Self) {
|
|||
|
||||
const [firstTicket] = tickets;
|
||||
const clientId = firstTicket.clientFk;
|
||||
const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, myOptions);
|
||||
const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, companyFk, myOptions);
|
||||
if (!clientCanBeInvoiced)
|
||||
throw new UserError(`This client can't be invoiced`);
|
||||
|
||||
|
|
|
@ -141,6 +141,7 @@ describe('ticket filter()', () => {
|
|||
});
|
||||
|
||||
it('should return the tickets that are not pending', async() => {
|
||||
pending('#6010 test intermitente');
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('Ticket transferClient()', () => {
|
||||
const userId = 9;
|
||||
const activeCtx = {
|
||||
accessToken: {userId: userId},
|
||||
};
|
||||
const ctx = {req: activeCtx};
|
||||
|
||||
it('should throw an error as the ticket is not editable', async() => {
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
let error;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
const ticketId = 4;
|
||||
const clientId = 1;
|
||||
await models.Ticket.transferClient(ctx, ticketId, clientId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`The current ticket can't be modified`);
|
||||
});
|
||||
|
||||
it('should be assigned a different clientFk', async() => {
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
let updatedTicket;
|
||||
const ticketId = 10;
|
||||
const clientId = 1;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await models.Ticket.transferClient(ctx, ticketId, clientId, options);
|
||||
updatedTicket = await models.Ticket.findById(ticketId, {fields: ['clientFk']}, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
|
||||
expect(updatedTicket.clientFk).toEqual(clientId);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,49 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('transferClient', {
|
||||
description: 'Transfering ticket to another client',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'the ticket id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'clientFk',
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
http: {
|
||||
path: `/:id/transferClient`,
|
||||
verb: 'PATCH'
|
||||
}
|
||||
});
|
||||
|
||||
Self.transferClient = async(ctx, id, clientFk, options) => {
|
||||
const models = Self.app.models;
|
||||
const myOptions = {};
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const isEditable = await Self.isEditable(ctx, id, myOptions);
|
||||
|
||||
if (!isEditable)
|
||||
throw new UserError(`The current ticket can't be modified`);
|
||||
|
||||
const ticket = await models.Ticket.findById(
|
||||
id,
|
||||
{fields: ['id', 'shipped', 'clientFk', 'addressFk']},
|
||||
myOptions
|
||||
);
|
||||
const client = await models.Client.findById(clientFk, {fields: ['id', 'defaultAddressFk']}, myOptions);
|
||||
|
||||
await ticket.updateAttributes({
|
||||
clientFk,
|
||||
addressFk: client.defaultAddressFk,
|
||||
});
|
||||
};
|
||||
};
|
|
@ -19,6 +19,7 @@ module.exports = function(Self) {
|
|||
require('../methods/ticket/uploadFile')(Self);
|
||||
require('../methods/ticket/addSale')(Self);
|
||||
require('../methods/ticket/transferSales')(Self);
|
||||
require('../methods/ticket/transferClient')(Self);
|
||||
require('../methods/ticket/recalculateComponents')(Self);
|
||||
require('../methods/ticket/sendSms')(Self);
|
||||
require('../methods/ticket/isLocked')(Self);
|
||||
|
|
|
@ -36,13 +36,12 @@
|
|||
translate>
|
||||
as PDF without prices
|
||||
</vn-item>
|
||||
<a class="vn-item"
|
||||
<vn-item
|
||||
ng-if="$ctrl.hasDocuwareFile"
|
||||
href='api/Docuwares/{{$ctrl.ticket.id}}/download/deliveryNote?access_token={{$ctrl.vnToken.token}}'
|
||||
target="_blank"
|
||||
ng-click="$ctrl.docuwareDownload()"
|
||||
translate>
|
||||
as PDF signed
|
||||
</a>
|
||||
</vn-item>
|
||||
<vn-item
|
||||
ng-click="$ctrl.showCsvDeliveryNote()"
|
||||
translate>
|
||||
|
|
|
@ -96,20 +96,14 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
transferClient() {
|
||||
this.$http.get(`Clients/${this.ticket.client.id}`).then(client => {
|
||||
const ticket = this.ticket;
|
||||
const ticket = this.ticket;
|
||||
const clientFk = ticket.client.id;
|
||||
|
||||
const params =
|
||||
{
|
||||
clientFk: client.data.id,
|
||||
addressFk: client.data.defaultAddressFk,
|
||||
};
|
||||
|
||||
this.$http.patch(`Tickets/${ticket.id}`, params).then(() => {
|
||||
this.$http.patch(`Tickets/${ticket.id}/transferClient`, {clientFk})
|
||||
.then(() => {
|
||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||
this.reload();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
isTicketEditable() {
|
||||
|
@ -327,6 +321,10 @@ class Controller extends Section {
|
|||
});
|
||||
}
|
||||
|
||||
docuwareDownload() {
|
||||
this.vnFile.download(`api/Ticket/${this.ticket.id}/docuwareDownload`);
|
||||
}
|
||||
|
||||
setTicketWeight(weight) {
|
||||
return this.$http.patch(`Tickets/${this.ticket.id}`, {weight})
|
||||
.then(() => {
|
||||
|
|
|
@ -326,17 +326,4 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
|
|||
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('transferClient()', () => {
|
||||
it(`should perform two queries, a get to obtain the clientData and a patch to update the ticket`, () => {
|
||||
const client =
|
||||
{
|
||||
clientFk: 1101,
|
||||
addressFk: 1,
|
||||
};
|
||||
$httpBackend.expect('GET', `Clients/${ticket.client.id}`).respond(client);
|
||||
$httpBackend.expect('PATCH', `Tickets/${ticket.id}`).respond();
|
||||
controller.transferClient();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,10 +38,12 @@ class Controller extends SearchPanel {
|
|||
|
||||
applyFilters(param) {
|
||||
if (typeof this.filter.scopeDays === 'number') {
|
||||
const shippedFrom = Date.vnNew();
|
||||
const today = Date.vnNew();
|
||||
const shippedFrom = new Date(today.getTime());
|
||||
shippedFrom.setDate(today.getDate() - 30);
|
||||
shippedFrom.setHours(0, 0, 0, 0);
|
||||
|
||||
const shippedTo = new Date(shippedFrom.getTime());
|
||||
const shippedTo = new Date(today.getTime());
|
||||
shippedTo.setDate(shippedTo.getDate() + this.filter.scopeDays);
|
||||
shippedTo.setHours(23, 59, 59, 999);
|
||||
Object.assign(this.filter, {shippedFrom, shippedTo});
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('docuwareDownload', {
|
||||
description: 'Download a worker document',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
description: 'The document id',
|
||||
http: {source: 'path'}
|
||||
}
|
||||
],
|
||||
returns: [
|
||||
{
|
||||
arg: 'body',
|
||||
type: 'file',
|
||||
root: true
|
||||
}, {
|
||||
arg: 'Content-Type',
|
||||
type: 'String',
|
||||
http: {target: 'header'}
|
||||
}, {
|
||||
arg: 'Content-Disposition',
|
||||
type: 'String',
|
||||
http: {target: 'header'}
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: `/:id/docuwareDownload`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.docuwareDownload = async id => {
|
||||
const filter = {
|
||||
condition: [
|
||||
{
|
||||
DBName: 'FILENAME',
|
||||
Value: [id]
|
||||
}
|
||||
]
|
||||
};
|
||||
return Self.app.models.Docuware.download(id, 'hr', filter);
|
||||
};
|
||||
};
|
|
@ -5,6 +5,12 @@ module.exports = Self => {
|
|||
description: 'Find all instances of the model matched by filter from the data source.',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
description: 'The worker id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'filter',
|
||||
type: 'Object',
|
||||
|
@ -17,16 +23,17 @@ module.exports = Self => {
|
|||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/filter`,
|
||||
path: `/:id/filter`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.filter = async(ctx, filter) => {
|
||||
Self.filter = async(ctx, id, filter) => {
|
||||
const conn = Self.dataSource.connector;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const models = Self.app.models;
|
||||
|
||||
const account = await Self.app.models.VnUser.findById(userId);
|
||||
const account = await models.VnUser.findById(userId);
|
||||
const stmt = new ParameterizedSQL(
|
||||
`SELECT d.id dmsFk, d.reference, d.description, d.file, d.created, d.hardCopyNumber, d.hasFile
|
||||
FROM workerDocument wd
|
||||
|
@ -47,7 +54,38 @@ module.exports = Self => {
|
|||
}]
|
||||
}, oldWhere]};
|
||||
stmt.merge(conn.makeSuffix(filter));
|
||||
const workerDms = await conn.executeStmt(stmt);
|
||||
|
||||
return await conn.executeStmt(stmt);
|
||||
// Get docuware info
|
||||
const docuware = await models.Docuware.findOne({
|
||||
fields: ['dmsTypeFk'],
|
||||
where: {code: 'hr', action: 'find'}
|
||||
});
|
||||
const docuwareDmsType = docuware.dmsTypeFk;
|
||||
let workerDocuware = [];
|
||||
if (!docuwareDmsType || (docuwareDmsType && await models.DmsType.hasReadRole(ctx, docuwareDmsType))) {
|
||||
const worker = await models.Worker.findById(id, {fields: ['fi', 'firstName', 'lastName']});
|
||||
const docuwareParse = {
|
||||
'Filename': 'dmsFk',
|
||||
'Tipo Documento': 'description',
|
||||
'Stored on': 'created',
|
||||
'Document ID': 'id'
|
||||
};
|
||||
workerDocuware =
|
||||
await models.Docuware.getById('hr', worker.lastName + worker.firstName, docuwareParse) ?? [];
|
||||
for (document of workerDocuware) {
|
||||
const defaultData = {
|
||||
file: 'dw' + document.id + '.png',
|
||||
isDocuware: true,
|
||||
hardCopyNumber: null,
|
||||
hasFile: false,
|
||||
reference: worker.fi,
|
||||
dmsFk: 'DW' + document.id
|
||||
};
|
||||
|
||||
document = Object.assign(document, defaultData);
|
||||
}
|
||||
}
|
||||
return workerDms.concat(workerDocuware);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@ module.exports = Self => {
|
|||
require('../methods/worker-dms/downloadFile')(Self);
|
||||
require('../methods/worker-dms/removeFile')(Self);
|
||||
require('../methods/worker-dms/filter')(Self);
|
||||
require('../methods/worker-dms/docuwareDownload')(Self);
|
||||
|
||||
Self.isMine = async function(ctx, dmsId) {
|
||||
const myUserId = ctx.req.accessToken.userId;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
data="absenceTypes"
|
||||
auto-load="true">
|
||||
</vn-crud-model>
|
||||
<div ng-if="$ctrl.worker.hasWorkCenter">
|
||||
<div ng-if="$ctrl.card.hasWorkCenter">
|
||||
<div class="vn-w-lg">
|
||||
<vn-card class="vn-pa-sm calendars">
|
||||
<vn-icon ng-if="::$ctrl.isSubordinate" icon="info" color-marginal
|
||||
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
ng-if="!$ctrl.worker.hasWorkCenter"
|
||||
ng-if="!$ctrl.card.hasWorkCenter"
|
||||
class="bg-title"
|
||||
translate>
|
||||
Autonomous worker
|
||||
|
@ -73,41 +73,39 @@
|
|||
value-field="businessFk"
|
||||
order="businessFk DESC"
|
||||
limit="5">
|
||||
<tpl-item>
|
||||
<div>#{{businessFk}}</div>
|
||||
<div class="text-caption text-secondary">
|
||||
{{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}}
|
||||
</div>
|
||||
</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</div>
|
||||
<div name="absenceTypes" class="input vn-py-md" style="overflow: hidden;">
|
||||
<vn-chip ng-repeat="absenceType in absenceTypes" ng-class="::{'selectable': $ctrl.isSubordinate}"
|
||||
ng-click="$ctrl.pick(absenceType)">
|
||||
<vn-avatar
|
||||
ng-style="{backgroundColor: absenceType.rgb}">
|
||||
<vn-icon icon="check" ng-if="absenceType.id == $ctrl.absenceType.id"></vn-icon>
|
||||
</vn-avatar>
|
||||
{{absenceType.name}}
|
||||
</vn-chip>
|
||||
</div>
|
||||
<div class="vn-py-md">
|
||||
<vn-chip>
|
||||
<vn-avatar class="festive">
|
||||
</vn-avatar>
|
||||
<span translate>Festive</span>
|
||||
</vn-chip>
|
||||
<vn-chip>
|
||||
<vn-avatar class="today">
|
||||
</vn-avatar>
|
||||
<span translate>Current day</span>
|
||||
</vn-chip>
|
||||
</div>
|
||||
<tpl-item>
|
||||
<div>#{{businessFk}}</div>
|
||||
<div class="text-caption text-secondary">
|
||||
{{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}}
|
||||
</div>
|
||||
</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</div>
|
||||
<div name="absenceTypes" class="input vn-py-md" style="overflow: hidden;">
|
||||
<vn-chip ng-repeat="absenceType in absenceTypes" ng-class="::{'selectable': $ctrl.isSubordinate}" ng-click="$ctrl.pick(absenceType)">
|
||||
<vn-avatar ng-style="{backgroundColor: absenceType.rgb}">
|
||||
<vn-icon icon="check" ng-if="absenceType.id == $ctrl.absenceType.id"></vn-icon>
|
||||
</vn-avatar>
|
||||
{{absenceType.name}}
|
||||
</vn-chip>
|
||||
</div>
|
||||
<div class="vn-py-md">
|
||||
<vn-chip>
|
||||
<vn-avatar class="festive">
|
||||
</vn-avatar>
|
||||
<span translate>Festive</span>
|
||||
</vn-chip>
|
||||
<vn-chip>
|
||||
<vn-avatar class="today">
|
||||
</vn-avatar>
|
||||
<span translate>Current day</span>
|
||||
</vn-chip>
|
||||
</div>
|
||||
</div>
|
||||
</vn-side-menu>
|
||||
|
||||
<vn-confirm
|
||||
vn-id="confirm"
|
||||
message="This item will be deleted"
|
||||
question="Are you sure you want to continue?">
|
||||
</vn-confirm>
|
||||
|
||||
|
|
|
@ -31,6 +31,8 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
set businessId(value) {
|
||||
if (!this.card.hasWorkCenter) return;
|
||||
|
||||
this._businessId = value;
|
||||
if (value) {
|
||||
this.refresh()
|
||||
|
@ -64,7 +66,7 @@ class Controller extends Section {
|
|||
|
||||
set worker(value) {
|
||||
this._worker = value;
|
||||
if (value && value.hasWorkCenter) {
|
||||
if (value) {
|
||||
this.getIsSubordinate();
|
||||
this.getActiveContract();
|
||||
}
|
||||
|
@ -293,5 +295,8 @@ ngModule.vnComponent('vnWorkerCalendar', {
|
|||
controller: Controller,
|
||||
bindings: {
|
||||
worker: '<'
|
||||
},
|
||||
require: {
|
||||
card: '^vnWorkerCard'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -20,6 +20,9 @@ describe('Worker', () => {
|
|||
controller.absenceType = {id: 1, name: 'Holiday', code: 'holiday', rgb: 'red'};
|
||||
controller.$params.id = 1106;
|
||||
controller._worker = {id: 1106};
|
||||
controller.card = {
|
||||
hasWorkCenter: true
|
||||
};
|
||||
}));
|
||||
|
||||
describe('year() getter', () => {
|
||||
|
@ -74,7 +77,7 @@ describe('Worker', () => {
|
|||
let yesterday = new Date(today.getTime());
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
|
||||
controller.worker = {id: 1107, hasWorkCenter: true};
|
||||
controller.worker = {id: 1107};
|
||||
|
||||
expect(controller.getIsSubordinate).toHaveBeenCalledWith();
|
||||
expect(controller.getActiveContract).toHaveBeenCalledWith();
|
||||
|
|
|
@ -3,7 +3,7 @@ import ModuleCard from 'salix/components/module-card';
|
|||
|
||||
class Controller extends ModuleCard {
|
||||
reload() {
|
||||
let filter = {
|
||||
const filter = {
|
||||
include: [
|
||||
{
|
||||
relation: 'user',
|
||||
|
@ -32,13 +32,12 @@ class Controller extends ModuleCard {
|
|||
]
|
||||
};
|
||||
|
||||
this.$http.get(`Workers/${this.$params.id}`, {filter})
|
||||
.then(res => this.worker = res.data)
|
||||
.then(() =>
|
||||
this.$http.get(`Workers/${this.$params.id}/activeContract`)
|
||||
.then(res => {
|
||||
if (res.data) this.worker.hasWorkCenter = res.data.workCenterFk;
|
||||
}));
|
||||
return Promise.all([
|
||||
this.$http.get(`Workers/${this.$params.id}`, {filter})
|
||||
.then(res => this.worker = res.data),
|
||||
this.$http.get(`Workers/${this.$params.id}/activeContract`)
|
||||
.then(res => this.hasWorkCenter = res.data.workCenterFk)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
<slot-before>
|
||||
<div class="photo" text-center>
|
||||
<img vn-id="photo"
|
||||
ng-src="{{$root.imagePath('user', '520x520', $ctrl.worker.id)}}"
|
||||
zoom-image="{{$root.imagePath('user', '1600x1600', $ctrl.worker.id)}}"
|
||||
ng-src="{{$root.imagePath('user', '520x520', $ctrl.worker.id)}}"
|
||||
zoom-image="{{$root.imagePath('user', '1600x1600', $ctrl.worker.id)}}"
|
||||
on-error-src/>
|
||||
<vn-float-button ng-click="uploadPhoto.show('user', $ctrl.worker.id)"
|
||||
icon="edit"
|
||||
|
@ -15,39 +15,32 @@
|
|||
</div>
|
||||
</slot-before>
|
||||
<slot-menu>
|
||||
<vn-item
|
||||
ng-click="$ctrl.handleExcluded()"
|
||||
translate
|
||||
ng-if="!$ctrl.excluded">
|
||||
Click to exclude the user from getting disabled
|
||||
</vn-item>
|
||||
<vn-item
|
||||
ng-click="$ctrl.handleExcluded()"
|
||||
translate
|
||||
ng-if="$ctrl.excluded">
|
||||
Click to allow the user to be disabled
|
||||
</vn-item>
|
||||
<vn-item ng-click="$ctrl.handleExcluded()" translate>
|
||||
{{$ctrl.workerExcluded
|
||||
? 'Click to allow the user to be disabled'
|
||||
: 'Click to exclude the user from getting disabled'}}
|
||||
</vn-item>
|
||||
</slot-menu>
|
||||
<slot-body>
|
||||
<div class="attributes">
|
||||
<vn-label-value
|
||||
label="User"
|
||||
label="User"
|
||||
value="{{$ctrl.worker.user.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Email"
|
||||
label="Email"
|
||||
value="{{$ctrl.worker.user.emailUser.email}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Department"
|
||||
label="Department"
|
||||
value="{{$ctrl.worker.department.department.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Phone"
|
||||
label="Phone"
|
||||
value="{{$ctrl.worker.phone}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Extension"
|
||||
label="Extension"
|
||||
value="{{$ctrl.worker.sip.extension}}">
|
||||
</vn-label-value>
|
||||
</div>
|
||||
|
@ -84,7 +77,7 @@
|
|||
</vn-popup>
|
||||
|
||||
<!-- Upload photo dialog -->
|
||||
<vn-upload-photo
|
||||
vn-id="uploadPhoto"
|
||||
<vn-upload-photo
|
||||
vn-id="uploadPhoto"
|
||||
on-response="$ctrl.onUploadResponse()">
|
||||
</vn-upload-photo>
|
||||
</vn-upload-photo>
|
||||
|
|
|
@ -18,28 +18,19 @@ class Controller extends Descriptor {
|
|||
this.getIsExcluded();
|
||||
}
|
||||
|
||||
get excluded() {
|
||||
return this.entity.excluded;
|
||||
}
|
||||
|
||||
set excluded(value) {
|
||||
this.entity.excluded = value;
|
||||
}
|
||||
|
||||
getIsExcluded() {
|
||||
this.$http.get(`workerDisableExcludeds/${this.entity.id}/exists`).then(data => {
|
||||
this.excluded = data.data.exists;
|
||||
this.$http.get(`WorkerDisableExcludeds/${this.entity.id}/exists`).then(data => {
|
||||
this.workerExcluded = data.data.exists;
|
||||
});
|
||||
}
|
||||
|
||||
handleExcluded() {
|
||||
if (this.excluded) {
|
||||
this.$http.delete(`workerDisableExcludeds/${this.entity.id}`);
|
||||
this.excluded = false;
|
||||
} else {
|
||||
this.$http.post(`workerDisableExcludeds`, {workerFk: this.entity.id, dated: new Date});
|
||||
this.excluded = true;
|
||||
}
|
||||
if (this.workerExcluded)
|
||||
this.$http.delete(`WorkerDisableExcludeds/${this.entity.id}`);
|
||||
else
|
||||
this.$http.post(`WorkerDisableExcludeds`, {workerFk: this.entity.id, dated: new Date});
|
||||
|
||||
this.workerExcluded = !this.workerExcluded;
|
||||
}
|
||||
|
||||
loadData() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="WorkerDms/filter"
|
||||
url="WorkerDms/{{$ctrl.$params.id}}/filter"
|
||||
link="{worker: $ctrl.$params.id}"
|
||||
limit="20"
|
||||
data="$ctrl.workerDms"
|
||||
|
@ -46,14 +46,14 @@
|
|||
</span>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-check
|
||||
<vn-check
|
||||
ng-model="document.hasFile"
|
||||
disabled="true">
|
||||
</vn-check>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<span title="{{'Download file' | translate}}" class="link"
|
||||
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||
ng-click="$ctrl.downloadFile(document.dmsFk, document.isDocuware)">
|
||||
{{::document.file}}
|
||||
</span>
|
||||
</vn-td>
|
||||
|
@ -63,16 +63,14 @@
|
|||
<vn-td shrink>
|
||||
<vn-icon-button title="{{'Download file' | translate}}"
|
||||
icon="cloud_download"
|
||||
ng-click="$ctrl.downloadFile(document.dmsFk)">
|
||||
ng-click="$ctrl.downloadFile(document.dmsFk, document.isDocuware)">
|
||||
</vn-icon-button>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-td expand ng-if="::!document.isDocuware">
|
||||
<vn-icon-button ui-sref="worker.card.dms.edit({dmsId: {{::document.dmsFk}}})"
|
||||
icon="edit"
|
||||
title="{{'Edit file' | translate}}">
|
||||
</vn-icon-button>
|
||||
</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-icon-button
|
||||
icon="delete"
|
||||
ng-click="confirm.show($index)"
|
||||
|
@ -80,6 +78,14 @@
|
|||
tabindex="-1">
|
||||
</vn-icon-button>
|
||||
</vn-td>
|
||||
<vn-td expand ng-if="::document.isDocuware">
|
||||
<vn-icon-button
|
||||
icon="open_in_new"
|
||||
ng-click="$ctrl.openDocuware()"
|
||||
title="{{'Open in docuware' | translate}}"
|
||||
tabindex="-1">
|
||||
</vn-icon-button>
|
||||
</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
|
@ -91,9 +97,9 @@
|
|||
fixed-bottom-right>
|
||||
<vn-float-button icon="add"></vn-float-button>
|
||||
</a>
|
||||
<vn-confirm
|
||||
<vn-confirm
|
||||
vn-id="confirm"
|
||||
message="This file will be deleted"
|
||||
question="Are you sure you want to continue?"
|
||||
on-accept="$ctrl.deleteDms($data)">
|
||||
</vn-confirm>
|
||||
</vn-confirm>
|
||||
|
|
|
@ -17,9 +17,15 @@ class Controller extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
downloadFile(dmsId) {
|
||||
downloadFile(dmsId, isDocuware) {
|
||||
if (isDocuware) return this.vnFile.download(`api/workerDms/${dmsId}/docuwareDownload`);
|
||||
this.vnFile.download(`api/workerDms/${dmsId}/downloadFile`);
|
||||
}
|
||||
|
||||
async openDocuware() {
|
||||
const url = await this.vnApp.getUrl(`WebClient`, 'docuware');
|
||||
if (url) window.open(url).focus();
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$element', '$scope', 'vnFile'];
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
filter="::$ctrl.filter"
|
||||
data="$ctrl.hours">
|
||||
</vn-crud-model>
|
||||
<div ng-if="$ctrl.worker.hasWorkCenter">
|
||||
<div ng-if="$ctrl.card.hasWorkCenter">
|
||||
<vn-card class="vn-pa-lg vn-w-lg">
|
||||
<vn-table model="model" auto-load="false">
|
||||
<vn-thead>
|
||||
|
@ -107,7 +107,7 @@
|
|||
</vn-button-bar>
|
||||
</div>
|
||||
<div
|
||||
ng-if="!$ctrl.worker.hasWorkCenter"
|
||||
ng-if="!$ctrl.card.hasWorkCenter"
|
||||
class="bg-title"
|
||||
translate>
|
||||
Autonomous worker
|
||||
|
@ -136,6 +136,7 @@
|
|||
</vn-calendar>
|
||||
</div>
|
||||
</vn-side-menu>
|
||||
|
||||
<vn-dialog
|
||||
vn-id="addTimeDialog"
|
||||
on-accept="$ctrl.addTime()"
|
||||
|
|
|
@ -141,6 +141,8 @@ class Controller extends Section {
|
|||
]}
|
||||
};
|
||||
this.$.model.applyFilter(filter, params).then(() => {
|
||||
if (!this.card.hasWorkCenter) return;
|
||||
|
||||
this.getWorkedHours(this.started, this.ended);
|
||||
this.getAbsences();
|
||||
});
|
||||
|
@ -151,7 +153,6 @@ class Controller extends Section {
|
|||
}
|
||||
|
||||
getAbsences() {
|
||||
if (!this.worker.hasWorkerCenter) return;
|
||||
const fullYear = this.started.getFullYear();
|
||||
let params = {
|
||||
workerFk: this.$params.id,
|
||||
|
@ -486,5 +487,8 @@ ngModule.vnComponent('vnWorkerTimeControl', {
|
|||
controller: Controller,
|
||||
bindings: {
|
||||
worker: '<'
|
||||
},
|
||||
require: {
|
||||
card: '^vnWorkerCard'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -16,9 +16,8 @@ describe('Component vnWorkerTimeControl', () => {
|
|||
$scope = $rootScope.$new();
|
||||
$element = angular.element('<vn-worker-time-control></vn-worker-time-control>');
|
||||
controller = $componentController('vnWorkerTimeControl', {$element, $scope});
|
||||
controller.worker = {
|
||||
hasWorkerCenter: true
|
||||
|
||||
controller.card = {
|
||||
hasWorkCenter: true
|
||||
};
|
||||
}));
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ module.exports = Self => {
|
|||
Object.assign(myOptions, options);
|
||||
|
||||
const [res] = await Self.rawSql(
|
||||
`CALL zone_getLeaves(?, ?, ?)`,
|
||||
`CALL zone_getLeaves(?, ?, ?, FALSE)`,
|
||||
[id, parentId, search],
|
||||
myOptions
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "salix-back",
|
||||
"version": "23.30.01",
|
||||
"version": "23.32.01",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "salix-back",
|
||||
"version": "23.30.01",
|
||||
"version": "23.32.01",
|
||||
"author": "Verdnatura Levante SL",
|
||||
"description": "Salix backend",
|
||||
"license": "GPL-3.0",
|
||||
|
|
|
@ -10,16 +10,17 @@ module.exports = {
|
|||
|
||||
async send(options) {
|
||||
options.from = `${config.app.senderName} <${config.app.senderEmail}>`;
|
||||
if (!process.env.NODE_ENV)
|
||||
options.to = config.app.senderEmail;
|
||||
const env = process.env.NODE_ENV;
|
||||
const canSend = env === 'production' || !env || options.force;
|
||||
|
||||
if (process.env.NODE_ENV !== 'production' && !options.force) {
|
||||
if (!canSend || !config.smtp.auth.user) {
|
||||
const notProductionError = {message: 'This not production, this email not sended'};
|
||||
await this.mailLog(options, notProductionError);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
if (!config.smtp.auth.user)
|
||||
return Promise.resolve(true);
|
||||
if (!env)
|
||||
options.to = config.app.senderEmail;
|
||||
|
||||
let res;
|
||||
let error;
|
||||
|
|
|
@ -1,31 +1,19 @@
|
|||
SELECT
|
||||
SELECT
|
||||
io.ref,
|
||||
c.socialName,
|
||||
sa.iban,
|
||||
pm.name AS payMethod,
|
||||
t.clientFk,
|
||||
t.shipped,
|
||||
t.nickname,
|
||||
s.ticketFk,
|
||||
s.itemFk,
|
||||
s.concept,
|
||||
s.quantity,
|
||||
s.price,
|
||||
ib.ediBotanic botanical,
|
||||
s.quantity,
|
||||
s.price,
|
||||
s.discount,
|
||||
i.tag5,
|
||||
i.value5,
|
||||
i.tag6,
|
||||
i.value6,
|
||||
i.tag7,
|
||||
i.value7,
|
||||
tc.code AS vatType,
|
||||
ib.ediBotanic botanical
|
||||
s.itemFk,
|
||||
s.concept,
|
||||
tc.code vatType
|
||||
FROM vn.invoiceOut io
|
||||
JOIN vn.ticket t ON t.refFk = io.ref
|
||||
JOIN vn.supplier su ON su.id = io.companyFk
|
||||
JOIN vn.client c ON c.id = t.clientFk
|
||||
JOIN vn.client c ON c.id = t.clientFk
|
||||
JOIN vn.payMethod pm ON pm.id = c.payMethodFk
|
||||
JOIN vn.company co ON co.id = io.companyFk
|
||||
JOIN vn.company co ON co.id = io.companyFk
|
||||
JOIN vn.supplierAccount sa ON sa.id = co.supplierAccountFk
|
||||
JOIN vn.sale s ON s.ticketFk = t.id
|
||||
JOIN item i ON i.id = s.itemFk
|
||||
|
@ -38,35 +26,23 @@ SELECT
|
|||
AND itc.itemFk = s.itemFk
|
||||
JOIN vn.taxClass tc ON tc.id = itc.taxClassFk
|
||||
WHERE t.refFk = ?
|
||||
UNION ALL
|
||||
SELECT
|
||||
UNION ALL
|
||||
SELECT
|
||||
io.ref,
|
||||
c.socialName,
|
||||
sa.iban,
|
||||
pm.name AS payMethod,
|
||||
t.clientFk,
|
||||
t.shipped,
|
||||
t.nickname,
|
||||
t.id AS ticketFk,
|
||||
t.id ticketFk,
|
||||
NULL botanical,
|
||||
ts.quantity,
|
||||
ts.price,
|
||||
0 discount,
|
||||
'',
|
||||
ts.description concept,
|
||||
ts.quantity,
|
||||
ts.price,
|
||||
0 discount,
|
||||
NULL AS tag5,
|
||||
NULL AS value5,
|
||||
NULL AS tag6,
|
||||
NULL AS value6,
|
||||
NULL AS tag7,
|
||||
NULL AS value7,
|
||||
tc.code AS vatType,
|
||||
NULL AS botanical
|
||||
ts.description concept,
|
||||
tc.code vatType
|
||||
FROM vn.invoiceOut io
|
||||
JOIN vn.ticket t ON t.refFk = io.ref
|
||||
JOIN vn.ticketService ts ON ts.ticketFk = t.id
|
||||
JOIN vn.client c ON c.id = t.clientFk
|
||||
JOIN vn.client c ON c.id = t.clientFk
|
||||
JOIN vn.payMethod pm ON pm.id = c.payMethodFk
|
||||
JOIN vn.company co ON co.id = io.companyFk
|
||||
JOIN vn.company co ON co.id = io.companyFk
|
||||
JOIN vn.supplierAccount sa ON sa.id = co.supplierAccountFk
|
||||
JOIN vn.taxClass tc ON tc.id = ts.taxClassFk
|
||||
WHERE t.refFk = ?
|
||||
WHERE t.refFk = ?
|
||||
|
|
Loading…
Reference in New Issue