dms update files
gitea/salix/test This commit looks good Details

This commit is contained in:
Joan Sanchez 2019-07-15 11:40:11 +02:00
parent 88b698f3eb
commit fefb389872
22 changed files with 670 additions and 17 deletions

View File

@ -0,0 +1,22 @@
const app = require('vn-loopback/server/server');
describe('dms updateFile()', () => {
it(`should return an error for a user without enough privileges`, async() => {
let clientId = 101;
let companyId = 442;
let warehouseId = 1;
let dmsTypeId = 14;
let dmsId = 1;
let ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: dmsTypeId}};
let error;
await app.models.Dms.updateFile(ctx, dmsId, warehouseId, companyId, dmsTypeId).catch(e => {
error = e;
}).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`);
});
expect(error).toBeDefined();
});
});

View File

@ -0,0 +1,158 @@
const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra');
module.exports = Self => {
Self.remoteMethodCtx('updateFile', {
description: 'updates a file properties or file',
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'Number',
description: 'The document id',
http: {source: 'path'}
},
{
arg: 'warehouseId',
type: 'Number',
description: 'The warehouse id'
}, {
arg: 'companyId',
type: 'Number',
description: 'The company id'
}, {
arg: 'dmsTypeId',
type: 'Number',
description: 'The dms type id'
}, {
arg: 'reference',
type: 'String'
}, {
arg: 'description',
type: 'String'
}, {
arg: 'hasFile',
type: 'Boolean',
description: 'True if has an attached file'
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/:id/updateFile`,
verb: 'POST'
}
});
Self.updateFile = async(ctx, id, warehouseId, companyId,
dmsTypeId, reference, description, hasFile, options) => {
const storageConnector = Self.app.dataSources.storage.connector;
const models = Self.app.models;
const fileOptions = {};
let tx;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, dmsTypeId);
if (!hasWriteRole)
throw new UserError(`You don't have enough privileges`);
// Upload file to temporary path
const tempContainer = await getContainer('temp');
let files = [];
try {
const uploaded = await models.Container.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
files = Object.values(uploaded.files).map(file => {
return file[0];
});
} catch (err) {
if (err.message != 'No file content uploaded')
throw e;
}
const updatedDmsList = [];
for (const file of files) {
const updatedDms = await updateDms(id, dmsTypeId, companyId, warehouseId,
reference, description, hasFile, file.name, myOptions);
const pathHash = storageConnector.getPathHash(updatedDms.id);
const container = await getContainer(pathHash);
const originPath = `${tempContainer.client.root}/${tempContainer.name}/${file.name}`;
const destinationPath = `${container.client.root}/${pathHash}/${updatedDms.file}`;
fs.rename(originPath, destinationPath);
updatedDmsList.push(updatedDms);
}
if (tx) await tx.commit();
return updatedDmsList;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
async function updateDms(id, dmsTypeId, companyId, warehouseId,
reference, description, hasFile, fileName, myOptions) {
const storageConnector = Self.app.dataSources.storage.connector;
const dms = await Self.findById(id, null, myOptions);
const updatedDms = await dms.updateAttributes({
dmsTypeFk: dmsTypeId,
companyFk: companyId,
warehouseFk: warehouseId,
reference: reference,
description: description,
hasFile: hasFile
}, myOptions);
const oldExtension = storageConnector.getFileExtension(dms.file);
const newExtension = storageConnector.getFileExtension(fileName);
try {
if (oldExtension != newExtension) {
const pathHash = storageConnector.getPathHash(updatedDms.id);
await Self.app.models.Container.removeFile(pathHash, dms.file);
}
} catch (err) {}
fileName = `${updatedDms.id}.${newExtension}`;
return updatedDms.updateAttribute('file', fileName, myOptions);
}
/**
* Returns a container instance
* If doesn't exists creates a new one
*
* @param {String} name Container name
* @return {Object} Container instance
*/
async function getContainer(name) {
const models = Self.app.models;
let container;
try {
container = await models.Container.getContainer(name);
} catch (err) {
if (err.code === 'ENOENT') {
container = await models.Container.createContainer({
name: name
});
} else throw err;
}
return container;
}
};

View File

