mirror of
https://github.com/sismics/docs.git
synced 2024-11-22 14:07:55 +01:00
#159: display and validate route steps
This commit is contained in:
parent
8a854bb37d
commit
5b8cd18128
@ -1,6 +1,10 @@
|
|||||||
package com.sismics.docs.core.dao.jpa.dto;
|
package com.sismics.docs.core.dao.jpa.dto;
|
||||||
|
|
||||||
import com.sismics.docs.core.constant.RouteStepType;
|
import com.sismics.docs.core.constant.RouteStepType;
|
||||||
|
import com.sismics.util.JsonUtil;
|
||||||
|
|
||||||
|
import javax.json.Json;
|
||||||
|
import javax.json.JsonObjectBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route step DTO.
|
* Route step DTO.
|
||||||
@ -147,4 +151,23 @@ public class RouteStepDto {
|
|||||||
this.validatorUserName = validatorUserName;
|
this.validatorUserName = validatorUserName;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform in JSON.
|
||||||
|
*
|
||||||
|
* @return JSON object builder
|
||||||
|
*/
|
||||||
|
public JsonObjectBuilder toJson() {
|
||||||
|
return Json.createObjectBuilder()
|
||||||
|
.add("name", getName())
|
||||||
|
.add("type", getType().name())
|
||||||
|
.add("comment", JsonUtil.nullable(getComment()))
|
||||||
|
.add("end_date", JsonUtil.nullable(getEndDateTimestamp()))
|
||||||
|
.add("validator_username", JsonUtil.nullable(getValidatorUserName()))
|
||||||
|
.add("target", Json.createObjectBuilder()
|
||||||
|
.add("id", getTargetId())
|
||||||
|
.add("name", JsonUtil.nullable(getTargetName()))
|
||||||
|
.add("type", getTargetType()))
|
||||||
|
.add("transition", JsonUtil.nullable(getTransition()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package com.sismics.rest.util;
|
package com.sismics.util;
|
||||||
|
|
||||||
import javax.json.Json;
|
import javax.json.Json;
|
||||||
import javax.json.JsonValue;
|
import javax.json.JsonValue;
|
@ -4,6 +4,7 @@ import com.sismics.docs.core.constant.AclType;
|
|||||||
import com.sismics.docs.core.constant.PermType;
|
import com.sismics.docs.core.constant.PermType;
|
||||||
import com.sismics.docs.core.dao.jpa.AclDao;
|
import com.sismics.docs.core.dao.jpa.AclDao;
|
||||||
import com.sismics.docs.core.dao.jpa.dto.AclDto;
|
import com.sismics.docs.core.dao.jpa.dto.AclDto;
|
||||||
|
import com.sismics.util.JsonUtil;
|
||||||
|
|
||||||
import javax.json.Json;
|
import javax.json.Json;
|
||||||
import javax.json.JsonArrayBuilder;
|
import javax.json.JsonArrayBuilder;
|
||||||
|
@ -10,7 +10,7 @@ import com.sismics.docs.core.util.jpa.PaginatedList;
|
|||||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||||
import com.sismics.rest.exception.ForbiddenClientException;
|
import com.sismics.rest.exception.ForbiddenClientException;
|
||||||
import com.sismics.rest.util.JsonUtil;
|
import com.sismics.util.JsonUtil;
|
||||||
|
|
||||||
import javax.json.Json;
|
import javax.json.Json;
|
||||||
import javax.json.JsonArrayBuilder;
|
import javax.json.JsonArrayBuilder;
|
||||||
|
@ -25,8 +25,8 @@ import com.sismics.rest.exception.ClientException;
|
|||||||
import com.sismics.rest.exception.ForbiddenClientException;
|
import com.sismics.rest.exception.ForbiddenClientException;
|
||||||
import com.sismics.rest.exception.ServerException;
|
import com.sismics.rest.exception.ServerException;
|
||||||
import com.sismics.rest.util.AclUtil;
|
import com.sismics.rest.util.AclUtil;
|
||||||
import com.sismics.rest.util.JsonUtil;
|
|
||||||
import com.sismics.rest.util.ValidationUtil;
|
import com.sismics.rest.util.ValidationUtil;
|
||||||
|
import com.sismics.util.JsonUtil;
|
||||||
import com.sismics.util.context.ThreadLocalContext;
|
import com.sismics.util.context.ThreadLocalContext;
|
||||||
import com.sismics.util.mime.MimeType;
|
import com.sismics.util.mime.MimeType;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
@ -216,10 +216,9 @@ public class DocumentResource extends BaseResource {
|
|||||||
// Add current route step
|
// Add current route step
|
||||||
RouteStepDto routeStepDto = new RouteStepDao().getCurrentStep(documentId);
|
RouteStepDto routeStepDto = new RouteStepDao().getCurrentStep(documentId);
|
||||||
if (routeStepDto != null && !principal.isAnonymous()) {
|
if (routeStepDto != null && !principal.isAnonymous()) {
|
||||||
document.add("route_step", Json.createObjectBuilder()
|
JsonObjectBuilder step = routeStepDto.toJson();
|
||||||
.add("name", routeStepDto.getName())
|
step.add("transitionable", getTargetIdList(null).contains(routeStepDto.getTargetId()));
|
||||||
.add("type", routeStepDto.getType().name())
|
document.add("route_step", step);
|
||||||
.add("transitionable", getTargetIdList(null).contains(routeStepDto.getTargetId())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.ok().entity(document.build()).build();
|
return Response.ok().entity(document.build()).build();
|
||||||
|
@ -22,9 +22,9 @@ import com.sismics.docs.core.util.PdfUtil;
|
|||||||
import com.sismics.rest.exception.ClientException;
|
import com.sismics.rest.exception.ClientException;
|
||||||
import com.sismics.rest.exception.ForbiddenClientException;
|
import com.sismics.rest.exception.ForbiddenClientException;
|
||||||
import com.sismics.rest.exception.ServerException;
|
import com.sismics.rest.exception.ServerException;
|
||||||
import com.sismics.rest.util.JsonUtil;
|
|
||||||
import com.sismics.rest.util.ValidationUtil;
|
import com.sismics.rest.util.ValidationUtil;
|
||||||
import com.sismics.util.HttpUtil;
|
import com.sismics.util.HttpUtil;
|
||||||
|
import com.sismics.util.JsonUtil;
|
||||||
import com.sismics.util.context.ThreadLocalContext;
|
import com.sismics.util.context.ThreadLocalContext;
|
||||||
import com.sismics.util.mime.MimeType;
|
import com.sismics.util.mime.MimeType;
|
||||||
import com.sismics.util.mime.MimeTypeUtil;
|
import com.sismics.util.mime.MimeTypeUtil;
|
||||||
|
@ -14,8 +14,8 @@ import com.sismics.docs.core.util.jpa.SortCriteria;
|
|||||||
import com.sismics.docs.rest.constant.BaseFunction;
|
import com.sismics.docs.rest.constant.BaseFunction;
|
||||||
import com.sismics.rest.exception.ClientException;
|
import com.sismics.rest.exception.ClientException;
|
||||||
import com.sismics.rest.exception.ForbiddenClientException;
|
import com.sismics.rest.exception.ForbiddenClientException;
|
||||||
import com.sismics.rest.util.JsonUtil;
|
|
||||||
import com.sismics.rest.util.ValidationUtil;
|
import com.sismics.rest.util.ValidationUtil;
|
||||||
|
import com.sismics.util.JsonUtil;
|
||||||
|
|
||||||
import javax.json.Json;
|
import javax.json.Json;
|
||||||
import javax.json.JsonArrayBuilder;
|
import javax.json.JsonArrayBuilder;
|
||||||
|
@ -18,7 +18,6 @@ import com.sismics.docs.core.util.SecurityUtil;
|
|||||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||||
import com.sismics.rest.exception.ClientException;
|
import com.sismics.rest.exception.ClientException;
|
||||||
import com.sismics.rest.exception.ForbiddenClientException;
|
import com.sismics.rest.exception.ForbiddenClientException;
|
||||||
import com.sismics.rest.util.JsonUtil;
|
|
||||||
import com.sismics.rest.util.ValidationUtil;
|
import com.sismics.rest.util.ValidationUtil;
|
||||||
|
|
||||||
import javax.json.*;
|
import javax.json.*;
|
||||||
@ -44,6 +43,7 @@ public class RouteResource extends BaseResource {
|
|||||||
* @apiParam {String} documentId Document ID
|
* @apiParam {String} documentId Document ID
|
||||||
* @apiSuccess {String} status Status OK
|
* @apiSuccess {String} status Status OK
|
||||||
* @apiError (client) InvalidRouteModel Invalid route model
|
* @apiError (client) InvalidRouteModel Invalid route model
|
||||||
|
* @apiError (client) RunningRoute A running route already exists on this document
|
||||||
* @apiError (client) ForbiddenError Access denied
|
* @apiError (client) ForbiddenError Access denied
|
||||||
* @apiError (client) NotFound Route model or document not found
|
* @apiError (client) NotFound Route model or document not found
|
||||||
* @apiPermission user
|
* @apiPermission user
|
||||||
@ -72,6 +72,12 @@ public class RouteResource extends BaseResource {
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid creating 2 running routes on the same document
|
||||||
|
RouteStepDao routeStepDao = new RouteStepDao();
|
||||||
|
if (routeStepDao.getCurrentStep(documentId) != null) {
|
||||||
|
throw new ClientException("RunningRoute", "A running route already exists on this document");
|
||||||
|
}
|
||||||
|
|
||||||
// Create the route
|
// Create the route
|
||||||
Route route = new Route()
|
Route route = new Route()
|
||||||
.setDocumentId(documentId)
|
.setDocumentId(documentId)
|
||||||
@ -80,7 +86,6 @@ public class RouteResource extends BaseResource {
|
|||||||
routeDao.create(route, principal.getId());
|
routeDao.create(route, principal.getId());
|
||||||
|
|
||||||
// Create the steps
|
// Create the steps
|
||||||
RouteStepDao routeStepDao = new RouteStepDao();
|
|
||||||
try (JsonReader reader = Json.createReader(new StringReader(routeModel.getSteps()))) {
|
try (JsonReader reader = Json.createReader(new StringReader(routeModel.getSteps()))) {
|
||||||
JsonArray stepsJson = reader.readArray();
|
JsonArray stepsJson = reader.readArray();
|
||||||
for (int order = 0; order < stepsJson.size(); order++) {
|
for (int order = 0; order < stepsJson.size(); order++) {
|
||||||
@ -108,9 +113,8 @@ public class RouteResource extends BaseResource {
|
|||||||
RouteStepDto routeStep = routeStepDao.getCurrentStep(documentId);
|
RouteStepDto routeStep = routeStepDao.getCurrentStep(documentId);
|
||||||
RoutingUtil.updateAcl(documentId, routeStep, null, principal.getId());
|
RoutingUtil.updateAcl(documentId, routeStep, null, principal.getId());
|
||||||
|
|
||||||
// Always return OK
|
|
||||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||||
.add("status", "ok");
|
.add("route_step", routeStep.toJson());
|
||||||
return Response.ok().entity(response.build()).build();
|
return Response.ok().entity(response.build()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,10 +177,13 @@ public class RouteResource extends BaseResource {
|
|||||||
RoutingUtil.updateAcl(documentId, newRouteStep, routeStep, principal.getId());
|
RoutingUtil.updateAcl(documentId, newRouteStep, routeStep, principal.getId());
|
||||||
// TODO Send an email to the new route step
|
// TODO Send an email to the new route step
|
||||||
|
|
||||||
// Always return OK
|
|
||||||
// TODO Return if the document is still readable and return the new current step if any
|
|
||||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||||
.add("status", "ok");
|
.add("readable", aclDao.checkPermission(documentId, PermType.READ, getTargetIdList(null)));
|
||||||
|
if (newRouteStep != null) {
|
||||||
|
JsonObjectBuilder step = newRouteStep.toJson();
|
||||||
|
step.add("transitionable", getTargetIdList(null).contains(newRouteStep.getTargetId()));
|
||||||
|
response.add("route_step", step);
|
||||||
|
}
|
||||||
return Response.ok().entity(response.build()).build();
|
return Response.ok().entity(response.build()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,17 +239,7 @@ public class RouteResource extends BaseResource {
|
|||||||
JsonArrayBuilder steps = Json.createArrayBuilder();
|
JsonArrayBuilder steps = Json.createArrayBuilder();
|
||||||
|
|
||||||
for (RouteStepDto routeStepDto : routeStepDtoList) {
|
for (RouteStepDto routeStepDto : routeStepDtoList) {
|
||||||
steps.add(Json.createObjectBuilder()
|
steps.add(routeStepDto.toJson());
|
||||||
.add("name", routeStepDto.getName())
|
|
||||||
.add("type", routeStepDto.getType().name())
|
|
||||||
.add("comment", JsonUtil.nullable(routeStepDto.getComment()))
|
|
||||||
.add("end_date", JsonUtil.nullable(routeStepDto.getEndDateTimestamp()))
|
|
||||||
.add("validator_username", JsonUtil.nullable(routeStepDto.getValidatorUserName()))
|
|
||||||
.add("target", Json.createObjectBuilder()
|
|
||||||
.add("id", routeStepDto.getTargetId())
|
|
||||||
.add("name", JsonUtil.nullable(routeStepDto.getTargetName()))
|
|
||||||
.add("type", routeStepDto.getTargetType()))
|
|
||||||
.add("transition", JsonUtil.nullable(routeStepDto.getTransition())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
routes.add(Json.createObjectBuilder()
|
routes.add(Json.createObjectBuilder()
|
||||||
@ -253,7 +250,8 @@ public class RouteResource extends BaseResource {
|
|||||||
|
|
||||||
JsonObjectBuilder json = Json.createObjectBuilder()
|
JsonObjectBuilder json = Json.createObjectBuilder()
|
||||||
.add("routes", routes);
|
.add("routes", routes);
|
||||||
|
|
||||||
return Response.ok().entity(json.build()).build();
|
return Response.ok().entity(json.build()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Workflow cancellation
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ import com.sismics.docs.core.model.jpa.Acl;
|
|||||||
import com.sismics.docs.core.model.jpa.Share;
|
import com.sismics.docs.core.model.jpa.Share;
|
||||||
import com.sismics.rest.exception.ClientException;
|
import com.sismics.rest.exception.ClientException;
|
||||||
import com.sismics.rest.exception.ForbiddenClientException;
|
import com.sismics.rest.exception.ForbiddenClientException;
|
||||||
import com.sismics.rest.util.JsonUtil;
|
|
||||||
import com.sismics.rest.util.ValidationUtil;
|
import com.sismics.rest.util.ValidationUtil;
|
||||||
|
import com.sismics.util.JsonUtil;
|
||||||
|
|
||||||
import javax.json.Json;
|
import javax.json.Json;
|
||||||
import javax.json.JsonObjectBuilder;
|
import javax.json.JsonObjectBuilder;
|
||||||
|
@ -10,9 +10,9 @@ import com.sismics.docs.rest.constant.BaseFunction;
|
|||||||
import com.sismics.rest.exception.ClientException;
|
import com.sismics.rest.exception.ClientException;
|
||||||
import com.sismics.rest.exception.ForbiddenClientException;
|
import com.sismics.rest.exception.ForbiddenClientException;
|
||||||
import com.sismics.rest.exception.ServerException;
|
import com.sismics.rest.exception.ServerException;
|
||||||
import com.sismics.rest.util.JsonUtil;
|
|
||||||
import com.sismics.rest.util.ValidationUtil;
|
import com.sismics.rest.util.ValidationUtil;
|
||||||
import com.sismics.util.HttpUtil;
|
import com.sismics.util.HttpUtil;
|
||||||
|
import com.sismics.util.JsonUtil;
|
||||||
import com.sismics.util.css.Selector;
|
import com.sismics.util.css.Selector;
|
||||||
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
|
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
|
||||||
import org.glassfish.jersey.media.multipart.FormDataParam;
|
import org.glassfish.jersey.media.multipart.FormDataParam;
|
||||||
|
@ -21,9 +21,9 @@ import com.sismics.docs.rest.constant.BaseFunction;
|
|||||||
import com.sismics.rest.exception.ClientException;
|
import com.sismics.rest.exception.ClientException;
|
||||||
import com.sismics.rest.exception.ForbiddenClientException;
|
import com.sismics.rest.exception.ForbiddenClientException;
|
||||||
import com.sismics.rest.exception.ServerException;
|
import com.sismics.rest.exception.ServerException;
|
||||||
import com.sismics.rest.util.JsonUtil;
|
|
||||||
import com.sismics.rest.util.ValidationUtil;
|
import com.sismics.rest.util.ValidationUtil;
|
||||||
import com.sismics.security.UserPrincipal;
|
import com.sismics.security.UserPrincipal;
|
||||||
|
import com.sismics.util.JsonUtil;
|
||||||
import com.sismics.util.context.ThreadLocalContext;
|
import com.sismics.util.context.ThreadLocalContext;
|
||||||
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
||||||
import com.sismics.util.totp.GoogleAuthenticator;
|
import com.sismics.util.totp.GoogleAuthenticator;
|
||||||
|
@ -285,6 +285,15 @@ angular.module('docs',
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.state('document.view.workflow', {
|
||||||
|
url: '/workflow',
|
||||||
|
views: {
|
||||||
|
'tab': {
|
||||||
|
templateUrl: 'partial/docs/document.view.workflow.html',
|
||||||
|
controller: 'DocumentViewWorkflow'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
.state('document.view.content.file', {
|
.state('document.view.content.file', {
|
||||||
url: '/file/:fileId',
|
url: '/file/:fileId',
|
||||||
views: {
|
views: {
|
||||||
|
@ -5,16 +5,16 @@
|
|||||||
*/
|
*/
|
||||||
angular.module('docs').controller('DocumentView', function ($scope, $state, $stateParams, $location, $dialog, $uibModal, Restangular, $translate) {
|
angular.module('docs').controller('DocumentView', function ($scope, $state, $stateParams, $location, $dialog, $uibModal, Restangular, $translate) {
|
||||||
// Load document data from server
|
// Load document data from server
|
||||||
Restangular.one('document', $stateParams.id).get().then(function(data) {
|
Restangular.one('document', $stateParams.id).get().then(function (data) {
|
||||||
$scope.document = data;
|
$scope.document = data;
|
||||||
}, function(response) {
|
}, function (response) {
|
||||||
$scope.error = response;
|
$scope.error = response;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load comments from server
|
// Load comments from server
|
||||||
Restangular.one('comment', $stateParams.id).get().then(function(data) {
|
Restangular.one('comment', $stateParams.id).get().then(function (data) {
|
||||||
$scope.comments = data.comments;
|
$scope.comments = data.comments;
|
||||||
}, function(response) {
|
}, function (response) {
|
||||||
$scope.commentsError = response;
|
$scope.commentsError = response;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
|||||||
* Add a comment.
|
* Add a comment.
|
||||||
*/
|
*/
|
||||||
$scope.comment = '';
|
$scope.comment = '';
|
||||||
$scope.addComment = function() {
|
$scope.addComment = function () {
|
||||||
if ($scope.comment.length === 0) {
|
if ($scope.comment.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
|||||||
Restangular.one('comment').put({
|
Restangular.one('comment').put({
|
||||||
id: $stateParams.id,
|
id: $stateParams.id,
|
||||||
content: $scope.comment
|
content: $scope.comment
|
||||||
}).then(function(data) {
|
}).then(function (data) {
|
||||||
$scope.comment = '';
|
$scope.comment = '';
|
||||||
$scope.comments.push(data);
|
$scope.comments.push(data);
|
||||||
});
|
});
|
||||||
@ -39,7 +39,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
|||||||
/**
|
/**
|
||||||
* Delete a comment.
|
* Delete a comment.
|
||||||
*/
|
*/
|
||||||
$scope.deleteComment = function(comment) {
|
$scope.deleteComment = function (comment) {
|
||||||
var title = $translate.instant('document.view.delete_comment_title');
|
var title = $translate.instant('document.view.delete_comment_title');
|
||||||
var msg = $translate.instant('document.view.delete_comment_message');
|
var msg = $translate.instant('document.view.delete_comment_message');
|
||||||
var btns = [
|
var btns = [
|
||||||
@ -49,7 +49,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
|||||||
|
|
||||||
$dialog.messageBox(title, msg, btns, function (result) {
|
$dialog.messageBox(title, msg, btns, function (result) {
|
||||||
if (result === 'ok') {
|
if (result === 'ok') {
|
||||||
Restangular.one('comment', comment.id).remove().then(function() {
|
Restangular.one('comment', comment.id).remove().then(function () {
|
||||||
$scope.comments.splice($scope.comments.indexOf(comment), 1);
|
$scope.comments.splice($scope.comments.indexOf(comment), 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
|||||||
|
|
||||||
$dialog.messageBox(title, msg, btns, function (result) {
|
$dialog.messageBox(title, msg, btns, function (result) {
|
||||||
if (result === 'ok') {
|
if (result === 'ok') {
|
||||||
Restangular.one('document', document.id).remove().then(function() {
|
Restangular.one('document', document.id).remove().then(function () {
|
||||||
$scope.loadDocuments();
|
$scope.loadDocuments();
|
||||||
$state.go('document.default');
|
$state.go('document.default');
|
||||||
});
|
});
|
||||||
@ -105,7 +105,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
|||||||
/**
|
/**
|
||||||
* Display a share.
|
* Display a share.
|
||||||
*/
|
*/
|
||||||
$scope.showShare = function(share) {
|
$scope.showShare = function (share) {
|
||||||
// Show the link
|
// Show the link
|
||||||
var link = $location.absUrl().replace($location.path(), '').replace('#', '') + 'share.html#/share/' + $stateParams.id + '/' + share.id;
|
var link = $location.absUrl().replace($location.path(), '').replace('#', '') + 'share.html#/share/' + $stateParams.id + '/' + share.id;
|
||||||
var title = $translate.instant('document.view.shared_document_title');
|
var title = $translate.instant('document.view.shared_document_title');
|
||||||
@ -119,7 +119,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
|||||||
if (result === 'unshare') {
|
if (result === 'unshare') {
|
||||||
// Unshare this document and update the local shares
|
// Unshare this document and update the local shares
|
||||||
Restangular.one('share', share.id).remove().then(function () {
|
Restangular.one('share', share.id).remove().then(function () {
|
||||||
$scope.document.acls = _.reject($scope.document.acls, function(s) {
|
$scope.document.acls = _.reject($scope.document.acls, function (s) {
|
||||||
return share.id === s.id;
|
return share.id === s.id;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -130,7 +130,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
|||||||
/**
|
/**
|
||||||
* Export the current document to PDF.
|
* Export the current document to PDF.
|
||||||
*/
|
*/
|
||||||
$scope.exportPdf = function() {
|
$scope.exportPdf = function () {
|
||||||
$uibModal.open({
|
$uibModal.open({
|
||||||
templateUrl: 'partial/docs/document.pdf.html',
|
templateUrl: 'partial/docs/document.pdf.html',
|
||||||
controller: 'DocumentModalPdf'
|
controller: 'DocumentModalPdf'
|
||||||
@ -138,4 +138,22 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the workflow.
|
||||||
|
*/
|
||||||
|
$scope.validateWorkflow = function (transition) {
|
||||||
|
Restangular.one('route').post('validate', {
|
||||||
|
documentId: $stateParams.id,
|
||||||
|
transition: transition,
|
||||||
|
comment: $scope.workflowComment
|
||||||
|
}).then(function (data) {
|
||||||
|
$scope.workflowComment = '';
|
||||||
|
if (data.readable) {
|
||||||
|
$scope.document.route_step = data.route_step;
|
||||||
|
} else {
|
||||||
|
$state.go('document.default');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
});
|
});
|
@ -0,0 +1,33 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document view workflow controller.
|
||||||
|
*/
|
||||||
|
angular.module('docs').controller('DocumentViewWorkflow', function ($scope, $stateParams, Restangular) {
|
||||||
|
$scope.loadRoutes = function () {
|
||||||
|
Restangular.one('route').get({
|
||||||
|
documentId: $stateParams.id
|
||||||
|
}).then(function(data) {
|
||||||
|
$scope.routes = data.routes;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load route models
|
||||||
|
Restangular.one('routemodel').get().then(function(data) {
|
||||||
|
$scope.routemodels = data.routemodels;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start the selected workflow
|
||||||
|
$scope.startWorkflow = function () {
|
||||||
|
Restangular.one('route').post('start', {
|
||||||
|
routeModelId: $scope.routemodel,
|
||||||
|
documentId: $stateParams.id
|
||||||
|
}).then(function (data) {
|
||||||
|
$scope.document.route_step = data.route_step;
|
||||||
|
$scope.loadRoutes();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load routes
|
||||||
|
$scope.loadRoutes();
|
||||||
|
});
|
@ -58,6 +58,7 @@
|
|||||||
<script src="app/docs/controller/document/DocumentEdit.js" type="text/javascript"></script>
|
<script src="app/docs/controller/document/DocumentEdit.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/document/DocumentView.js" type="text/javascript"></script>
|
<script src="app/docs/controller/document/DocumentView.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/document/DocumentViewContent.js" type="text/javascript"></script>
|
<script src="app/docs/controller/document/DocumentViewContent.js" type="text/javascript"></script>
|
||||||
|
<script src="app/docs/controller/document/DocumentViewWorkflow.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/document/DocumentViewPermissions.js" type="text/javascript"></script>
|
<script src="app/docs/controller/document/DocumentViewPermissions.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/document/DocumentViewActivity.js" type="text/javascript"></script>
|
<script src="app/docs/controller/document/DocumentViewActivity.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/document/DocumentModalShare.js" type="text/javascript"></script>
|
<script src="app/docs/controller/document/DocumentModalShare.js" type="text/javascript"></script>
|
||||||
|
@ -92,6 +92,8 @@
|
|||||||
"no_comments": "No comments on this document yet",
|
"no_comments": "No comments on this document yet",
|
||||||
"add_comment": "Add a comment",
|
"add_comment": "Add a comment",
|
||||||
"error_loading_comments": "Error loading comments",
|
"error_loading_comments": "Error loading comments",
|
||||||
|
"workflow_current": "Current workflow step",
|
||||||
|
"workflow_comment": "Add a workflow comment",
|
||||||
"content": {
|
"content": {
|
||||||
"content": "Content",
|
"content": "Content",
|
||||||
"delete_file_title": "Delete file",
|
"delete_file_title": "Delete file",
|
||||||
@ -103,6 +105,14 @@
|
|||||||
"drop_zone": "Drag & drop files here to upload",
|
"drop_zone": "Drag & drop files here to upload",
|
||||||
"add_files": "Add files"
|
"add_files": "Add files"
|
||||||
},
|
},
|
||||||
|
"workflow": {
|
||||||
|
"workflow": "Workflow",
|
||||||
|
"message": "Verify or validate your documents with people of your organization using workflows.",
|
||||||
|
"workflow_start_label": "Which workflow to start?",
|
||||||
|
"add_more_workflow": "Add more workflows",
|
||||||
|
"start_workflow_submit": "Start workflow",
|
||||||
|
"full_name": "<strong>{{ name }}</strong> started on {{ create_date | date }}"
|
||||||
|
},
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"permissions": "Permissions",
|
"permissions": "Permissions",
|
||||||
"message": "Permissions can be applied directly to this document, or can come from <a href=\"#/tag\">tags</a>.",
|
"message": "Permissions can be applied directly to this document, or can come from <a href=\"#/tag\">tags</a>.",
|
||||||
@ -428,6 +438,15 @@
|
|||||||
"GROUP": "Group",
|
"GROUP": "Group",
|
||||||
"SHARE": "Shared"
|
"SHARE": "Shared"
|
||||||
},
|
},
|
||||||
|
"workflow_type": {
|
||||||
|
"VALIDATE": "Validation",
|
||||||
|
"APPROVE": "Approbation"
|
||||||
|
},
|
||||||
|
"workflow_transition": {
|
||||||
|
"APPROVED": "Approved",
|
||||||
|
"REJECTED": "Rejected",
|
||||||
|
"VALIDATED": "Validated"
|
||||||
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"required": "Required",
|
"required": "Required",
|
||||||
"too_short": "Too short",
|
"too_short": "Too short",
|
||||||
|
@ -72,6 +72,11 @@
|
|||||||
<span class="glyphicon glyphicon-file"></span> {{ 'document.view.content' | translate }}
|
<span class="glyphicon glyphicon-file"></span> {{ 'document.view.content' | translate }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li ng-class="{ active: $state.current.name == 'document.view.workflow' }">
|
||||||
|
<a href="#/document/view/{{ document.id }}/workflow">
|
||||||
|
<span class="glyphicon glyphicon-random"></span> {{ 'document.view.workflow' | translate }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li ng-class="{ active: $state.current.name == 'document.view.permissions' }">
|
<li ng-class="{ active: $state.current.name == 'document.view.permissions' }">
|
||||||
<a href="#/document/view/{{ document.id }}/permissions">
|
<a href="#/document/view/{{ document.id }}/permissions">
|
||||||
<span class="glyphicon glyphicon-user"></span> {{ 'document.view.permissions' | translate }}
|
<span class="glyphicon glyphicon-user"></span> {{ 'document.view.permissions' | translate }}
|
||||||
@ -86,7 +91,43 @@
|
|||||||
<div ui-view="tab"></div>
|
<div ui-view="tab"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<p class="page-header page-header-comments">
|
<div ng-show="document.route_step">
|
||||||
|
<p class="page-header page-header-side">
|
||||||
|
<span class="glyphicon glyphicon-random"></span>
|
||||||
|
{{ 'document.view.workflow_current' | translate }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="text-center card">
|
||||||
|
<p>{{ document.route_step.name }}</p>
|
||||||
|
<p>
|
||||||
|
<span class="glyphicon glyphicon-transfer" ng-if="document.route_step.type == 'APPROVE'"></span>
|
||||||
|
<span class="glyphicon glyphicon-ok-circle" ng-if="document.route_step.type == 'VALIDATE'"></span>
|
||||||
|
{{ 'workflow_type.' + document.route_step.type | translate }}
|
||||||
|
<span class="label label-default"><acl data="document.route_step.target"></acl></span>
|
||||||
|
</p>
|
||||||
|
<p ng-show="document.route_step.transitionable">
|
||||||
|
<textarea ng-model="workflowComment" maxlength="500" class="form-control mb-10"
|
||||||
|
ng-attr-placeholder="{{ 'document.view.workflow_comment' | translate }}"></textarea>
|
||||||
|
<span class="btn btn-primary"
|
||||||
|
ng-show="document.route_step.type == 'VALIDATE'"
|
||||||
|
ng-click="validateWorkflow('VALIDATED')">
|
||||||
|
{{ 'workflow_transition.VALIDATED' | translate }}
|
||||||
|
</span>
|
||||||
|
<span class="btn btn-success"
|
||||||
|
ng-show="document.route_step.type == 'APPROVE'"
|
||||||
|
ng-click="validateWorkflow('APPROVED')">
|
||||||
|
{{ 'workflow_transition.APPROVED' | translate }}
|
||||||
|
</span>
|
||||||
|
<span class="btn btn-danger"
|
||||||
|
ng-show="document.route_step.type == 'APPROVE'"
|
||||||
|
ng-click="validateWorkflow('REJECTED')">
|
||||||
|
{{ 'workflow_transition.REJECTED' | translate }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="page-header page-header-side">
|
||||||
<span class="glyphicon glyphicon-comment"></span>
|
<span class="glyphicon glyphicon-comment"></span>
|
||||||
{{ 'document.view.comments' | translate }}
|
{{ 'document.view.comments' | translate }}
|
||||||
</p>
|
</p>
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
<p class="well-sm">{{ 'document.view.workflow.message' | translate }}</p>
|
||||||
|
|
||||||
|
<form name="startWorkflowForm" class="form-horizontal" novalidate ng-show="!document.route_step">
|
||||||
|
<div class="form-group" ng-class="{ 'has-error': !startWorkflowForm.routemodel.$valid && startWorkflowForm.$dirty }">
|
||||||
|
<label for="inputRouteModel" class="col-sm-3">
|
||||||
|
{{ 'document.view.workflow.workflow_start_label' | translate }}
|
||||||
|
<p ng-if="userInfo.base_functions.indexOf('ADMIN') != -1">
|
||||||
|
<a href="#/settings/workflow">{{ 'document.view.workflow.add_more_workflow' | translate }}</a>
|
||||||
|
</p>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<select required class="form-control" id="inputRouteModel" name="routemodel" ng-model="routemodel">
|
||||||
|
<option ng-repeat="routemodel in routemodels" value="{{ routemodel.id }}">{{ routemodel.name }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<span class="help-block" ng-show="startWorkflowForm.routemodel.$error.required && startWorkflowForm.$dirty">{{ 'validation.required' | translate }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-offset-3 col-sm-9">
|
||||||
|
<button type="submit" class="btn btn-primary"
|
||||||
|
ng-disabled="!startWorkflowForm.$valid"
|
||||||
|
ng-click="startWorkflow()">
|
||||||
|
{{ 'document.view.workflow.start_workflow_submit' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<table class="table" ng-repeat="route in routes">
|
||||||
|
|
||||||
|
<caption translate="document.view.workflow.full_name"
|
||||||
|
translate-values="{ name: route.name, create_date: route.create_date }"></caption>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>For</th>
|
||||||
|
<th>Validation</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="step in route.steps" ng-class="{
|
||||||
|
'bg-success': step.transition == 'VALIDATED' || step.transition == 'APPROVED',
|
||||||
|
'bg-danger': step.transition == 'REJECTED'
|
||||||
|
}">
|
||||||
|
<td>
|
||||||
|
<span class="glyphicon glyphicon-transfer" ng-if="step.type == 'APPROVE'"></span>
|
||||||
|
<span class="glyphicon glyphicon-ok-circle" ng-if="step.type == 'VALIDATE'"></span>
|
||||||
|
{{ 'workflow_type.' + step.type | translate }}
|
||||||
|
</td>
|
||||||
|
<td>{{ step.name }}</td>
|
||||||
|
<td>
|
||||||
|
<acl data="step.target"></acl>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span ng-show="step.end_date">
|
||||||
|
{{ 'workflow_transition.' + step.transition | translate }}
|
||||||
|
{{ step.end_date | timeAgo: dateTimeFormat }}
|
||||||
|
by
|
||||||
|
<a href="#/user/{{ step.validator_username }}" class="label label-default">{{ step.validator_username }}</a>
|
||||||
|
<span ng-show="step.comment" class="text-">
|
||||||
|
<br/>
|
||||||
|
<em>{{ step.comment }}</em>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
@ -237,7 +237,7 @@ input[readonly].share-link {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
.page-header-comments {
|
.page-header-side {
|
||||||
margin: 14px 0 20px;
|
margin: 14px 0 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,3 +450,21 @@ input[readonly].share-link {
|
|||||||
.mb-10 {
|
.mb-10 {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
.btn {
|
||||||
|
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||||
|
font-weight: 400;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cards
|
||||||
|
.card {
|
||||||
|
min-width: 0;
|
||||||
|
word-wrap: break-word;
|
||||||
|
background-color: #fff;
|
||||||
|
background-clip: border-box;
|
||||||
|
border: 1px solid rgba(0,0,0,.125);
|
||||||
|
border-radius: .25em;
|
||||||
|
padding: 1.25em;
|
||||||
|
}
|
@ -52,11 +52,13 @@ public class TestRouteResource extends BaseJerseyTest {
|
|||||||
String document1Id = json.getString("id");
|
String document1Id = json.getString("id");
|
||||||
|
|
||||||
// Start the default route on document 1
|
// Start the default route on document 1
|
||||||
target().path("/route/start").request()
|
json = target().path("/route/start").request()
|
||||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, route1Token)
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, route1Token)
|
||||||
.post(Entity.form(new Form()
|
.post(Entity.form(new Form()
|
||||||
.param("documentId", document1Id)
|
.param("documentId", document1Id)
|
||||||
.param("routeModelId", routeModels.getJsonObject(0).getString("id"))), JsonObject.class);
|
.param("routeModelId", routeModels.getJsonObject(0).getString("id"))), JsonObject.class);
|
||||||
|
JsonObject step = json.getJsonObject("route_step");
|
||||||
|
Assert.assertEquals("Check the document's metadata", step.getString("name"));
|
||||||
|
|
||||||
// Get the route on document 1
|
// Get the route on document 1
|
||||||
json = target().path("/route")
|
json = target().path("/route")
|
||||||
@ -71,7 +73,7 @@ public class TestRouteResource extends BaseJerseyTest {
|
|||||||
Assert.assertNotNull(route.getJsonNumber("create_date"));
|
Assert.assertNotNull(route.getJsonNumber("create_date"));
|
||||||
JsonArray steps = route.getJsonArray("steps");
|
JsonArray steps = route.getJsonArray("steps");
|
||||||
Assert.assertEquals(3, steps.size());
|
Assert.assertEquals(3, steps.size());
|
||||||
JsonObject step = steps.getJsonObject(0);
|
step = steps.getJsonObject(0);
|
||||||
Assert.assertEquals("Check the document's metadata", step.getString("name"));
|
Assert.assertEquals("Check the document's metadata", step.getString("name"));
|
||||||
Assert.assertEquals("VALIDATE", step.getString("type"));
|
Assert.assertEquals("VALIDATE", step.getString("type"));
|
||||||
Assert.assertTrue(step.isNull("comment"));
|
Assert.assertTrue(step.isNull("comment"));
|
||||||
@ -102,11 +104,14 @@ public class TestRouteResource extends BaseJerseyTest {
|
|||||||
Assert.assertEquals("Check the document's metadata", routeStep.getString("name"));
|
Assert.assertEquals("Check the document's metadata", routeStep.getString("name"));
|
||||||
|
|
||||||
// Validate the current step with admin
|
// Validate the current step with admin
|
||||||
target().path("/route/validate").request()
|
json = target().path("/route/validate").request()
|
||||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||||
.post(Entity.form(new Form()
|
.post(Entity.form(new Form()
|
||||||
.param("documentId", document1Id)
|
.param("documentId", document1Id)
|
||||||
.param("transition", "VALIDATED")), JsonObject.class);
|
.param("transition", "VALIDATED")), JsonObject.class);
|
||||||
|
step = json.getJsonObject("route_step");
|
||||||
|
Assert.assertEquals("Add relevant files to the document", step.getString("name"));
|
||||||
|
Assert.assertTrue(json.getBoolean("readable"));
|
||||||
|
|
||||||
// Get the route on document 1
|
// Get the route on document 1
|
||||||
json = target().path("/route")
|
json = target().path("/route")
|
||||||
@ -136,12 +141,15 @@ public class TestRouteResource extends BaseJerseyTest {
|
|||||||
Assert.assertEquals("Add relevant files to the document", routeStep.getString("name"));
|
Assert.assertEquals("Add relevant files to the document", routeStep.getString("name"));
|
||||||
|
|
||||||
// Validate the current step with admin
|
// Validate the current step with admin
|
||||||
target().path("/route/validate").request()
|
json = target().path("/route/validate").request()
|
||||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||||
.post(Entity.form(new Form()
|
.post(Entity.form(new Form()
|
||||||
.param("documentId", document1Id)
|
.param("documentId", document1Id)
|
||||||
.param("transition", "VALIDATED")
|
.param("transition", "VALIDATED")
|
||||||
.param("comment", "OK")), JsonObject.class);
|
.param("comment", "OK")), JsonObject.class);
|
||||||
|
step = json.getJsonObject("route_step");
|
||||||
|
Assert.assertEquals("Approve the document", step.getString("name"));
|
||||||
|
Assert.assertTrue(json.getBoolean("readable"));
|
||||||
|
|
||||||
// Get the route on document 1
|
// Get the route on document 1
|
||||||
json = target().path("/route")
|
json = target().path("/route")
|
||||||
@ -171,11 +179,13 @@ public class TestRouteResource extends BaseJerseyTest {
|
|||||||
Assert.assertEquals("Approve the document", routeStep.getString("name"));
|
Assert.assertEquals("Approve the document", routeStep.getString("name"));
|
||||||
|
|
||||||
// Validate the current step with admin
|
// Validate the current step with admin
|
||||||
target().path("/route/validate").request()
|
json = target().path("/route/validate").request()
|
||||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||||
.post(Entity.form(new Form()
|
.post(Entity.form(new Form()
|
||||||
.param("documentId", document1Id)
|
.param("documentId", document1Id)
|
||||||
.param("transition", "APPROVED")), JsonObject.class);
|
.param("transition", "APPROVED")), JsonObject.class);
|
||||||
|
Assert.assertFalse(json.containsKey("route_step"));
|
||||||
|
Assert.assertFalse(json.getBoolean("readable"));
|
||||||
|
|
||||||
// Get the route on document 1
|
// Get the route on document 1
|
||||||
json = target().path("/route")
|
json = target().path("/route")
|
||||||
|
Loading…
Reference in New Issue
Block a user