Merge branch 'dev' into 3948-invoceIn.index
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
This commit is contained in:
commit
93829eeff5
|
@ -46,7 +46,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
const {data} = await Self.getUserStatus(recipient.name);
|
const {data} = await Self.getUserStatus(recipient.name);
|
||||||
if (data) {
|
if (data) {
|
||||||
if (data.status === 'offline') {
|
if (data.status === 'offline' || data.status === 'busy') {
|
||||||
// Send message to department room
|
// Send message to department room
|
||||||
const workerDepartment = await models.WorkerDepartment.findById(recipientId, {
|
const workerDepartment = await models.WorkerDepartment.findById(recipientId, {
|
||||||
include: {
|
include: {
|
||||||
|
@ -58,6 +58,8 @@ module.exports = Self => {
|
||||||
|
|
||||||
if (channelName)
|
if (channelName)
|
||||||
return Self.send(ctx, `#${channelName}`, `@${recipient.name} ➔ ${message}`);
|
return Self.send(ctx, `#${channelName}`, `@${recipient.name} ➔ ${message}`);
|
||||||
|
else
|
||||||
|
return Self.send(ctx, `@${recipient.name}`, message);
|
||||||
} else
|
} else
|
||||||
return Self.send(ctx, `@${recipient.name}`, message);
|
return Self.send(ctx, `@${recipient.name}`, message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Delete file
|
|
@ -2271,8 +2271,14 @@ INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `wa
|
||||||
(3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 104', 'Client:104 readme', CURDATE()),
|
(3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 104', 'Client:104 readme', CURDATE()),
|
||||||
(4, 3, '4.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Worker: 106', 'Worker:106 readme', CURDATE()),
|
(4, 3, '4.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Worker: 106', 'Worker:106 readme', CURDATE()),
|
||||||
(5, 5, '5.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'travel: 1', 'dmsForThermograph', CURDATE()),
|
(5, 5, '5.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'travel: 1', 'dmsForThermograph', CURDATE()),
|
||||||
(6, 5, '6.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'NotExists', 'DoesNotExists', CURDATE());
|
(6, 5, '6.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'NotExists', 'DoesNotExists', CURDATE()),
|
||||||
|
(7, 20, '7.jpg', 'image/jpeg', 9, 1, 442, NULL, FALSE, '1', 'TICKET ID DEL CLIENTE BRUCE WAYNE ID 1101', CURDATE()),
|
||||||
|
(8, 20, '8.mp4', 'video/mp4', 9, 1, 442, NULL, FALSE, '1', 'TICKET ID DEL CLIENTE BRUCE WAYNE ID 1101', CURDATE());
|
||||||
|
|
||||||
|
INSERT INTO `vn`.`claimDms`(`claimFk`, `dmsFk`)
|
||||||
|
VALUES
|
||||||
|
(1, 7),
|
||||||
|
(1, 8);
|
||||||
|
|
||||||
INSERT INTO `vn`.`ticketDms`(`ticketFk`, `dmsFk`)
|
INSERT INTO `vn`.`ticketDms`(`ticketFk`, `dmsFk`)
|
||||||
VALUES
|
VALUES
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
.icon-agency-term:before {
|
.icon-agency-term:before {
|
||||||
content: "\e950";
|
content: "\e950";
|
||||||
}
|
}
|
||||||
.icon-deaulter:before {
|
.icon-defaulter:before {
|
||||||
content: "\e94b";
|
content: "\e94b";
|
||||||
}
|
}
|
||||||
.icon-100:before {
|
.icon-100:before {
|
||||||
|
|
|
@ -39,7 +39,8 @@
|
||||||
"multipart/x-zip",
|
"multipart/x-zip",
|
||||||
"image/png",
|
"image/png",
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
"image/jpg"
|
"image/jpg",
|
||||||
|
"video/mp4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dmsStorage": {
|
"dmsStorage": {
|
||||||
|
@ -84,5 +85,18 @@
|
||||||
"application/octet-stream",
|
"application/octet-stream",
|
||||||
"application/pdf"
|
"application/pdf"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"claimStorage": {
|
||||||
|
"name": "claimStorage",
|
||||||
|
"connector": "loopback-component-storage",
|
||||||
|
"provider": "filesystem",
|
||||||
|
"root": "./storage/dms",
|
||||||
|
"maxFileSize": "31457280",
|
||||||
|
"allowedContentTypes": [
|
||||||
|
"image/png",
|
||||||
|
"image/jpeg",
|
||||||
|
"image/jpg",
|
||||||
|
"video/mp4"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('downloadFile', {
|
||||||
|
description: 'Get the claim file',
|
||||||
|
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/downloadFile`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.downloadFile = async function(ctx, id) {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const ClaimContainer = models.ClaimContainer;
|
||||||
|
const dms = await models.Dms.findById(id);
|
||||||
|
const pathHash = ClaimContainer.getHash(dms.id);
|
||||||
|
try {
|
||||||
|
await ClaimContainer.getFile(pathHash, dms.file);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code != 'ENOENT')
|
||||||
|
throw e;
|
||||||
|
|
||||||
|
const error = new UserError(`File doesn't exists`);
|
||||||
|
error.statusCode = 404;
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = ClaimContainer.downloadStream(pathHash, dms.file);
|
||||||
|
|
||||||
|
return [stream, dms.contentType, `filename="${dms.file}"`];
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
describe('claim downloadFile()', () => {
|
||||||
|
const dmsId = 7;
|
||||||
|
|
||||||
|
it('should return a response for an employee with image content-type', async() => {
|
||||||
|
const workerId = 1107;
|
||||||
|
const ctx = {req: {accessToken: {userId: workerId}}};
|
||||||
|
const result = await app.models.Claim.downloadFile(ctx, dmsId);
|
||||||
|
|
||||||
|
expect(result[1]).toEqual('image/jpeg');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,18 @@
|
||||||
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
describe('claim uploadFile()', () => {
|
||||||
|
it(`should return an error for a user without enough privileges`, async() => {
|
||||||
|
const clientId = 1101;
|
||||||
|
const ticketDmsTypeId = 14;
|
||||||
|
const ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: ticketDmsTypeId}};
|
||||||
|
|
||||||
|
let error;
|
||||||
|
await app.models.Claim.uploadFile(ctx).catch(e => {
|
||||||
|
error = e;
|
||||||
|
}).finally(() => {
|
||||||
|
expect(error.message).toEqual(`You don't have enough privileges`);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,6 +1,10 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('uploadFile', {
|
Self.remoteMethodCtx('uploadFile', {
|
||||||
description: 'Upload and attach a document',
|
description: 'Upload and attach a file',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: [{
|
accepts: [{
|
||||||
arg: 'id',
|
arg: 'id',
|
||||||
|
@ -53,22 +57,54 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.uploadFile = async(ctx, id, options) => {
|
Self.uploadFile = async(ctx, id, options) => {
|
||||||
let tx;
|
const tx = await Self.beginTransaction({});
|
||||||
const myOptions = {};
|
const myOptions = {};
|
||||||
|
|
||||||
if (typeof options == 'object')
|
if (typeof options == 'object')
|
||||||
Object.assign(myOptions, options);
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
if (!myOptions.transaction) {
|
if (!myOptions.transaction)
|
||||||
tx = await Self.beginTransaction({});
|
|
||||||
myOptions.transaction = tx;
|
myOptions.transaction = tx;
|
||||||
}
|
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
const TempContainer = models.TempContainer;
|
||||||
|
const ClaimContainer = models.ClaimContainer;
|
||||||
|
const fileOptions = {};
|
||||||
|
const args = ctx.args;
|
||||||
|
|
||||||
|
let srcFile;
|
||||||
try {
|
try {
|
||||||
const uploadedFiles = await models.Dms.uploadFile(ctx, myOptions);
|
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId, myOptions);
|
||||||
uploadedFiles.forEach(dms => {
|
if (!hasWriteRole)
|
||||||
|
throw new UserError(`You don't have enough privileges`);
|
||||||
|
|
||||||
|
// Upload file to temporary path
|
||||||
|
const tempContainer = await TempContainer.container('dms');
|
||||||
|
const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
|
||||||
|
const files = Object.values(uploaded.files).map(file => {
|
||||||
|
return file[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
const addedDms = [];
|
||||||
|
for (const uploadedFile of files) {
|
||||||
|
const newDms = await createDms(ctx, uploadedFile, myOptions);
|
||||||
|
const pathHash = ClaimContainer.getHash(newDms.id);
|
||||||
|
|
||||||
|
const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
|
||||||
|
srcFile = path.join(file.client.root, file.container, file.name);
|
||||||
|
|
||||||
|
const claimContainer = await ClaimContainer.container(pathHash);
|
||||||
|
const dstFile = path.join(claimContainer.client.root, pathHash, newDms.file);
|
||||||
|
|
||||||
|
await fs.move(srcFile, dstFile, {
|
||||||
|
overwrite: true
|
||||||
|
});
|
||||||
|
|
||||||
|
addedDms.push(newDms);
|
||||||
|
}
|
||||||
|
|
||||||
|
addedDms.forEach(dms => {
|
||||||
const newClaimDms = models.ClaimDms.create({
|
const newClaimDms = models.ClaimDms.create({
|
||||||
claimFk: id,
|
claimFk: id,
|
||||||
dmsFk: dms.id
|
dmsFk: dms.id
|
||||||
|
@ -83,7 +119,34 @@ module.exports = Self => {
|
||||||
return resolvedPromises;
|
return resolvedPromises;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (tx) await tx.rollback();
|
if (tx) await tx.rollback();
|
||||||
|
|
||||||
|
if (fs.existsSync(srcFile))
|
||||||
|
await fs.unlink(srcFile);
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function createDms(ctx, file, myOptions) {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const myUserId = ctx.req.accessToken.userId;
|
||||||
|
const args = ctx.args;
|
||||||
|
|
||||||
|
const newDms = await models.Dms.create({
|
||||||
|
workerFk: myUserId,
|
||||||
|
dmsTypeFk: args.dmsTypeId,
|
||||||
|
companyFk: args.companyId,
|
||||||
|
warehouseFk: args.warehouseId,
|
||||||
|
reference: args.reference,
|
||||||
|
description: args.description,
|
||||||
|
contentType: file.type,
|
||||||
|
hasFile: args.hasFile
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
let fileName = file.name;
|
||||||
|
const extension = models.DmsContainer.getFileExtension(fileName);
|
||||||
|
fileName = `${newDms.id}.${extension}`;
|
||||||
|
|
||||||
|
return newDms.updateAttribute('file', fileName, myOptions);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -37,5 +37,8 @@
|
||||||
},
|
},
|
||||||
"ClaimLog": {
|
"ClaimLog": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"ClaimContainer": {
|
||||||
|
"dataSource": "claimStorage"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"name": "ClaimContainer",
|
||||||
|
"base": "Container",
|
||||||
|
"acls": [{
|
||||||
|
"accessType": "READ",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
}]
|
||||||
|
}
|
|
@ -7,4 +7,5 @@ module.exports = Self => {
|
||||||
require('../methods/claim/uploadFile')(Self);
|
require('../methods/claim/uploadFile')(Self);
|
||||||
require('../methods/claim/updateClaimAction')(Self);
|
require('../methods/claim/updateClaimAction')(Self);
|
||||||
require('../methods/claim/isEditable')(Self);
|
require('../methods/claim/isEditable')(Self);
|
||||||
|
require('../methods/claim/downloadFile')(Self);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<vn-crud-model
|
<vn-crud-model
|
||||||
vn-id="model"
|
vn-id="model"
|
||||||
auto-load="true"
|
auto-load="true"
|
||||||
|
filter="::$ctrl.filter"
|
||||||
url="ClaimDms"
|
url="ClaimDms"
|
||||||
link="{claimFk: $ctrl.$params.id}"
|
link="{claimFk: $ctrl.$params.id}"
|
||||||
limit="20"
|
limit="20"
|
||||||
|
@ -14,8 +15,13 @@
|
||||||
<section class="photo" ng-repeat="photo in $ctrl.photos">
|
<section class="photo" ng-repeat="photo in $ctrl.photos">
|
||||||
<section class="image vn-shadow" on-error-src
|
<section class="image vn-shadow" on-error-src
|
||||||
ng-style="{'background': 'url(' + $ctrl.getImagePath(photo.dmsFk) + ')'}"
|
ng-style="{'background': 'url(' + $ctrl.getImagePath(photo.dmsFk) + ')'}"
|
||||||
zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}">
|
zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}"
|
||||||
|
ng-if="photo.dms.contentType != 'video/mp4'">
|
||||||
</section>
|
</section>
|
||||||
|
<video id="videobcg" muted="muted" controls ng-if="photo.dms.contentType == 'video/mp4'"
|
||||||
|
class="video">
|
||||||
|
<source src="{{$ctrl.getImagePath(photo.dmsFk)}}" type="video/mp4">
|
||||||
|
</video>
|
||||||
<section class="actions">
|
<section class="actions">
|
||||||
<vn-button
|
<vn-button
|
||||||
class="round"
|
class="round"
|
||||||
|
@ -35,7 +41,7 @@
|
||||||
</vn-confirm>
|
</vn-confirm>
|
||||||
<vn-float-button
|
<vn-float-button
|
||||||
icon="add"
|
icon="add"
|
||||||
vn-tooltip="Select photo"
|
vn-tooltip="Select file"
|
||||||
vn-bind="+"
|
vn-bind="+"
|
||||||
ng-click="$ctrl.openUploadDialog()"
|
ng-click="$ctrl.openUploadDialog()"
|
||||||
fixed-bottom-right>
|
fixed-bottom-right>
|
||||||
|
|
|
@ -6,6 +6,13 @@ class Controller extends Section {
|
||||||
constructor($element, $, vnFile) {
|
constructor($element, $, vnFile) {
|
||||||
super($element, $);
|
super($element, $);
|
||||||
this.vnFile = vnFile;
|
this.vnFile = vnFile;
|
||||||
|
this.filter = {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
relation: 'dms'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteDms(index) {
|
deleteDms(index) {
|
||||||
|
@ -13,7 +20,7 @@ class Controller extends Section {
|
||||||
return this.$http.post(`ClaimDms/${dmsFk}/removeFile`)
|
return this.$http.post(`ClaimDms/${dmsFk}/removeFile`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$.model.remove(index);
|
this.$.model.remove(index);
|
||||||
this.vnApp.showSuccess(this.$t('Photo deleted'));
|
this.vnApp.showSuccess(this.$t('File deleted'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,13 +88,13 @@ class Controller extends Section {
|
||||||
data: this.dms.files
|
data: this.dms.files
|
||||||
};
|
};
|
||||||
this.$http(options).then(() => {
|
this.$http(options).then(() => {
|
||||||
this.vnApp.showSuccess(this.$t('Photo uploaded!'));
|
this.vnApp.showSuccess(this.$t('File uploaded!'));
|
||||||
this.$.model.refresh();
|
this.$.model.refresh();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getImagePath(dmsId) {
|
getImagePath(dmsId) {
|
||||||
return this.vnFile.getPath(`/api/dms/${dmsId}/downloadFile`);
|
return this.vnFile.getPath(`/api/Claims/${dmsId}/downloadFile`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
Are you sure you want to continue?: ¿Seguro que quieres continuar?
|
Are you sure you want to continue?: ¿Seguro que quieres continuar?
|
||||||
Drag & Drop photos here...: Arrastra y suelta fotos aquí...
|
Drag & Drop photos here...: Arrastra y suelta fotos aquí...
|
||||||
Photo deleted: Foto eliminada
|
File deleted: Archivo eliminado
|
||||||
Photo uploaded!: Foto subida!
|
File uploaded!: Archivo subido!
|
||||||
Select photo: Seleccionar foto
|
Select file: Seleccionar fichero
|
|
@ -29,4 +29,19 @@ vn-claim-photos {
|
||||||
height: 288px;
|
height: 288px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),
|
||||||
|
0 3px 1px -2px rgba(0,0,0,.2),
|
||||||
|
0 1px 5px 0 rgba(0,0,0,.12);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
|
||||||
|
}
|
||||||
|
.video:hover {
|
||||||
|
border: 2px solid $color-primary
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -33,7 +33,6 @@ describe('Address updateAddress', () => {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(error).toBeDefined();
|
|
||||||
expect(error.message).toEqual('Incoterms is required for a non UEE member');
|
expect(error.message).toEqual('Incoterms is required for a non UEE member');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -57,7 +56,6 @@ describe('Address updateAddress', () => {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(error).toBeDefined();
|
|
||||||
expect(error.message).toEqual('Customs agent is required for a non UEE member');
|
expect(error.message).toEqual('Customs agent is required for a non UEE member');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -91,6 +89,8 @@ describe('Address updateAddress', () => {
|
||||||
it('should return an error for a user without enough privileges', async() => {
|
it('should return an error for a user without enough privileges', async() => {
|
||||||
const tx = await models.Client.beginTransaction({});
|
const tx = await models.Client.beginTransaction({});
|
||||||
|
|
||||||
|
let error;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
ctx.args = {
|
ctx.args = {
|
||||||
|
@ -124,8 +124,10 @@ describe('Address updateAddress', () => {
|
||||||
expect(address.isLogifloraAllowed).toEqual(true);
|
expect(address.isLogifloraAllowed).toEqual(true);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
|
ctx.req.accessToken.userId = employeeId;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
|
ctx.req.accessToken.userId = employeeId;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -89,11 +89,12 @@ module.exports = function(Self) {
|
||||||
const args = ctx.args;
|
const args = ctx.args;
|
||||||
const userId = ctx.req.accessToken.userId;
|
const userId = ctx.req.accessToken.userId;
|
||||||
const myOptions = {};
|
const myOptions = {};
|
||||||
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant', myOptions);
|
|
||||||
|
|
||||||
if (typeof options == 'object')
|
if (typeof options == 'object')
|
||||||
Object.assign(myOptions, options);
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant', myOptions);
|
||||||
|
|
||||||
if (args.isLogifloraAllowed && !isSalesAssistant)
|
if (args.isLogifloraAllowed && !isSalesAssistant)
|
||||||
throw new UserError(`You don't have enough privileges`);
|
throw new UserError(`You don't have enough privileges`);
|
||||||
|
|
||||||
|
|
|
@ -46,10 +46,6 @@ module.exports = Self => {
|
||||||
message: 'TIN must be unique'
|
message: 'TIN must be unique'
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.validatesUniquenessOf('socialName', {
|
|
||||||
message: 'The company name must be unique'
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.validatesFormatOf('email', {
|
Self.validatesFormatOf('email', {
|
||||||
message: 'Invalid email',
|
message: 'Invalid email',
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
|
@ -63,17 +59,37 @@ module.exports = Self => {
|
||||||
min: 3, max: 10
|
min: 3, max: 10
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Self.validateAsync('socialName', socialNameIsUnique, {
|
||||||
|
message: 'The company name must be unique'
|
||||||
|
});
|
||||||
|
|
||||||
|
async function socialNameIsUnique(err, done) {
|
||||||
|
const filter = {
|
||||||
|
where: {
|
||||||
|
and: [
|
||||||
|
{socialName: this.socialName},
|
||||||
|
{isActive: true},
|
||||||
|
{id: {neq: this.id}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const client = await Self.app.models.Client.findOne(filter);
|
||||||
|
if (client)
|
||||||
|
err();
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
Self.validateAsync('iban', ibanNeedsValidation, {
|
Self.validateAsync('iban', ibanNeedsValidation, {
|
||||||
message: 'The IBAN does not have the correct format'
|
message: 'The IBAN does not have the correct format'
|
||||||
});
|
});
|
||||||
|
|
||||||
async function ibanNeedsValidation(err, done) {
|
async function ibanNeedsValidation(err, done) {
|
||||||
let filter = {
|
const filter = {
|
||||||
fields: ['code'],
|
fields: ['code'],
|
||||||
where: {id: this.countryFk}
|
where: {id: this.countryFk}
|
||||||
};
|
};
|
||||||
let country = await Self.app.models.Country.findOne(filter);
|
const country = await Self.app.models.Country.findOne(filter);
|
||||||
let code = country ? country.code.toLowerCase() : null;
|
const code = country ? country.code.toLowerCase() : null;
|
||||||
if (code != 'es')
|
if (code != 'es')
|
||||||
return done();
|
return done();
|
||||||
|
|
||||||
|
@ -90,12 +106,12 @@ module.exports = Self => {
|
||||||
if (!this.isTaxDataChecked)
|
if (!this.isTaxDataChecked)
|
||||||
return done();
|
return done();
|
||||||
|
|
||||||
let filter = {
|
const filter = {
|
||||||
fields: ['code'],
|
fields: ['code'],
|
||||||
where: {id: this.countryFk}
|
where: {id: this.countryFk}
|
||||||
};
|
};
|
||||||
let country = await Self.app.models.Country.findOne(filter);
|
const country = await Self.app.models.Country.findOne(filter);
|
||||||
let code = country ? country.code.toLowerCase() : null;
|
const code = country ? country.code.toLowerCase() : null;
|
||||||
|
|
||||||
if (!this.fi || !validateTin(this.fi, code))
|
if (!this.fi || !validateTin(this.fi, code))
|
||||||
err();
|
err();
|
||||||
|
@ -118,8 +134,8 @@ module.exports = Self => {
|
||||||
function cannotHaveET(err) {
|
function cannotHaveET(err) {
|
||||||
if (!this.fi) return;
|
if (!this.fi) return;
|
||||||
|
|
||||||
let tin = this.fi.toUpperCase();
|
const tin = this.fi.toUpperCase();
|
||||||
let cannotHaveET = /^[A-B]/.test(tin);
|
const cannotHaveET = /^[A-B]/.test(tin);
|
||||||
|
|
||||||
if (cannotHaveET && this.isEqualizated)
|
if (cannotHaveET && this.isEqualizated)
|
||||||
err();
|
err();
|
||||||
|
|
|
@ -30,7 +30,7 @@ module.exports = Self => {
|
||||||
SUM(iidd.amount) totalDueDay
|
SUM(iidd.amount) totalDueDay
|
||||||
FROM vn.invoiceIn ii
|
FROM vn.invoiceIn ii
|
||||||
LEFT JOIN (SELECT SUM(iit.taxableBase) totalTaxableBase,
|
LEFT JOIN (SELECT SUM(iit.taxableBase) totalTaxableBase,
|
||||||
SUM(iit.taxableBase * (1 + (ti.PorcentajeIva / 100))) totalVat
|
CAST(SUM(iit.taxableBase * (1 + (ti.PorcentajeIva / 100))) AS DECIMAL(10,2)) totalVat
|
||||||
FROM vn.invoiceInTax iit
|
FROM vn.invoiceInTax iit
|
||||||
LEFT JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk
|
LEFT JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk
|
||||||
WHERE iit.invoiceInFk = ?) iit ON TRUE
|
WHERE iit.invoiceInFk = ?) iit ON TRUE
|
||||||
|
|
|
@ -13,13 +13,14 @@
|
||||||
vn-id="supplier"
|
vn-id="supplier"
|
||||||
url="Suppliers"
|
url="Suppliers"
|
||||||
label="Supplier"
|
label="Supplier"
|
||||||
search-function="{or: [{id: $search}, {name: {like: '%'+ $search +'%'}}]}"
|
search-function="{or: [{id: $search}, {name: {like: '%'+ $search +'%'}}, {nif: {like: '%'+ $search +'%'}}]}"
|
||||||
|
fields="['nif']"
|
||||||
show-field="name"
|
show-field="name"
|
||||||
value-field="id"
|
value-field="id"
|
||||||
ng-model="$ctrl.invoiceIn.supplierFk"
|
ng-model="$ctrl.invoiceIn.supplierFk"
|
||||||
order="id"
|
order="id"
|
||||||
vn-focus>
|
vn-focus>
|
||||||
<tpl-item>{{id}}: {{name}}</tpl-item>
|
<tpl-item>{{id}}: {{nif}}: {{name}}</tpl-item>
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
vn-one
|
vn-one
|
||||||
|
@ -30,14 +31,6 @@
|
||||||
label="Issued"
|
label="Issued"
|
||||||
ng-model="$ctrl.invoiceIn.issued">
|
ng-model="$ctrl.invoiceIn.issued">
|
||||||
</vn-date-picker>
|
</vn-date-picker>
|
||||||
<vn-autocomplete
|
|
||||||
vn-one
|
|
||||||
label="Currency"
|
|
||||||
ng-model="$ctrl.invoiceIn.currencyFk"
|
|
||||||
url="Currencies"
|
|
||||||
show-field="code"
|
|
||||||
value-field="id">
|
|
||||||
</vn-autocomplete>
|
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
vn-one
|
vn-one
|
||||||
label="Company"
|
label="Company"
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
const models = require('vn-loopback/server/server').models;
|
const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
describe('item lastEntriesFilter()', () => {
|
describe('item lastEntriesFilter()', () => {
|
||||||
const minDate = new Date(value);
|
it('should return one entry for the given item', async() => {
|
||||||
minHour.setHours(0, 0, 0, 0);
|
const minDate = new Date();
|
||||||
const maxDate = new Date(value);
|
minDate.setHours(0, 0, 0, 0);
|
||||||
maxHour.setHours(23, 59, 59, 59);
|
const maxDate = new Date();
|
||||||
|
maxDate.setHours(23, 59, 59, 59);
|
||||||
|
|
||||||
it('should return one entry for a given item', async() => {
|
|
||||||
const tx = await models.Item.beginTransaction({});
|
const tx = await models.Item.beginTransaction({});
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
@ -23,13 +22,18 @@ describe('item lastEntriesFilter()', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return five entries for a given item', async() => {
|
it('should return five entries for the given item', async() => {
|
||||||
|
const minDate = new Date();
|
||||||
|
minDate.setHours(0, 0, 0, 0);
|
||||||
|
minDate.setMonth(minDate.getMonth() - 2, 1);
|
||||||
|
|
||||||
|
const maxDate = new Date();
|
||||||
|
maxDate.setHours(23, 59, 59, 59);
|
||||||
|
|
||||||
const tx = await models.Item.beginTransaction({});
|
const tx = await models.Item.beginTransaction({});
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
minDate.setMonth(minDate.getMonth() - 2, 1);
|
|
||||||
|
|
||||||
const filter = {where: {itemFk: 1, landed: {between: [minDate, maxDate]}}};
|
const filter = {where: {itemFk: 1, landed: {between: [minDate, maxDate]}}};
|
||||||
const result = await models.Item.lastEntriesFilter(filter, options);
|
const result = await models.Item.lastEntriesFilter(filter, options);
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
<th field="salesPersonFk">
|
<th field="salesPersonFk">
|
||||||
<span translate>Salesperson</span>
|
<span translate>Salesperson</span>
|
||||||
</th>
|
</th>
|
||||||
<th field="shipped" shrink-date>
|
<th field="shippedDate" shrink-date>
|
||||||
<span translate>Date</span>
|
<span translate>Date</span>
|
||||||
</th>
|
</th>
|
||||||
<th field="theoreticalHour">
|
<th field="theoreticalHour">
|
||||||
|
@ -153,8 +153,8 @@
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="chip {{::$ctrl.compareDate(ticket.shipped)}}">
|
<span class="chip {{::$ctrl.compareDate(ticket.shippedDate)}}">
|
||||||
{{::ticket.shipped | date: 'dd/MM/yyyy'}}
|
{{::ticket.shippedDate | date: 'dd/MM/yyyy'}}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{::ticket.zoneLanding | date: 'HH:mm'}}</td>
|
<td>{{::ticket.zoneLanding | date: 'HH:mm'}}</td>
|
||||||
|
|
|
@ -52,7 +52,7 @@ export default class Controller extends Section {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'shipped',
|
field: 'shippedDate',
|
||||||
searchable: false
|
searchable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -136,7 +136,7 @@ export default class Controller extends Section {
|
||||||
return {'z.hour': value};
|
return {'z.hour': value};
|
||||||
case 'practicalHour':
|
case 'practicalHour':
|
||||||
return {'zed.etc': value};
|
return {'zed.etc': value};
|
||||||
case 'shipped':
|
case 'shippedDate':
|
||||||
return {'t.shipped': {
|
return {'t.shipped': {
|
||||||
between: this.dateRange(value)}
|
between: this.dateRange(value)}
|
||||||
};
|
};
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
{{::order.name | dashIfEmpty}}
|
{{::order.name | dashIfEmpty}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td expand>
|
<vn-td>
|
||||||
<span
|
<span
|
||||||
vn-click-stop="clientDescriptor.show($event, order.clientFk)"
|
vn-click-stop="clientDescriptor.show($event, order.clientFk)"
|
||||||
class="link">
|
class="link">
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
disabled="true">
|
disabled="true">
|
||||||
</vn-check>
|
</vn-check>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td center>{{::order.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
|
<vn-td shrink-datetime>{{::order.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
|
||||||
<vn-td shrink-date>
|
<vn-td shrink-date>
|
||||||
<span class="chip {{$ctrl.compareDate(order.landed)}}">
|
<span class="chip {{$ctrl.compareDate(order.landed)}}">
|
||||||
{{::order.landed | date:'dd/MM/yyyy'}}
|
{{::order.landed | date:'dd/MM/yyyy'}}
|
||||||
|
|
|
@ -78,7 +78,7 @@
|
||||||
<vn-float-button
|
<vn-float-button
|
||||||
vn-bind="+"
|
vn-bind="+"
|
||||||
fixed-bottom-right
|
fixed-bottom-right
|
||||||
vn-tooltip="New row"
|
vn-tooltip="Add row"
|
||||||
ui-sref="supplier.card.agencyTerm.create"
|
ui-sref="supplier.card.agencyTerm.create"
|
||||||
icon="add"
|
icon="add"
|
||||||
label="Add">
|
label="Add">
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Controller extends Section {
|
||||||
set sales(value) {
|
set sales(value) {
|
||||||
this._sales = value;
|
this._sales = value;
|
||||||
|
|
||||||
if (value) this.applyVolumes();
|
if (value && value.length) this.applyVolumes();
|
||||||
}
|
}
|
||||||
|
|
||||||
applyVolumes() {
|
applyVolumes() {
|
||||||
|
|
|
@ -73,15 +73,14 @@ class Controller extends Section {
|
||||||
for (let event of $events)
|
for (let event of $events)
|
||||||
zoneIds.push(event.zoneFk);
|
zoneIds.push(event.zoneFk);
|
||||||
|
|
||||||
this.$.zoneEvents.show($event.target);
|
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
zoneIds: zoneIds,
|
zoneIds: zoneIds,
|
||||||
date: day
|
date: day
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$http.post(`Zones/getZoneClosing`, params)
|
this.$http.post(`Zones/getZoneClosing`, params)
|
||||||
.then(res => this.zoneClosing = res.data);
|
.then(res => this.zoneClosing = res.data)
|
||||||
|
.then(() => this.$.zoneEvents.show($event.target));
|
||||||
}
|
}
|
||||||
|
|
||||||
preview(zone) {
|
preview(zone) {
|
||||||
|
|
|
@ -7,6 +7,10 @@ const fs = require('fs-extra');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: 'delivery-note',
|
name: 'delivery-note',
|
||||||
|
created() {
|
||||||
|
if (!this.type)
|
||||||
|
this.type = 'deliveryNote';
|
||||||
|
},
|
||||||
async serverPrefetch() {
|
async serverPrefetch() {
|
||||||
this.client = await this.fetchClient(this.ticketId);
|
this.client = await this.fetchClient(this.ticketId);
|
||||||
this.ticket = await this.fetchTicket(this.ticketId);
|
this.ticket = await this.fetchTicket(this.ticketId);
|
||||||
|
@ -129,7 +133,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 429 KiB |
Binary file not shown.
Loading…
Reference in New Issue