@ -9,27 +9,25 @@ module.exports = Self => {
{ {
arg: 'warehouseId', arg: 'warehouseId',
type: 'Number', type: 'Number',
description: '' description: 'The warehouse id'
}, { }, {
arg: 'companyId', arg: 'companyId',
type: 'Number', type: 'Number',
description: '' description: 'The company id'
}, { }, {
arg: 'dmsTypeId', arg: 'dmsTypeId',
type: 'Number', type: 'Number',
description: '' description: 'The dms type id'
}, { }, {
arg: 'reference', arg: 'reference',
type: 'String', type: 'String'
description: ''
}, { }, {
arg: 'description', arg: 'description',
type: 'String', type: 'String'
description: ''
}, { }, {
arg: 'hasFile', arg: 'hasFile',
type: 'Boolean', type: 'Boolean',
description: '' description: 'True if has an attached file'
}], }],
returns: { returns: {
type: 'Object', type: 'Object',

View File

@ -2,4 +2,5 @@ module.exports = Self => {
require('../methods/dms/downloadFile')(Self); require('../methods/dms/downloadFile')(Self);
require('../methods/dms/uploadFile')(Self); require('../methods/dms/uploadFile')(Self);
require('../methods/dms/removeFile')(Self); require('../methods/dms/removeFile')(Self);
require('../methods/dms/updateFile')(Self);
}; };

View File

@ -141,6 +141,12 @@ vn-table {
background-color: $color-alert-medium; background-color: $color-alert-medium;
} }
& > vn-td vn-icon-menu {
display: inline-block;
color: $color-main;
padding: .25em
}
& > [actions] { & > [actions] {
width: 1px; width: 1px;

View File

@ -9,7 +9,7 @@ module.exports = app => {
}; };
storageConnector.getFileExtension = function(fileName) { storageConnector.getFileExtension = function(fileName) {
return fileName.split('.').pop(); return fileName.split('.').pop().toLowerCase();
}; };
storageConnector.getPathHash = function(id) { storageConnector.getPathHash = function(id) {

View File

@ -0,0 +1,63 @@
<vn-watcher
vn-id="watcher"
data="$ctrl.dms">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" margin-medium enctype="multipart/form-data">
<div compact>
<vn-card pad-large>
<vn-horizontal>
<vn-textfield vn-one vn-focus
label="Reference"
field="$ctrl.dms.reference">
</vn-textfield>
<vn-autocomplete vn-one required="true"
label="Company"
field="$ctrl.dms.companyId"
url="/api/Companies"
show-field="code"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one required="true"
label="Warehouse"
field="$ctrl.dms.warehouseId"
url="/api/Warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete vn-one required="true"
label="Type"
field="$ctrl.dms.dmsTypeId"
url="/api/DmsTypes"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textarea vn-one required="true"
label="Description"
field="$ctrl.dms.description">
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<vn-input-file vn-one
label="File"
model="$ctrl.dms.files"
on-change="$ctrl.onFileChange(files)"
accept=".pdf, .png, .jpg, .jpeg, application/zip, application/rar">
</vn-input-file>
</vn-horizontal>
<vn-vertical>
<vn-check
label="Attached file"
field="$ctrl.dms.hasFile">
</vn-check>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
<vn-button ui-sref="client.card.dms.index" label="Cancel"></vn-button>
</vn-button-bar>
</div>
</form>

View File

@ -0,0 +1,86 @@
import ngModule from '../../module';
import './style.scss';
class Controller {
constructor($scope, $http, $state, $translate, vnApp) {
this.$ = $scope;
this.$http = $http;
this.$state = $state;
this.$stateParams = $state.params;
this.$translate = $translate;
this.vnApp = vnApp;
}
get client() {
return this._client;
}
set client(value) {
this._client = value;
if (value)
this.setDefaultParams();
}
setDefaultParams() {
const path = `/api/Dms/${this.$stateParams.dmsId}`;
this.$http.get(path).then(res => {
const dms = res.data && res.data;
this.dms = {
reference: dms.reference,
warehouseId: dms.warehouseFk,
companyId: dms.companyFk,
dmsTypeId: dms.dmsTypeFk,
description: dms.description,
hasFile: dms.hasFile,
files: []
};
});
}
onSubmit() {
const query = `/api/dms/${this.$stateParams.dmsId}/updateFile`;
const options = {
method: 'POST',
url: query,
params: this.dms,
headers: {
'Content-Type': undefined
},
transformRequest: files => {
const formData = new FormData();
for (let i = 0; i < files.length; i++)
formData.append(files[i].name, files[i]);
return formData;
},
data: this.dms.files
};
this.$http(options).then(res => {
if (res) {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.$.watcher.updateOriginalData();
this.$state.go('client.card.dms.index');
}
});
}
onFileChange(files) {
if (files.length > 0) {
this.$.$applyAsync(() => {
this.dms.hasFile = true;
});
}
}
}
Controller.$inject = ['$scope', '$http', '$state', '$translate', 'vnApp'];
ngModule.component('vnClientDmsEdit', {
template: require('./index.html'),
controller: Controller,
bindings: {
client: '<'
}
});

View File

@ -0,0 +1,60 @@
import './index';
describe('Client', () => {
describe('Component vnClientDmsCreate', () => {
let controller;
let $scope;
let $httpBackend;
let $httpParamSerializer;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnClientDmsCreate', {$scope});
controller._client = {id: 101, name: 'Bruce wayne'};
}));
describe('client() setter', () => {
it('should set the client data and then call setDefaultParams()', () => {
spyOn(controller, 'setDefaultParams');
controller.client = {
id: 15,
name: 'Bruce wayne'
};
expect(controller.client).toBeDefined();
expect(controller.setDefaultParams).toHaveBeenCalledWith();
});
});
describe('setDefaultParams()', () => {
it('should perform a GET query and define the dms property on controller', () => {
const params = {filter: {
where: {code: 'paymentsLaw'}
}};
let serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `/api/DmsTypes/findOne?${serializedParams}`).respond({id: 12, code: 'paymentsLaw'});
$httpBackend.expect('GET', `/api/DmsTypes/findOne?${serializedParams}`);
controller.setDefaultParams();
$httpBackend.flush();
expect(controller.dms).toBeDefined();
expect(controller.dms.reference).toEqual(101);
expect(controller.dms.dmsTypeId).toEqual(12);
});
});
describe('onFileChange()', () => {
it('should set dms hasFile property to true if has any files', () => {
const files = [{id: 1, name: 'MyFile'}];
controller.onFileChange(files);
$scope.$apply();
expect(controller.dms.hasFile).toBeTruthy();
});
});
});
});

