added ticket.dms #1373
gitea/salix/dev This commit looks good Details

This commit is contained in:
Joan Sanchez 2019-05-01 18:49:39 +02:00
parent 3615fe36a8
commit 2b7287dabb
23 changed files with 328 additions and 30 deletions

View File

@ -0,0 +1,76 @@
const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra');
module.exports = Self => {
Self.remoteMethodCtx('download', {
description: 'Download a document',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'String',
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/download`,
verb: 'GET'
}
});
Self.download = async function(ctx, id) {
const userId = ctx.req.accessToken.userId;
const env = process.env.NODE_ENV;
const document = await Self.findById(id, {
include: {
relation: 'dmsType',
scope: {
fields: ['path', 'readRoleFk'],
include: {
relation: 'readRole'
}
}
}
});
const readRole = document.dmsType().readRole().name;
const hasRequiredRole = await Self.app.models.Account.hasRole(userId, readRole);
if (!hasRequiredRole)
throw new UserError(`You don't have enough privileges`);
if (env && env != 'development') {
const path = `/${document.companyFk}/${document.dmsType().path}/${document.file}`;
file = {
path: `/var/lib/salix/dms/${path}`,
contentType: 'application/octet-stream',
name: document.file
};
} else {
file = {
path: `${process.cwd()}/README.md`,
contentType: 'text/plain',
name: `README.md`
};
}
await fs.access(file.path);
let stream = fs.createReadStream(file.path);
return [stream, file.contentType, `filename="${file.name}"`];
};
};

View File

@ -0,0 +1,30 @@
const app = require('vn-loopback/server/server');
/**
* Pendiente de fixtures dms, dmsType, ticketDms
* CAU: 10728
*/
xdescribe('dms download()', () => {
let dmsId = 1;
it('should return a response for an employee with text content-type', async() => {
let workerFk = 107;
let ctx = {req: {accessToken: {userId: workerFk}}};
const result = await app.models.Dms.download(ctx, dmsId);
expect(result[1]).toEqual('text/plain');
});
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.Dms.download(ctx, dmsId).catch(e => {
error = e;
}).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`);
});
expect(error).toBeDefined();
});
});

3
back/models/dms.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/dms/download')(Self);
};

View File

@ -22,12 +22,22 @@
"required": true
}
},
"acls": [
{
"relations": {
"readRole": {
"type": "belongsTo",
"model": "Role",
"foreignKey": "readRoleFk"
},
"writeRole": {
"type": "belongsTo",
"model": "Role",
"foreignKey": "writeRoleFk"
}
},
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}]
}

View File

@ -1,3 +1,5 @@
INSERT INTO `salix`.`ACL` (`id`, `model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES (162, 'InvoiceOut', 'delete', 'WRITE', 'ALLOW', 'ROLE', 'invoicing');
INSERT INTO `salix`.`ACL` (`id`, `model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES (163, 'InvoiceOut', 'book', 'WRITE', 'ALLOW', 'ROLE', 'invoicing');
INSERT INTO `salix`.`ACL` (`id`, `model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES (164, 'InvoiceOut', 'regenerate', 'WRITE', 'ALLOW', 'ROLE', 'invoicing');
INSERT INTO `salix`.`ACL` (`id`, `model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES (165, 'TicketDms', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (`id`, `model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES (166, 'Dms', 'download', 'READ', 'ALLOW', 'ROLE', 'employee');

View File

@ -6,6 +6,7 @@ CREATE
SQL SECURITY DEFINER
VIEW `vn`.`ticketDms` AS
SELECT
`g`.`Id_Ticket` AS `ticketFk`, `g`.`gestdoc_id` AS `dmsFk`
`g`.`Id_Ticket` AS `ticketFk`,
`g`.`gestdoc_id` AS `dmsFk`
FROM
`vn2008`.`tickets_gestdoc` `g`;

View File

@ -1,3 +1,7 @@
ALTER TABLE `vn2008`.`gesttip`
ADD COLUMN `writeRoleFk` INT(10) UNSIGNED NULL AFTER `path`,
ADD COLUMN `ReadRoleFk` INT(10) UNSIGNED NULL AFTER `writeRoleFk`;
ADD COLUMN `readRoleFk` INT(10) UNSIGNED NULL AFTER `writeRoleFk`,
ADD CONSTRAINT `readRoleFk` FOREIGN KEY (`readRoleFk`) REFERENCES `account`.`role` (`id`),
ADD CONSTRAINT `writeRoleFk` FOREIGN KEY (`writeRoleFk`) REFERENCES `account`.`role` (`id`);
UPDATE `vn2008`.`gesttip` SET `readRoleFk`='1' WHERE `id`='14';

View File

@ -0,0 +1,13 @@
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `vn`.`dmsType` AS
SELECT
`g`.`id` AS `id`,
`g`.`tipo` AS `name`,
`g`.`path` AS `path`,
`g`.`readRoleFk` AS `readRoleFk`,
`g`.`writeRoleFk` AS `writeRoleFk`
FROM
`vn2008`.`gesttip` `g`;

View File

@ -18,4 +18,5 @@ services:
- NODE_ENV
volumes:
- /containers/salix:/etc/salix
- /mnt/storage/pdfs:/var/lib/salix/pdfs
- /mnt/storage/pdfs:/var/lib/salix/pdfs
- /mnt/storage/dms:/var/lib/salix/dms

View File

@ -356,7 +356,7 @@ export default {
expeditionRow: 'vn-ticket-expedition vn-table vn-tbody > vn-tr'
},
ticketPackages: {
packagesButton: 'vn-left-menu a[ui-sref="ticket.card.package.index"]',
packagesButton: 'vn-left-menu a[ui-sref="ticket.card.package"]',
firstPackageAutocomplete: 'vn-autocomplete[label="Package"]',
firstQuantityInput: 'vn-input-number[label="Quantity"] input',
firstRemovePackageButton: 'vn-icon-button[vn-tooltip="Remove package"]',

View File

@ -8,7 +8,7 @@ describe('Ticket Create packages path', () => {
return nightmare
.loginAndModule('employee', 'ticket')
.accessToSearchResult('id:1')
.accessToSection('ticket.card.package.index');
.accessToSection('ticket.card.package');
});
it(`should attempt create a new package but receive an error if package is blank`, async() => {
@ -53,7 +53,7 @@ describe('Ticket Create packages path', () => {
it(`should confirm the first select is the expected one`, async() => {
const result = await nightmare
.reloadSection('ticket.card.package.index')
.reloadSection('ticket.card.package')
.waitForTextInInput(`${selectors.ticketPackages.firstPackageAutocomplete} input`, 'Container medical box 1m')
.waitToGetProperty(`${selectors.ticketPackages.firstPackageAutocomplete} input`, 'value');

View File

@ -14,10 +14,9 @@
<vn-vertical>
<vn-card pad-large>
<vn-horizontal>
<vn-one></vn-one>
<vn-one>
</vn-one>
<vn-one>
<vn-autocomplete vn-one
<vn-autocomplete
vn-id="company"
field="$ctrl.companyFk"
on-change="$ctrl.setOrder(value)"
@ -89,7 +88,7 @@
href="api/InvoiceOuts/{{::balance.id}}/download?access_token={{::$ctrl.accessToken}}">
<vn-icon-button
icon="cloud_download"
title="Download PDF">
title="{{'Download PDF' | translate}}">
</vn-icon-button>
</a>
</vn-td>

View File

@ -87,10 +87,6 @@ class Controller {
this.$.balanceCreateDialog.show();
}
onDownload() {
alert('Not implemented yet');
}
showWorkerDescriptor(event, workerFk) {
if (event.defaultPrevented) return;

View File

@ -1,6 +1,10 @@
vn-client-risk-index {
@import "./variables";
vn-client-balance-index {
.totalBox {
display: table;
float: right;
border: $border-thin-light;
text-align: left;
float: right
}
}

View File

@ -0,0 +1,51 @@
<vn-crud-model
vn-id="model"
url="/ticket/api/TicketDms"
link="{ticketFk: $ctrl.$stateParams.id}"
filter="::$ctrl.filter"
limit="20"
data="$ctrl.ticketDms">
</vn-crud-model>
<vn-vertical>
<vn-card pad-large>
<vn-vertical>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="dmsFk" default-order="DESC" number>Id</vn-th>
<vn-th>Type</vn-th>
<vn-th>Employee</vn-th>
<vn-th>Created</vn-th>
<vn-th></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="document in $ctrl.ticketDms">
<vn-td number>{{::document.dmsFk}}</vn-td>
<vn-td>{{::document.dms.dmsType.name}}</vn-td>
<vn-td>
<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 | dateTime:'dd/MM/yyyy HH:mm'}}</vn-td>
<vn-td center>
<a
target="_blank"
href="api/dms/{{::document.dmsFk}}/download?access_token={{::$ctrl.accessToken}}">
<vn-icon-button
icon="cloud_download"
title="{{'Download PDF' | translate}}">
</vn-icon-button>
</a>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-vertical>
<vn-pagination model="model"></vn-pagination>
</vn-card>
</vn-vertical>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>

View File

@ -0,0 +1,51 @@
import ngModule from '../module';
import './style.scss';
class Controller {
constructor($stateParams, $scope, vnToken) {
this.$stateParams = $stateParams;
this.$ = $scope;
this.accessToken = vnToken.token;
this.filter = {
include: {
relation: 'dms',
scope: {
fields: ['dmsTypeFk', 'workerFk', '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();
}
}
Controller.$inject = ['$stateParams', '$scope', 'vnToken'];
ngModule.component('vnTicketDms', {
template: require('./index.html'),
controller: Controller,
});

View File

@ -0,0 +1,44 @@
import './index';
describe('Client', () => {
describe('Component vnClientBalanceIndex', () => {
let $componentController;
let $scope;
let $httpBackend;
let $httpParamSerializer;
let controller;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
$scope = $rootScope.$new();
controller = $componentController('vnClientBalanceIndex', {$scope});
}));
describe('balances() setter', () => {
it('should calculate the balance for each line from the oldest date to the newest', () => {
controller.getCurrentBalance = jasmine.createSpy(controller, 'getCurrentBalance').and.returnValue(1000);
let balances = [
{credit: -100, debit: 0},
{credit: 0, debit: 300},
{credit: 100, debit: 0},
{credit: 0, debit: -300}
];
const params = {filter: controller.filter};
let serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `/client/api/ClientRisks?${serializedParams}`).respond(balances);
$httpBackend.expect('GET', `/client/api/ClientRisks?${serializedParams}`);
controller.balances = balances;
$httpBackend.flush();
expect(controller.balances[0].balance).toEqual(1000);
expect(controller.balances[1].balance).toEqual(900);
expect(controller.balances[2].balance).toEqual(600);
expect(controller.balances[3].balance).toEqual(700);
});
});
});
});

View File

@ -0,0 +1,2 @@
Type: Tipo
File management: Gestión documental

View File

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

View File

@ -31,3 +31,5 @@ import './request/index';
import './request/create';
import './log';
import './weekly';
import './dms';

View File

@ -26,7 +26,7 @@ class Controller {
Controller.$inject = ['$scope', '$stateParams'];
ngModule.component('vnTicketPackageIndex', {
ngModule.component('vnTicketPackage', {
template: require('./index.html'),
controller: Controller
});

View File

@ -11,14 +11,15 @@
{"state": "ticket.card.volume", "icon": "icon-volume"},
{"state": "ticket.card.expedition", "icon": "icon-package"},
{"state": "ticket.card.service", "icon": "icon-services"},
{"state": "ticket.card.package.index", "icon": "icon-bucket"},
{"state": "ticket.card.package", "icon": "icon-bucket"},
{"state": "ticket.card.tracking.index", "icon": "remove_red_eye"},
{"state": "ticket.card.saleChecked", "icon": "assignment"},
{"state": "ticket.card.components", "icon": "icon-components"},
{"state": "ticket.card.saleTracking", "icon": "assignment"},
{"state": "ticket.card.picture", "icon": "image"},
{"state": "ticket.card.log", "icon": "history"},
{"state": "ticket.card.request.index", "icon": "icon-100"}
{"state": "ticket.card.request.index", "icon": "icon-100"},
{"state": "ticket.card.dms", "icon": "cloud_download"}
],
"keybindings": [
{"key": "t", "state": "ticket.index"}
@ -114,13 +115,8 @@
}
}, {
"url" : "/package",
"abstract": true,
"state": "ticket.card.package",
"component": "ui-view"
}, {
"url" : "/index",
"state": "ticket.card.package.index",
"component": "vn-ticket-package-index",
"component": "vn-ticket-package",
"description": "Packages",
"params": {
"ticket": "$ctrl.ticket"
@ -216,6 +212,12 @@
"state": "ticket.create",
"component": "vn-ticket-create",
"description": "New ticket"
},
{
"url": "/dms",
"state": "ticket.card.dms",
"component": "vn-ticket-dms",
"description": "File management"
}
]
}

View File

@ -31,3 +31,4 @@ Reserved: Reservado
SMSAvailability: >-
Verdnatura le comunica: Pedido {{ticketFk}} día {{created | date: "dd/MM/yyyy"}}.
{{notAvailables}} no disponible/s. Disculpe las molestias.
Continue anyway?: ¿Continuar de todas formas?