1762-worker_dms #31

Manually merged
bernat merged 4 commits from 1762-worker_dms into dev 2019-11-25 08:33:06 +00:00
32 changed files with 1102 additions and 19 deletions

View File

@ -63,7 +63,7 @@ module.exports = Self => {
}
try {
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId);
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId, myOptions);
if (!hasWriteRole)
throw new UserError(`You don't have enough privileges`);
@ -83,7 +83,7 @@ module.exports = Self => {
const originPath = `${tempContainer.client.root}/${tempContainer.name}/${file.name}`;
const destinationPath = `${container.client.root}/${pathHash}/${newDms.file}`;
fs.rename(originPath, destinationPath);
await fs.rename(originPath, destinationPath);
addedDms.push(newDms);
}

View File

@ -60,8 +60,8 @@ module.exports = Self => {
* @param {String} name The role name
* @return {Boolean} %true if user has the role, %false otherwise
*/
Self.hasRole = async function(userId, name) {
let roles = await Self.getRoles(userId);
Self.hasRole = async function(userId, name, options) {
let roles = await Self.getRoles(userId, options);
return roles.some(role => role == name);
};
@ -71,13 +71,13 @@ module.exports = Self => {
* @param {Integer} userId The user id
* @return {Object} User role list
*/
Self.getRoles = async userId => {
Self.getRoles = async(userId, options) => {
let result = await Self.rawSql(
`SELECT r.name
FROM account.user u
JOIN account.roleRole rr ON rr.role = u.role
JOIN account.role r ON r.id = rr.inheritsFrom
WHERE u.id = ?`, [userId]);
WHERE u.id = ?`, [userId], options);
let roles = [];
for (role of result)

View File

@ -5,17 +5,18 @@ module.exports = Self => {
*
* @param {Object} ctx - Request context
* @param {Interger} id - DmsType id
* @param {Object} options - Query options
* @return {Boolean} True for user with read privileges
*/
Self.hasReadRole = async(ctx, id) => {
Self.hasReadRole = async(ctx, id, options) => {
const models = Self.app.models;
const dmsType = await models.DmsType.findById(id, {
include: {
relation: 'readRole'
}
});
}, options);
return await hasRole(ctx, dmsType);
return await hasRole(ctx, dmsType, options);
};
/**
@ -24,17 +25,18 @@ module.exports = Self => {
*
* @param {Object} ctx - Request context
* @param {Interger} id - DmsType id
* @param {Object} options - Query options
* @return {Boolean} True for user with write privileges
*/
Self.hasWriteRole = async(ctx, id) => {
Self.hasWriteRole = async(ctx, id, options) => {
const models = Self.app.models;
const dmsType = await models.DmsType.findById(id, {
include: {
relation: 'writeRole'
}
});
}, options);
return await hasRole(ctx, dmsType);
return await hasRole(ctx, dmsType, options);
};
/**
@ -42,8 +44,9 @@ module.exports = Self => {
* read or write privileges
* @param {Object} ctx - Context
* @param {Object} dmsType - Dms type [read/write]
* @param {Object} options - Query options
*/
async function hasRole(ctx, dmsType) {
async function hasRole(ctx, dmsType, options) {
const models = Self.app.models;
const myUserId = ctx.req.accessToken.userId;
@ -51,8 +54,8 @@ module.exports = Self => {
const writeRole = dmsType.writeRole() && dmsType.writeRole().name;
const requiredRole = readRole || writeRole;
const hasRequiredRole = await models.Account.hasRole(myUserId, requiredRole);
const isRoot = await models.Account.hasRole(myUserId, 'root');
const hasRequiredRole = await models.Account.hasRole(myUserId, requiredRole, options);
const isRoot = await models.Account.hasRole(myUserId, 'root', options);
if (isRoot || hasRequiredRole)
return true;

View File

@ -1869,7 +1869,8 @@ INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `wa
VALUES
(1, 14, '1.txt', 'text/plain', 5, 1, 442, NULL, FALSE, 'Ticket:11', 'Ticket:11 dms for the ticket', CURDATE()),
(2, 5, '2.txt', 'text/plain', 5, 1, 442, 1, TRUE, 'Client:104', 'Client:104 dms for the client', CURDATE()),
(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, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Worker: 106', 'Worker:106 readme', CURDATE());
INSERT INTO `vn`.`ticketDms`(`ticketFk`, `dmsFk`)
VALUES
@ -1880,6 +1881,10 @@ INSERT INTO `vn`.`clientDms`(`clientFk`, `dmsFk`)
(104, 2),
(104, 3);
INSERT INTO `vn`.`workerDocument`(`id`, `worker`, `document`)
VALUES
(1, 106, 4);
INSERT INTO `vn`.`device` (`sn`, `model`, `userFk`)
VALUES
('aaa', 'android', '9');

View File

@ -1,6 +1,6 @@
module.exports = Self => {
Self.remoteMethodCtx('removeFile', {
description: 'Removes a ticket document',
description: 'Removes a claim document',
accessType: 'WRITE',
accepts: {
arg: 'id',

View File

@ -20,7 +20,7 @@ module.exports = Self => {
Self.removeFile = async(ctx, id) => {
const models = Self.app.models;
const clientDms = await models.ClientDms.findById(id);
const clientDms = await Self.findById(id);
await models.Dms.removeFile(ctx, clientDms.dmsFk);

View File

@ -0,0 +1,23 @@
module.exports = Self => {
Self.remoteMethodCtx('allowedContentTypes', {
description: 'Returns a list of allowed contentTypes',
accessType: 'READ',
returns: {
type: ['Object'],
root: true
},
http: {
path: `/allowedContentTypes`,
verb: 'GET'
}
});
Self.allowedContentTypes = async() => {
const storageConnector = Self.app.dataSources.storage.connector;
const allowedContentTypes = storageConnector.allowedContentTypes;
const modelAllowedContentTypes = Self.definition.settings.allowedContentTypes;
return modelAllowedContentTypes || allowedContentTypes;
};
};

View File

@ -0,0 +1,30 @@
module.exports = Self => {
Self.remoteMethodCtx('removeFile', {
description: 'Removes a worker document',
accessType: 'WRITE',
accepts: {
arg: 'id',
type: 'Number',
description: 'The document id',
http: {source: 'path'}
},
returns: {
type: 'Object',
root: true
},
http: {
path: `/:id/removeFile`,
verb: 'POST'
}
});
Self.removeFile = async(ctx, id) => {
const models = Self.app.models;
const workerDms = await Self.findById(id);
await models.Dms.removeFile(ctx, workerDms.dmsFk);
return workerDms.destroy();
};
};

View File

@ -0,0 +1,18 @@
const app = require('vn-loopback/server/server');
describe('WorkerDms removeFile()', () => {
const workerDmsFk = 1;
it(`should return an error for a user without enough privileges`, async() => {
let clientId = 101;
let ctx = {req: {accessToken: {userId: clientId}}};
let error;
await app.models.WorkerDms.removeFile(ctx, workerDmsFk).catch(e => {
error = e;
}).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`);
});
expect(error).toBeDefined();
});
});

View File

@ -0,0 +1,19 @@
const app = require('vn-loopback/server/server');
describe('Worker uploadFile()', () => {
it(`should return an error for a user without enough privileges`, async() => {
let workerId = 106;
let currentUserId = 102;
let hhrrDataId = 3;
let ctx = {req: {accessToken: {userId: currentUserId}}, args: {dmsTypeId: hhrrDataId}};
let error;
await app.models.Worker.uploadFile(ctx, workerId).catch(e => {
error = e;
}).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`);
});
expect(error).toBeDefined();
});
});

View File

@ -0,0 +1,76 @@
module.exports = Self => {
Self.remoteMethodCtx('uploadFile', {
description: 'Upload and attach a file to a client',
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'Number',
description: 'The worker id',
http: {source: 'path'}
}, {
arg: 'warehouseId',
type: 'Number',
description: 'The warehouse id',
required: true
}, {
arg: 'companyId',
type: 'Number',
description: 'The company id',
required: true
}, {
arg: 'dmsTypeId',
type: 'Number',
description: 'The dms type id',
required: true
}, {
arg: 'reference',
type: 'String',
required: true
}, {
arg: 'description',
type: 'String',
required: true
}, {
arg: 'hasFile',
type: 'Boolean',
description: 'True if has an attached file',
required: true
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/:id/uploadFile`,
verb: 'POST'
}
});
Self.uploadFile = async(ctx, id) => {
const models = Self.app.models;
const promises = [];
const tx = await Self.beginTransaction({});
try {
const options = {transaction: tx};
const uploadedFiles = await models.Dms.uploadFile(ctx, options);
uploadedFiles.forEach(dms => {
const newWorkerDms = models.WorkerDms.create({
workerFk: id,
dmsFk: dms.id
}, options);
promises.push(newWorkerDms);
});
const resolvedPromises = await Promise.all(promises);
await tx.commit();
return resolvedPromises;
} catch (err) {
await tx.rollback();
throw err;
}
};
};

View File

@ -20,6 +20,9 @@
"WorkCenterHoliday": {
"dataSource": "vn"
},
"WorkerDms": {
"dataSource": "vn"
},
"Worker": {
"dataSource": "vn"
},

View File

@ -0,0 +1,4 @@
module.exports = Self => {
require('../methods/worker-dms/removeFile')(Self);
require('../methods/worker-dms/allowedContentTypes')(Self);
};

View File

@ -0,0 +1,47 @@
{
"name": "WorkerDms",
"base": "Loggable",
"log": {
"model":"ClientLog",
"relation": "worker",
"showField": "dmsFk"
},
"options": {
"mysql": {
"table": "workerDocument"
}
},
"properties": {
"id": {
"type": "Number",
"id": true
},
"dmsFk": {
"type": "Number",
"required": true,
"mysql": {
"columnName": "document"
}
},
"workerFk": {
"type": "Number",
"required": true,
"mysql": {
"columnName": "worker"
}
}
},
"relations": {
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
},
"dms": {
"type": "belongsTo",
"model": "Dms",
"foreignKey": "dmsFk"
}
}
}

View File

@ -3,4 +3,5 @@ module.exports = Self => {
require('../methods/worker/mySubordinates')(Self);
require('../methods/worker/isSubordinate')(Self);
require('../methods/worker/getWorkedHours')(Self);
require('../methods/worker/uploadFile')(Self);
};

View File

@ -0,0 +1,83 @@
<mg-ajax path="dms/upload" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.dms">
</vn-watcher>
<form
name="form"
ng-submit="$ctrl.onSubmit()"
class="vn-ma-md"
enctype="multipart/form-data">
<div class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
vn-one
vn-focus
label="Reference"
ng-model="$ctrl.dms.reference"
rule>
</vn-textfield>
<vn-autocomplete vn-one
label="Company"
ng-model="$ctrl.dms.companyId"
url="Companies"
show-field="code"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
label="Warehouse"
ng-model="$ctrl.dms.warehouseId"
url="Warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete vn-one
label="Type"
ng-model="$ctrl.dms.dmsTypeId"
url="DmsTypes"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textarea
vn-one
label="Description"
ng-model="$ctrl.dms.description"
rule>
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<vn-input-file
vn-one
label="File"
ng-model="$ctrl.dms.files"
on-change="$ctrl.onFileChange($files)"
accept="{{$ctrl.allowedContentTypes}}"
required="true"
multiple="true">
<append>
<vn-icon vn-none
color-marginal
title="{{$ctrl.contentTypesInfo}}"
icon="info">
</vn-icon>
</append>
</vn-input-file>
</vn-horizontal>
<vn-vertical>
<vn-check
label="Generate identifier for original file"
ng-model="$ctrl.dms.hasFile">
</vn-check>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Upload"></vn-submit>
<vn-button ui-sref="worker.card.dms.index" label="Cancel"></vn-button>
</vn-button-bar>
</div>
</form>

View File

@ -0,0 +1,114 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
class Controller extends Component {
constructor($element, $, vnConfig) {
super($element, $);
this.vnConfig = vnConfig;
this.dms = {
files: [],
hasFile: false,
hasFileAttached: false
};
}
get worker() {
return this._worker;
}
set worker(value) {
this._worker = value;
if (value) {
this.setDefaultParams();
this.getAllowedContentTypes();
}
}
getAllowedContentTypes() {
this.$http.get('workerDms/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes;
});
}
get contentTypesInfo() {
return this.$translate.instant('ContentTypesInfo', {
allowedContentTypes: this.allowedContentTypes
});
}
setDefaultParams() {
const params = {filter: {
where: {code: 'hhrrData'}
}};
this.$http.get('DmsTypes/findOne', {params}).then(res => {
const dmsType = res.data && res.data;
const companyId = this.vnConfig.companyFk;
const warehouseId = this.vnConfig.warehouseFk;
const defaultParams = {
reference: this.worker.id,
warehouseId: warehouseId,
companyId: companyId,
dmsTypeId: dmsType.id,
description: this.$translate.instant('WorkerFileDescription', {
dmsTypeName: dmsType.name,
workerId: this.worker.id,
workerName: this.worker.name
}).toUpperCase()
};
this.dms = Object.assign(this.dms, defaultParams);
});
}
onSubmit() {
const query = `Workers/${this.worker.id}/uploadFile`;
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('worker.card.dms.index');
}
});
}
onFileChange(files) {
let hasFileAttached = false;
if (files.length > 0)
hasFileAttached = true;
this.$.$applyAsync(() => {
this.dms.hasFileAttached = hasFileAttached;
});
}
}
Controller.$inject = ['$element', '$scope', 'vnConfig'];
ngModule.component('vnWorkerDmsCreate', {
template: require('./index.html'),
controller: Controller,
bindings: {
worker: '<'
}
});

View File

@ -0,0 +1,77 @@
import './index';
describe('Client', () => {
describe('Component vnWorkerDmsCreate', () => {
let $element;
let controller;
let $scope;
let $httpBackend;
let $httpParamSerializer;
beforeEach(ngModule('worker'));
beforeEach(angular.mock.inject(($compile, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
$element = $compile(`<vn-worker-dms-create></vn-worker-dms-create>`)($rootScope);
controller = $element.controller('vnWorkerDmsCreate');
controller._worker = {id: 101, name: 'Bruce wayne'};
}));
describe('worker() setter', () => {
it('should set the worker data and then call setDefaultParams() and getAllowedContentTypes()', () => {
spyOn(controller, 'setDefaultParams');
spyOn(controller, 'getAllowedContentTypes');
controller.worker = {
id: 15,
name: 'Bruce wayne'
};
expect(controller.worker).toBeDefined();
expect(controller.setDefaultParams).toHaveBeenCalledWith();
expect(controller.getAllowedContentTypes).toHaveBeenCalledWith();
});
});
describe('setDefaultParams()', () => {
it('should perform a GET query and define the dms property on controller', () => {
$httpBackend.whenRoute('GET', `DmsTypes`).respond({id: 12, code: 'hhrrData'});
const params = {filter: {
where: {code: 'hhrrData'}
}};
let serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `DmsTypes/findOne?${serializedParams}`).respond({id: 12, code: 'hhrrData'});
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 hasFileAttached property to true if has any files', () => {
const files = [{id: 1, name: 'MyFile'}];
controller.onFileChange(files);
$scope.$apply();
expect(controller.dms.hasFileAttached).toBeTruthy();
});
});
describe('getAllowedContentTypes()', () => {
it('should make an HTTP GET request to get the allowed content types', () => {
const expectedResponse = ['image/png', 'image/jpg'];
$httpBackend.when('GET', `workerDms/allowedContentTypes`).respond(expectedResponse);
$httpBackend.expect('GET', `workerDms/allowedContentTypes`);
controller.getAllowedContentTypes();
$httpBackend.flush();
expect(controller.allowedContentTypes).toBeDefined();
expect(controller.allowedContentTypes).toEqual('image/png, image/jpg');
});
});
});
});

View File

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

View File

@ -0,0 +1,82 @@
<vn-watcher
vn-id="watcher"
data="$ctrl.dms">
</vn-watcher>
<form
name="form"
ng-submit="$ctrl.onSubmit()"
class="vn-ma-md"
enctype="multipart/form-data">
<div class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
vn-one
vn-focus
label="Reference"
ng-model="$ctrl.dms.reference"
rule>
</vn-textfield>
<vn-autocomplete vn-one required="true"
label="Company"
ng-model="$ctrl.dms.companyId"
url="Companies"
show-field="code"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one required="true"
label="Warehouse"
ng-model="$ctrl.dms.warehouseId"
url="Warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete vn-one required="true"
label="Type"
ng-model="$ctrl.dms.dmsTypeId"
url="DmsTypes"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textarea
vn-one
required="true"
label="Description"
ng-model="$ctrl.dms.description"
rule>
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<vn-input-file
vn-one
label="File"
ng-model="$ctrl.dms.files"
on-change="$ctrl.onFileChange($files)"
accept="{{$ctrl.allowedContentTypes}}"
multiple="true">
<append>
<vn-icon vn-none
color-marginal
title="{{$ctrl.contentTypesInfo}}"
icon="info">
</vn-icon>
</append>
</vn-input-file>
</vn-horizontal>
<vn-vertical>
<vn-check disabled="true"
label="Generate identifier for original file"
ng-model="$ctrl.dms.hasFile">
</vn-check>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
<vn-button ui-sref="worker.card.dms.index" label="Cancel"></vn-button>
</vn-button-bar>
</div>
</form>

View File

@ -0,0 +1,94 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
class Controller extends Component {
get worker() {
return this._worker;
}
set worker(value) {
this._worker = value;
if (value) {
this.setDefaultParams();
this.getAllowedContentTypes();
}
}
getAllowedContentTypes() {
this.$http.get('WorkerDms/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes;
});
}
get contentTypesInfo() {
return this.$translate.instant('ContentTypesInfo', {
allowedContentTypes: this.allowedContentTypes
});
}
setDefaultParams() {
const path = `Dms/${this.$params.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,
hasFileAttached: false,
files: []
};
});
}
onSubmit() {
const query = `dms/${this.$params.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('worker.card.dms.index');
}
});
}
onFileChange(files) {
let hasFileAttached = false;
if (files.length > 0)
hasFileAttached = true;
this.$.$applyAsync(() => {
this.dms.hasFileAttached = hasFileAttached;
});
}
}
ngModule.component('vnWorkerDmsEdit', {
template: require('./index.html'),
controller: Controller,
bindings: {
worker: '<'
}
});

View File

@ -0,0 +1,84 @@
import './index';
describe('Worker', () => {
describe('Component vnClientDmsEdit', () => {
let controller;
let $scope;
let $element;
let $httpBackend;
beforeEach(ngModule('worker'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$element = angular.element(`<vn-worker-dms-edit></vn-worker-dms-edit`);
controller = $componentController('vnWorkerDmsEdit', {$element, $scope});
controller._worker = {id: 106};
controller.$params = {dmsId: 4};
}));
describe('worker() setter', () => {
it('should set the worker data and then call setDefaultParams() and getAllowedContentTypes()', () => {
spyOn(controller, 'setDefaultParams');
spyOn(controller, 'getAllowedContentTypes');
controller._worker = undefined;
controller.worker = {
id: 106
};
expect(controller.setDefaultParams).toHaveBeenCalledWith();
expect(controller.worker).toBeDefined();
expect(controller.getAllowedContentTypes).toHaveBeenCalledWith();
});
});
describe('setDefaultParams()', () => {
it('should perform a GET query and define the dms property on controller', () => {
const dmsId = 4;
const expectedResponse = {
reference: 101,
warehouseFk: 1,
companyFk: 442,
dmsTypeFk: 3,
description: 'Test',
hasFile: false,
hasFileAttached: false
};
$httpBackend.when('GET', `Dms/${dmsId}`).respond(expectedResponse);
$httpBackend.expect('GET', `Dms/${dmsId}`).respond(expectedResponse);
controller.setDefaultParams();
$httpBackend.flush();
expect(controller.dms).toBeDefined();
expect(controller.dms.reference).toEqual(101);
expect(controller.dms.dmsTypeId).toEqual(3);
});
});
describe('onFileChange()', () => {
it('should set dms hasFileAttached property to true if has any files', () => {
const files = [{id: 1, name: 'MyFile'}];
controller.dms = {hasFileAttached: false};
controller.onFileChange(files);
$scope.$apply();
expect(controller.dms.hasFileAttached).toBeTruthy();
});
});
describe('getAllowedContentTypes()', () => {
it('should make an HTTP GET request to get the allowed content types', () => {
const expectedResponse = ['image/png', 'image/jpg'];
$httpBackend.when('GET', `WorkerDms/allowedContentTypes`).respond(expectedResponse);
$httpBackend.expect('GET', `WorkerDms/allowedContentTypes`);
controller.getAllowedContentTypes();
$httpBackend.flush();
expect(controller.allowedContentTypes).toBeDefined();
expect(controller.allowedContentTypes).toEqual('image/png, image/jpg');
});
});
});
});

View File

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

View File

@ -0,0 +1,117 @@
<vn-crud-model
vn-id="model"
url="WorkerDms"
link="{workerFk: $ctrl.$params.id}"
filter="::$ctrl.filter"
limit="20"
data="$ctrl.workerDms"
order="dmsFk DESC"
auto-load="true">
</vn-crud-model>
<vn-data-viewer
model="model"
class="vn-w-lg">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="dmsFk" shrink>Id</vn-th>
<vn-th field="dmsTypeFk" shrink>Type</vn-th>
<vn-th field="hardCopyNumber" shrink number>Order</vn-th>
<vn-th field="reference" shrink>Reference</vn-th>
<vn-th expand>Description</vn-th>
<vn-th field="hasFile" shrink>Original</vn-th>
<vn-th shrink>File</vn-th>
<vn-th shrink>Employee</vn-th>
<vn-th field="created">Created</vn-th>
<vn-th shrink></vn-th>
<vn-th shrink></vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="document in $ctrl.workerDms">
<vn-td number shrink>{{::document.dmsFk}}</vn-td>
<vn-td shrink>
<span title="{{::document.dms.dmsType.name}}">
{{::document.dms.dmsType.name}}
</span>
</vn-td>
<vn-td shrink number>
<span class="chip" title="{{::document.dms.hardCopyNumber}}"
ng-class="{'message': document.dms.hardCopyNumber}">
{{::document.dms.hardCopyNumber}}
</span>
</vn-td>
<vn-td shrink>
<span title="{{::document.dms.reference}}">
{{::document.dms.reference}}
</span>
</vn-td>
<vn-td expand>
<span title="{{::document.dms.description}}">
{{::document.dms.description}}
</span>
</vn-td>
<vn-td shrink>
<vn-check disabled="true"
ng-model="document.dms.hasFile">
</vn-check>
</vn-td>
<vn-td shrink>
<a target="_blank"
title="{{'Download file' | translate}}"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">{{::document.dms.file}}
</a>
</vn-td>
<vn-td shrink>
<span class="link"
ng-click="$ctrl.showWorkerDescriptor($event, document.dms.workerFk)">
{{::document.dms.worker.user.nickname | dashIfEmpty}}
</span></vn-td>
<vn-td>
{{::document.dms.created | date:'dd/MM/yyyy HH:mm'}}
</vn-td>
<vn-td shrink>
<a target="_blank"
href="api/dms/{{::document.dmsFk}}/downloadFile?access_token={{::$ctrl.accessToken}}">
<vn-icon-button
icon="cloud_download"
title="{{'Download file' | translate}}">
</vn-icon-button>
</a>
</vn-td>
<vn-td shrink>
<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="$ctrl.showDeleteConfirm($index)"
title="{{'Remove file' | translate}}"
tabindex="-1">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<a ui-sref="worker.card.dms.create"
vn-tooltip="Upload file"
vn-bind="+"
fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>
<vn-confirm
vn-id="confirm"
message="This file will be deleted"
question="Are you sure you want to continue?"
on-response="$ctrl.deleteDms($response)">
</vn-confirm>

View File

@ -0,0 +1,76 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
class Controller extends Component {
constructor($element, $, vnToken) {
super($element, $);
this.accessToken = vnToken.token;
this.filter = {
include: {
relation: 'dms',
scope: {
fields: [
'dmsTypeFk',
'reference',
'hardCopyNumber',
'workerFk',
'description',
'hasFile',
'file',
'created',
],
include: [{
relation: 'dmsType',
scope: {
fields: ['name']
}
},
{
relation: 'worker',
scope: {
fields: ['userFk'],
include: {
relation: 'user',
scope: {
fields: ['nickname']
}
},
}
}]
},
}
};
}
showWorkerDescriptor(event, workerFk) {
event.preventDefault();
event.stopImmediatePropagation();
this.$.workerDescriptor.parent = event.target;
this.$.workerDescriptor.workerFk = workerFk;
this.$.workerDescriptor.show();
}
showDeleteConfirm(index) {
this.dmsIndex = index;
this.$.confirm.show();
}
deleteDms(response) {
if (response === 'accept') {
const dmsFk = this.workerDms[this.dmsIndex].dmsFk;
const query = `WorkerDms/${dmsFk}/removeFile`;
this.$http.post(query).then(() => {
this.$.model.remove(this.dmsIndex);
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
});
}
}
}
Controller.$inject = ['$element', '$scope', 'vnToken'];
ngModule.component('vnWorkerDmsIndex', {
template: require('./index.html'),
controller: Controller,
});

View File

@ -0,0 +1,42 @@
import './index';
import crudModel from 'core/mocks/crud-model';
describe('Worker', () => {
describe('Component vnWorkerDmsIndex', () => {
let $componentController;
let $scope;
let $element;
let $httpBackend;
let controller;
beforeEach(ngModule('worker'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
$element = angular.element(`<vn-worker-dms-index></vn-worker-dms-index`);
controller = $componentController('vnWorkerDmsIndex', {$element, $scope});
controller.$.model = crudModel;
}));
describe('deleteDms()', () => {
it('should make an HTTP Post query', () => {
const dmsId = 4;
const dmsIndex = 0;
spyOn(controller.vnApp, 'showSuccess');
spyOn(controller.$.model, 'remove');
controller.workerDms = [{dmsFk: 4}];
controller.dmsIndex = dmsIndex;
$httpBackend.when('POST', `WorkerDms/${dmsId}/removeFile`).respond({});
$httpBackend.expect('POST', `WorkerDms/${dmsId}/removeFile`);
controller.deleteDms('accept');
$httpBackend.flush();
expect(controller.$.model.remove).toHaveBeenCalledWith(dmsIndex);
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
});
});
});
});

View File

@ -0,0 +1,9 @@
Type: Tipo
File management: Gestión documental
File: Fichero
Hard copy: Copia
This file will be deleted: Este fichero va a ser borrado
Are you sure?: Estas seguro?
File deleted: Fichero eliminado
Remove file: Eliminar fichero
Download file: Descargar fichero

View File

@ -0,0 +1,6 @@
vn-client-risk-index {
.totalBox {
display: table;
float: right;
}
}

View File

@ -0,0 +1,2 @@
ClientFileDescription: "{{dmsTypeName}} from client {{clientName}} id {{clientId}}"
ContentTypesInfo: Allowed file types {{allowedContentTypes}}

View File

@ -0,0 +1,20 @@
Reference: Referencia
Description: Descripción
Company: Empresa
Upload file: Subir fichero
Edit file: Editar fichero
Upload: Subir
File: Fichero
WorkerFileDescription: "{{dmsTypeName}} del empleado {{workerName}} id {{workerId}}"
ContentTypesInfo: "Tipos de archivo permitidos: {{allowedContentTypes}}"
Generate identifier for original file: Generar identificador para archivo original
Are you sure you want to continue?: ¿Seguro que quieres continuar?
File management: Gestión documental
Hard copy: Copia
This file will be deleted: Este fichero va a ser borrado
Are you sure?: ¿Seguro?
File deleted: Fichero eliminado
Remove file: Eliminar fichero
Download file: Descargar fichero
Created: Creado
Employee: Empleado

View File

@ -14,3 +14,6 @@ import './calendar';
import './time-control';
import './log';
import './phones';
import './dms/index';
import './dms/create';
import './dms/edit';

View File

@ -13,7 +13,8 @@
{"state": "worker.card.pbx", "icon": "icon-pbx"},
{"state": "worker.card.calendar", "icon": "icon-calendar"},
{"state": "worker.card.timeControl", "icon": "access_time"},
{"state": "worker.card.phones", "icon": "contact_phone"}
{"state": "worker.card.phones", "icon": "contact_phone"},
{"state": "worker.card.dms.index", "icon": "cloud_upload"}
]
},
"routes": [
@ -89,6 +90,36 @@
"params": {
"worker": "$ctrl.worker"
}
},
{
"url": "/dms",
"state": "worker.card.dms",
"abstract": true,
"component": "ui-view"
},
{
"url": "/index",
"state": "worker.card.dms.index",
"component": "vn-worker-dms-index",
"description": "File management"
},
{
"url": "/create",
"state": "worker.card.dms.create",
"component": "vn-worker-dms-create",
"description": "Upload file",
"params": {
"worker": "$ctrl.worker"
}
},
{
"url": "/:dmsId/edit",
"state": "worker.card.dms.edit",
"component": "vn-worker-dms-edit",
"description": "Edit file",
"params": {
"worker": "$ctrl.worker"
}
}
]
}