View File

@ -0,0 +1,3 @@
Edit file: Editar fichero
File: Fichero
Attached file: Fichero adjunto

View File

@ -0,0 +1,7 @@
vn-ticket-request {
vn-textfield {
margin: 0!important;
max-width: 100px;
}
}

View File

@ -16,11 +16,11 @@
<vn-th field="dmsTypeFk" shrink>Type</vn-th> <vn-th field="dmsTypeFk" shrink>Type</vn-th>
<vn-th field="reference">Reference</vn-th> <vn-th field="reference">Reference</vn-th>
<vn-th>Description</vn-th> <vn-th>Description</vn-th>
<vn-th field="hasFile" center>Attached file</vn-th> <vn-th field="hasFile" center>Original</vn-th>
<vn-th shrink>File</vn-th> <vn-th shrink>File</vn-th>
<vn-th>Employee</vn-th> <vn-th shrink>Employee</vn-th>
<vn-th field="created">Created</vn-th> <vn-th field="created">Created</vn-th>
<vn-th></vn-th> <vn-th number></vn-th>
</vn-tr> </vn-tr>
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
@ -41,13 +41,13 @@
{{::document.dms.description}} {{::document.dms.description}}
</span> </span>
</vn-td> </vn-td>
<vn-td center> <vn-td shrink center>
<vn-check disabled="true" <vn-check disabled="true"
field="document.dms.hasFile"> field="document.dms.hasFile">
</vn-check> </vn-check>
</vn-td> </vn-td>
<vn-td shrink>{{::document.dms.file}}</vn-td> <vn-td shrink>{{::document.dms.file}}</vn-td>
<vn-td> <vn-td shrink>
<span class="link" <span class="link"
ng-click="$ctrl.showWorkerDescriptor($event, document.dms.workerFk)"> ng-click="$ctrl.showWorkerDescriptor($event, document.dms.workerFk)">
{{::document.dms.worker.user.nickname | dashIfEmpty}} {{::document.dms.worker.user.nickname | dashIfEmpty}}
@ -55,16 +55,21 @@
<vn-td> <vn-td>
{{::document.dms.created | dateTime:'dd/MM/yyyy HH:mm'}} {{::document.dms.created | dateTime:'dd/MM/yyyy HH:mm'}}
</vn-td> </vn-td>
<vn-td center shrink> <vn-td number>
<a target="_blank" <a target="_blank"
vn-tooltip="Download file" vn-tooltip="Download file"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}" href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}"
ng-show="document.hasFile"> ng-show="document.dms.hasFile">
<vn-icon-button <vn-icon-button
icon="cloud_download" icon="cloud_download"
title="{{'Download PDF' | translate}}"> title="{{'Download PDF' | translate}}">
</vn-icon-button> </vn-icon-button>
</a> </a>
<vn-icon-button ui-sref="client.card.dms.edit({dmsId: {{::document.dmsFk}}})"
vn-tooltip="Edit file"
icon="edit"
title="{{'Edit file' | translate}}">
</vn-icon-button>
<vn-icon-button <vn-icon-button
vn-tooltip="Remove file" vn-tooltip="Remove file"
icon="delete" icon="delete"

View File

@ -38,3 +38,4 @@ import './sms';
import './postcode'; import './postcode';
import './dms/index'; import './dms/index';
import './dms/create'; import './dms/create';
import './dms/edit';

View File

@ -336,6 +336,15 @@
"params": { "params": {
"client": "$ctrl.client" "client": "$ctrl.client"
} }
},
{
"url": "/:dmsId/edit",
"state": "client.card.dms.edit",
"component": "vn-client-dms-edit",
"description": "Edit file",
"params": {
"client": "$ctrl.client"
}
} }
] ]
} }

View File

@ -0,0 +1,63 @@
<vn-watcher
vn-id="watcher"
data="$ctrl.dms">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" margin-medium enctype="multipart/form-data">
<div compact>
<vn-card pad-large>
<vn-horizontal>
<vn-textfield vn-one vn-focus
label="Reference"
field="$ctrl.dms.reference">
</vn-textfield>
<vn-autocomplete vn-one required="true"
label="Company"
field="$ctrl.dms.companyId"
url="/api/Companies"
show-field="code"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one required="true"
label="Warehouse"
field="$ctrl.dms.warehouseId"
url="/api/Warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete vn-one required="true"
label="Type"
field="$ctrl.dms.dmsTypeId"
url="/api/DmsTypes"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textarea vn-one required="true"
label="Description"
field="$ctrl.dms.description">
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<vn-input-file vn-one
label="File"
model="$ctrl.dms.files"
on-change="$ctrl.onFileChange(files)"
accept=".pdf, .png, .jpg, .jpeg, application/zip, application/rar">
</vn-input-file>
</vn-horizontal>
<vn-vertical>
<vn-check
label="Attached file"
field="$ctrl.dms.hasFile">
</vn-check>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
<vn-button ui-sref="ticket.card.dms.index" label="Cancel"></vn-button>
</vn-button-bar>
</div>
</form>

View File

@ -0,0 +1,86 @@
import ngModule from '../../module';
import './style.scss';
class Controller {
constructor($scope, $http, $state, $translate, vnApp) {
this.$ = $scope;
this.$http = $http;
this.$state = $state;
this.$stateParams = $state.params;
this.$translate = $translate;
this.vnApp = vnApp;
}
get ticket() {
return this._ticket;
}
set ticket(value) {
this._ticket = value;
if (value)
this.setDefaultParams();
}
setDefaultParams() {
const path = `/api/Dms/${this.$stateParams.dmsId}`;
this.$http.get(path).then(res => {
const dms = res.data && res.data;
this.dms = {
reference: dms.reference,
warehouseId: dms.warehouseFk,
companyId: dms.companyFk,
dmsTypeId: dms.dmsTypeFk,
description: dms.description,
hasFile: dms.hasFile,
files: []
};
});
}
onSubmit() {
const query = `/api/dms/${this.$stateParams.dmsId}/updateFile`;
const options = {
method: 'POST',
url: query,
params: this.dms,
headers: {
'Content-Type': undefined
},
transformRequest: files => {
const formData = new FormData();
for (let i = 0; i < files.length; i++)
formData.append(files[i].name, files[i]);
return formData;
},
data: this.dms.files
};
this.$http(options).then(res => {
if (res) {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.$.watcher.updateOriginalData();
this.$state.go('ticket.card.dms.index');
}
});
}
onFileChange(files) {
if (files.length > 0) {
this.$.$applyAsync(() => {
this.dms.hasFile = true;
});
}
}
}
Controller.$inject = ['$scope', '$http', '$state', '$translate', 'vnApp'];
ngModule.component('vnTicketDmsEdit', {
template: require('./index.html'),
controller: Controller,
bindings: {
ticket: '<'
}
});

View File

@ -0,0 +1,60 @@
import './index';
describe('Client', () => {
describe('Component vnClientDmsCreate', () => {
let controller;
let $scope;
let $httpBackend;
let $httpParamSerializer;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnClientDmsCreate', {$scope});
controller._client = {id: 101, name: 'Bruce wayne'};
}));
describe('client() setter', () => {
it('should set the client data and then call setDefaultParams()', () => {
spyOn(controller, 'setDefaultParams');
controller.client = {
id: 15,
name: 'Bruce wayne'
};
expect(controller.client).toBeDefined();
expect(controller.setDefaultParams).toHaveBeenCalledWith();
});
});
describe('setDefaultParams()', () => {
it('should perform a GET query and define the dms property on controller', () => {
const params = {filter: {
where: {code: 'paymentsLaw'}
}};
let serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `/api/DmsTypes/findOne?${serializedParams}`).respond({id: 12, code: 'paymentsLaw'});
$httpBackend.expect('GET', `/api/DmsTypes/findOne?${serializedParams}`);
controller.setDefaultParams();
$httpBackend.flush();
expect(controller.dms).toBeDefined();
expect(controller.dms.reference).toEqual(101);
expect(controller.dms.dmsTypeId).toEqual(12);
});
});
describe('onFileChange()', () => {
it('should set dms hasFile property to true if has any files', () => {
const files = [{id: 1, name: 'MyFile'}];
controller.onFileChange(files);
$scope.$apply();
expect(controller.dms.hasFile).toBeTruthy();
});
});
});
});

View File

@ -0,0 +1,3 @@
Edit file: Editar fichero
File: Fichero
Attached file: Fichero adjunto

View File

@ -0,0 +1,7 @@
vn-ticket-request {
vn-textfield {
margin: 0!important;
max-width: 100px;
}
}

View File

@ -59,12 +59,17 @@
<a target="_blank" <a target="_blank"
vn-tooltip="Download file" vn-tooltip="Download file"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}" href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}"
ng-show="document.hasFile"> ng-show="document.dms.hasFile">
<vn-icon-button <vn-icon-button
icon="cloud_download" icon="cloud_download"
title="{{'Download PDF' | translate}}"> title="{{'Download PDF' | translate}}">
</vn-icon-button> </vn-icon-button>
</a> </a>
<vn-icon-button ui-sref="ticket.card.dms.edit({dmsId: {{::document.dmsFk}}})"
vn-tooltip="Edit file"
icon="edit"
title="{{'Edit file' | translate}}">
</vn-icon-button>
<vn-icon-button <vn-icon-button
vn-tooltip="Remove file" vn-tooltip="Remove file"
icon="delete" icon="delete"

View File

@ -33,3 +33,4 @@ import './log';
import './weekly'; import './weekly';
import './dms/index'; import './dms/index';
import './dms/create'; import './dms/create';
import './dms/edit';

View File

@ -233,6 +233,15 @@
"params": { "params": {
"ticket": "$ctrl.ticket" "ticket": "$ctrl.ticket"
} }
},
{
"url": "/:dmsId/edit",
"state": "ticket.card.dms.edit",
"component": "vn-ticket-dms-edit",
"description": "Edit file",
"params": {
"ticket": "$ctrl.ticket"
}
} }
] ]
} }