From 4dc0927140c2837e2160c91213dc4d0af31e14c8 Mon Sep 17 00:00:00 2001 From: "CITE\\amentis" Date: Wed, 18 Sep 2024 14:10:44 +0300 Subject: [PATCH] add description status logic --- .../org/opencdmp/data/DescriptionEntity.java | 12 ++++ .../org/opencdmp/model/PublicDescription.java | 7 +- .../model/PublicDescriptionStatus.java | 25 +++++++ .../builder/PublicDescriptionBuilder.java | 41 ++++++++++-- .../PublicDescriptionStatusBuilder.java | 54 +++++++++++++++ .../description/DescriptionBuilder.java | 37 ++++++++++- .../model/description/Description.java | 2 +- .../DescriptionToPublicApiDatasetMapper.java | 2 +- .../model/persist/DescriptionPersist.java | 28 ++++---- .../persist/DescriptionStatusPersist.java | 13 ++-- .../org/opencdmp/query/DescriptionQuery.java | 3 + .../dashborad/DashboardServiceImpl.java | 4 +- .../description/DescriptionServiceImpl.java | 52 ++++++++++----- .../DescriptionStatusService.java | 3 + .../DescriptionStatusServiceImpl.java | 32 ++++++++- .../DescriptionWorkflowService.java | 3 + .../DescriptionWorkflowServiceImpl.java | 29 ++++++++- .../service/plan/PlanServiceImpl.java | 50 +++++++++----- .../DescriptionStatusController.java | 24 +++++++ .../app/core/model/description/description.ts | 16 +++-- .../description-status.service.ts | 9 +++ .../recent-edited-activity.component.ts | 15 +++-- ...escription-base-fields-editor.component.ts | 2 +- .../editor/description-editor.component.html | 7 +- .../editor/description-editor.component.ts | 65 ++++++++++++++----- .../editor/description-editor.model.ts | 8 +-- .../description-editor-entity.resolver.ts | 9 +-- .../listing/description-listing.component.ts | 5 +- .../description-listing-item.component.html | 15 +++-- .../description-listing-item.component.ts | 2 +- .../description-overview.component.html | 45 ++++++------- .../description-overview.component.ts | 59 ++++++++++++++--- .../ui/plan/listing/plan-listing.component.ts | 10 +-- .../plan/overview/plan-overview.component.ts | 9 ++- .../plan-editor.component.ts | 4 +- .../resolvers/plan-editor-enitity.resolver.ts | 5 +- .../plan-finalize-dialog.component.html | 10 +-- .../plan-finalize-dialog.component.ts | 8 +-- 38 files changed, 560 insertions(+), 164 deletions(-) create mode 100644 backend/core/src/main/java/org/opencdmp/model/PublicDescriptionStatus.java create mode 100644 backend/core/src/main/java/org/opencdmp/model/builder/PublicDescriptionStatusBuilder.java diff --git a/backend/core/src/main/java/org/opencdmp/data/DescriptionEntity.java b/backend/core/src/main/java/org/opencdmp/data/DescriptionEntity.java index 987d6e4fe..8741b899f 100644 --- a/backend/core/src/main/java/org/opencdmp/data/DescriptionEntity.java +++ b/backend/core/src/main/java/org/opencdmp/data/DescriptionEntity.java @@ -39,6 +39,10 @@ public class DescriptionEntity extends TenantScopedBaseEntity { private DescriptionStatus status; public static final String _status = "status"; + @Column(name = "status_id", nullable = true) + private UUID statusId; + public static final String _statusId = "statusId"; + @Column(name = "description") private String description; @@ -187,5 +191,13 @@ public class DescriptionEntity extends TenantScopedBaseEntity { public void setDescriptionTemplateId(UUID descriptionTemplateId) { this.descriptionTemplateId = descriptionTemplateId; } + + public UUID getStatusId() { + return statusId; + } + + public void setStatusId(UUID statusId) { + this.statusId = statusId; + } } diff --git a/backend/core/src/main/java/org/opencdmp/model/PublicDescription.java b/backend/core/src/main/java/org/opencdmp/model/PublicDescription.java index 778d1df97..260007157 100644 --- a/backend/core/src/main/java/org/opencdmp/model/PublicDescription.java +++ b/backend/core/src/main/java/org/opencdmp/model/PublicDescription.java @@ -1,6 +1,5 @@ package org.opencdmp.model; -import org.opencdmp.commons.enums.DescriptionStatus; import java.time.Instant; import java.util.UUID; @@ -15,7 +14,7 @@ public class PublicDescription { public static final String _label = "label"; - private DescriptionStatus status; + private PublicDescriptionStatus status; public static final String _status = "status"; @@ -65,11 +64,11 @@ public class PublicDescription { } - public DescriptionStatus getStatus() { + public PublicDescriptionStatus getStatus() { return status; } - public void setStatus(DescriptionStatus status) { + public void setStatus(PublicDescriptionStatus status) { this.status = status; } diff --git a/backend/core/src/main/java/org/opencdmp/model/PublicDescriptionStatus.java b/backend/core/src/main/java/org/opencdmp/model/PublicDescriptionStatus.java new file mode 100644 index 000000000..73be295ca --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/PublicDescriptionStatus.java @@ -0,0 +1,25 @@ +package org.opencdmp.model; + + +import java.util.UUID; + +public class PublicDescriptionStatus { + + public final static String _id = "id"; + private UUID id; + + public final static String _name = "name"; + private String name; + + public final static String _internalStatus = "internalStatus"; + private org.opencdmp.commons.enums.DescriptionStatus internalStatus; + + public UUID getId() { return this.id; } + public void setId(UUID id) { this.id = id; } + + public String getName() { return this.name; } + public void setName(String name) { this.name = name; } + + public org.opencdmp.commons.enums.DescriptionStatus getInternalStatus() { return this.internalStatus; } + public void setInternalStatus(org.opencdmp.commons.enums.DescriptionStatus internalStatus) { this.internalStatus = internalStatus; } +} diff --git a/backend/core/src/main/java/org/opencdmp/model/builder/PublicDescriptionBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/PublicDescriptionBuilder.java index 0984191d4..8d7bc74b0 100644 --- a/backend/core/src/main/java/org/opencdmp/model/builder/PublicDescriptionBuilder.java +++ b/backend/core/src/main/java/org/opencdmp/model/builder/PublicDescriptionBuilder.java @@ -10,10 +10,8 @@ import gr.cite.tools.logging.LoggerService; import org.opencdmp.authorization.AuthorizationFlags; import org.opencdmp.convention.ConventionService; import org.opencdmp.data.DescriptionEntity; -import org.opencdmp.model.PublicDescription; -import org.opencdmp.model.PublicDescriptionTemplate; -import org.opencdmp.model.PublicPlan; -import org.opencdmp.model.PublicPlanDescriptionTemplate; +import org.opencdmp.model.*; +import org.opencdmp.query.DescriptionStatusQuery; import org.opencdmp.query.DescriptionTemplateQuery; import org.opencdmp.query.PlanDescriptionTemplateQuery; import org.opencdmp.query.PlanQuery; @@ -66,12 +64,15 @@ public class PublicDescriptionBuilder extends BaseBuilder planItemsMap = this.collectPlans(planFields, data); + FieldSet descriptionStatusFields = fields.extractPrefixed(this.asPrefix(PublicDescription._status)); + Map descriptionStatusItemsMap = this.collectDescriptionStatuses(descriptionStatusFields, data); + List models = new ArrayList<>(); for (DescriptionEntity d : data) { PublicDescription m = new PublicDescription(); if (fields.hasField(this.asIndexer(PublicDescription._id))) m.setId(d.getId()); if (fields.hasField(this.asIndexer(PublicDescription._label))) m.setLabel(d.getLabel()); - if (fields.hasField(this.asIndexer(PublicDescription._status))) m.setStatus(d.getStatus()); + if (!descriptionStatusFields.isEmpty() && descriptionStatusItemsMap != null && descriptionStatusItemsMap.containsKey(d.getStatusId())) m.setStatus(descriptionStatusItemsMap.get(d.getStatusId())); if (fields.hasField(this.asIndexer(PublicDescription._description))) m.setDescription(d.getDescription()); if (fields.hasField(this.asIndexer(PublicDescription._createdAt))) m.setCreatedAt(d.getCreatedAt()); if (fields.hasField(this.asIndexer(PublicDescription._updatedAt))) m.setUpdatedAt(d.getUpdatedAt()); @@ -177,4 +178,34 @@ public class PublicDescriptionBuilder extends BaseBuilder collectDescriptionStatuses(FieldSet fields, List data) throws MyApplicationException { + if (fields.isEmpty() || data.isEmpty()) + return null; + this.logger.debug("checking related - {}", PublicDescriptionStatus.class.getSimpleName()); + + Map itemMap; + if (!fields.hasOtherField(this.asIndexer(PublicDescriptionStatus._id))) { + itemMap = this.asEmpty( + data.stream().map(DescriptionEntity::getDescriptionTemplateId).distinct().collect(Collectors.toList()), + x -> { + PublicDescriptionStatus item = new PublicDescriptionStatus(); + item.setId(x); + return item; + }, + PublicDescriptionStatus::getId); + } else { + FieldSet clone = new BaseFieldSet(fields.getFields()).ensure(PublicDescriptionStatus._id); + DescriptionStatusQuery q = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().authorize(this.authorize).ids(data.stream().map(DescriptionEntity::getStatusId).distinct().collect(Collectors.toList())); + itemMap = this.builderFactory.builder(PublicDescriptionStatusBuilder.class).authorize(this.authorize).asForeignKey(q, clone, PublicDescriptionStatus::getId); + } + if (!fields.hasField(PublicDescriptionStatus._id)) { + itemMap.forEach((id, item) -> { + if (item != null) + item.setId(null); + }); + } + + return itemMap; + } + } diff --git a/backend/core/src/main/java/org/opencdmp/model/builder/PublicDescriptionStatusBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/PublicDescriptionStatusBuilder.java new file mode 100644 index 000000000..7234b7944 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/builder/PublicDescriptionStatusBuilder.java @@ -0,0 +1,54 @@ +package org.opencdmp.model.builder; + +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +import org.opencdmp.authorization.AuthorizationFlags; +import org.opencdmp.convention.ConventionService; + + +import org.opencdmp.data.DescriptionStatusEntity; +import org.opencdmp.model.PublicDescriptionStatus; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class PublicDescriptionStatusBuilder extends BaseBuilder { + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + @Autowired + public PublicDescriptionStatusBuilder( + ConventionService conventionService) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(PublicDescriptionStatusBuilder.class))); + } + + public PublicDescriptionStatusBuilder authorize(EnumSet values) { + this.authorize = values; + return this; + } + + @Override + public List build(FieldSet fields, List data) throws MyApplicationException { + this.logger.debug("building for {} items requesting {} fields", Optional.ofNullable(data).map(List::size).orElse(0), Optional.ofNullable(fields).map(FieldSet::getFields).map(Set::size).orElse(0)); + this.logger.trace(new DataLogEntry("requested fields", fields)); + if (fields == null || data == null || fields.isEmpty()) + return new ArrayList<>(); + List models = new ArrayList<>(); + for (DescriptionStatusEntity d : data) { + PublicDescriptionStatus m = new PublicDescriptionStatus(); + if (fields.hasField(this.asIndexer(PublicDescriptionStatus._id))) m.setId(d.getId()); + if (fields.hasField(this.asIndexer(PublicDescriptionStatus._name))) m.setName(d.getName()); + if (fields.hasField(this.asIndexer(PublicDescriptionStatus._internalStatus))) m.setInternalStatus(d.getInternalStatus()); + models.add(m); + } + this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); + return models; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/model/builder/description/DescriptionBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/description/DescriptionBuilder.java index 374f74996..d1caac1c0 100644 --- a/backend/core/src/main/java/org/opencdmp/model/builder/description/DescriptionBuilder.java +++ b/backend/core/src/main/java/org/opencdmp/model/builder/description/DescriptionBuilder.java @@ -26,10 +26,12 @@ import org.opencdmp.model.builder.DescriptionTagBuilder; import org.opencdmp.model.builder.PlanDescriptionTemplateBuilder; import org.opencdmp.model.builder.UserBuilder; import org.opencdmp.model.builder.descriptionreference.DescriptionReferenceBuilder; +import org.opencdmp.model.builder.descriptionstatus.DescriptionStatusBuilder; import org.opencdmp.model.builder.descriptiontemplate.DescriptionTemplateBuilder; import org.opencdmp.model.builder.plan.PlanBuilder; import org.opencdmp.model.description.Description; import org.opencdmp.model.descriptionreference.DescriptionReference; +import org.opencdmp.model.descriptionstatus.DescriptionStatus; import org.opencdmp.model.descriptiontemplate.DescriptionTemplate; import org.opencdmp.model.plan.Plan; import org.opencdmp.model.user.User; @@ -85,6 +87,9 @@ public class DescriptionBuilder extends BaseBuilder(); + FieldSet statusFields = fields.extractPrefixed(this.asPrefix(Description._status)); + Map statusItemsMap = this.collectDescriptionStatuses(statusFields, data); + FieldSet planDescriptionTemplateFields = fields.extractPrefixed(this.asPrefix(Description._planDescriptionTemplate)); Map planDescriptionTemplateItemsMap = this.collectPlanDescriptionTemplates(planDescriptionTemplateFields, data); @@ -116,7 +121,7 @@ public class DescriptionBuilder extends BaseBuilder collectDescriptionStatuses(FieldSet fields, List data) throws MyApplicationException { + if (fields.isEmpty() || data.isEmpty()) + return null; + this.logger.debug("checking related - {}", DescriptionStatus.class.getSimpleName()); + + Map itemMap; + if (!fields.hasOtherField(this.asIndexer(DescriptionStatus._id))) { + itemMap = this.asEmpty( + data.stream().map(DescriptionEntity::getStatusId).distinct().collect(Collectors.toList()), + x -> { + DescriptionStatus item = new DescriptionStatus(); + item.setId(x); + return item; + }, + DescriptionStatus::getId); + } else { + FieldSet clone = new BaseFieldSet(fields.getFields()).ensure(DescriptionStatus._id); + DescriptionStatusQuery q = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().authorize(this.authorize).ids(data.stream().map(DescriptionEntity::getStatusId).distinct().collect(Collectors.toList())); + itemMap = this.builderFactory.builder(DescriptionStatusBuilder.class).authorize(this.authorize).asForeignKey(q, clone, DescriptionStatus::getId); + } + if (!fields.hasField(DescriptionStatus._id)) { + itemMap.forEach((id, item) -> { + if (item != null) + item.setId(null); + }); + } + + return itemMap; + } + private Map collectUsers(FieldSet fields, List data) throws MyApplicationException { if (fields.isEmpty() || data.isEmpty()) return null; diff --git a/backend/core/src/main/java/org/opencdmp/model/description/Description.java b/backend/core/src/main/java/org/opencdmp/model/description/Description.java index 7ac20ef08..c6049cacd 100644 --- a/backend/core/src/main/java/org/opencdmp/model/description/Description.java +++ b/backend/core/src/main/java/org/opencdmp/model/description/Description.java @@ -1,6 +1,6 @@ package org.opencdmp.model.description; -import org.opencdmp.commons.enums.DescriptionStatus; +import org.opencdmp.model.descriptionstatus.DescriptionStatus; import org.opencdmp.commons.enums.IsActive; import org.opencdmp.model.*; import org.opencdmp.model.descriptionreference.DescriptionReference; diff --git a/backend/core/src/main/java/org/opencdmp/model/mapper/publicapi/DescriptionToPublicApiDatasetMapper.java b/backend/core/src/main/java/org/opencdmp/model/mapper/publicapi/DescriptionToPublicApiDatasetMapper.java index 1cbf7d4f5..d3ee28796 100644 --- a/backend/core/src/main/java/org/opencdmp/model/mapper/publicapi/DescriptionToPublicApiDatasetMapper.java +++ b/backend/core/src/main/java/org/opencdmp/model/mapper/publicapi/DescriptionToPublicApiDatasetMapper.java @@ -30,7 +30,7 @@ public class DescriptionToPublicApiDatasetMapper { model.setDescription(description.getDescription()); model.setReference(""); model.setUri(""); - model.setStatus(description.getStatus()); +// TODO status model.setStatus(description.getStatus()); model.setDmp(dmp); model.setDatasetProfileDefinition(descriptionTemplateToPublicApiDatasetProfileMapper.toPublicModel(description.getDescriptionTemplate())); diff --git a/backend/core/src/main/java/org/opencdmp/model/persist/DescriptionPersist.java b/backend/core/src/main/java/org/opencdmp/model/persist/DescriptionPersist.java index dd81a9404..14b1b6ea9 100644 --- a/backend/core/src/main/java/org/opencdmp/model/persist/DescriptionPersist.java +++ b/backend/core/src/main/java/org/opencdmp/model/persist/DescriptionPersist.java @@ -46,9 +46,9 @@ public class DescriptionPersist { public static final String _descriptionTemplateId = "descriptionTemplateId"; - private DescriptionStatus status; + private UUID statusId; - public static final String _status = "status"; + public static final String _statusId = "statusId"; private String description; @@ -97,12 +97,12 @@ public class DescriptionPersist { this.planDescriptionTemplateId = planDescriptionTemplateId; } - public DescriptionStatus getStatus() { - return this.status; + public UUID getStatusId() { + return statusId; } - public void setStatus(DescriptionStatus status) { - this.status = status; + public void setStatusId(UUID statusId) { + this.statusId = statusId; } public String getDescription() { @@ -178,16 +178,19 @@ public class DescriptionPersist { DescriptionTemplateEntity descriptionTemplate = null; PlanEntity planEntity = null; PlanBlueprintEntity planBlueprintEntity = null; + DescriptionStatusEntity statusEntity = null; try { descriptionTemplate = this.isValidGuid(item.getDescriptionTemplateId()) ? this.entityManager.find(DescriptionTemplateEntity.class, item.getDescriptionTemplateId(), true) : null; planEntity = this.isValidGuid(item.getPlanId()) ? this.entityManager.find(PlanEntity.class, item.getPlanId(), true) : null; planBlueprintEntity = planEntity == null ? null : this.entityManager.find(PlanBlueprintEntity.class, planEntity.getBlueprintId()); + statusEntity = this.isValidGuid(item.getStatusId()) ? this.entityManager.find(DescriptionStatusEntity.class, item.getStatusId(), true) : null; } catch (InvalidApplicationException e) { throw new RuntimeException(e); } DefinitionEntity definition = descriptionTemplate == null ? null : this.xmlHandlingService.fromXmlSafe(DefinitionEntity.class, descriptionTemplate.getDefinition()); PlanBlueprintEntity finalPlanBlueprintEntity = planBlueprintEntity; + DescriptionStatusEntity finalStatusEntity = statusEntity; return Arrays.asList( this.spec() .iff(() -> this.isValidGuid(item.getId())) @@ -214,21 +217,22 @@ public class DescriptionPersist { .must(() -> this.isValidGuid(item.getPlanDescriptionTemplateId())) .failOn(DescriptionPersist._planDescriptionTemplateId).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{DescriptionPersist._planDescriptionTemplateId}, LocaleContextHolder.getLocale())), this.spec() - .must(() -> !this.isNull(item.getStatus())) - .failOn(DescriptionPersist._status).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{DescriptionPersist._status}, LocaleContextHolder.getLocale())), + .iff(() -> this.isValidGuid(item.getId())) + .must(() -> this.isValidGuid(item.getStatusId())) + .failOn(DescriptionPersist._statusId).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{DescriptionPersist._statusId}, LocaleContextHolder.getLocale())), this.spec() - .iff(() -> item.getStatus() == DescriptionStatus.Finalized) + .iff(() -> finalStatusEntity != null && finalStatusEntity.getInternalStatus() != null && finalStatusEntity.getInternalStatus() == DescriptionStatus.Finalized) .must(() -> !this.isNull(item.getProperties())) .failOn(DescriptionPersist._properties).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{DescriptionPersist._properties}, LocaleContextHolder.getLocale())), this.spec() - .iff(() -> item.getStatus() == DescriptionStatus.Finalized) + .iff(() -> finalStatusEntity != null && finalStatusEntity.getInternalStatus() != null && finalStatusEntity.getInternalStatus() == DescriptionStatus.Finalized) .must(() -> this.isDescriptionTemplateMaxMultiplicityValid(finalPlanBlueprintEntity, item.getPlanId(), item.getPlanDescriptionTemplateId(), this.isValidGuid(item.getId()))) .failOn(DescriptionPersist._descriptionTemplateId).failWith(this.messageSource.getMessage("Validation.InvalidDescriptionTemplateMultiplicity", new Object[]{DescriptionPersist._descriptionTemplateId}, LocaleContextHolder.getLocale())), this.refSpec() - .iff(() -> item.getStatus() == DescriptionStatus.Finalized) + .iff(() -> finalStatusEntity != null && finalStatusEntity.getInternalStatus() != null && finalStatusEntity.getInternalStatus() == DescriptionStatus.Finalized) .on(DescriptionPersist._properties) .over(item.getProperties()) - .using(() -> this.validatorFactory.validator(PropertyDefinitionPersist.PropertyDefinitionPersistValidator.class).setStatus(item.getStatus()).withDefinition(definition).setVisibilityService(definition, item.getProperties())) + .using(() -> this.validatorFactory.validator(PropertyDefinitionPersist.PropertyDefinitionPersistValidator.class).setStatus(finalStatusEntity.getInternalStatus()).withDefinition(definition).setVisibilityService(definition, item.getProperties())) // this.navSpec() // .iff(() -> !this.isNull(item.getTags())) // .on(DescriptionPersist._tags) diff --git a/backend/core/src/main/java/org/opencdmp/model/persist/DescriptionStatusPersist.java b/backend/core/src/main/java/org/opencdmp/model/persist/DescriptionStatusPersist.java index 259afc084..84669208b 100644 --- a/backend/core/src/main/java/org/opencdmp/model/persist/DescriptionStatusPersist.java +++ b/backend/core/src/main/java/org/opencdmp/model/persist/DescriptionStatusPersist.java @@ -1,6 +1,5 @@ package org.opencdmp.model.persist; -import org.opencdmp.commons.enums.DescriptionStatus; import org.opencdmp.commons.validation.BaseValidator; import gr.cite.tools.validation.specification.Specification; import org.opencdmp.convention.ConventionService; @@ -21,7 +20,7 @@ public class DescriptionStatusPersist { public static final String _id = "id"; - private DescriptionStatus status; + private UUID statusId; public static final String _status = "status"; @@ -37,12 +36,12 @@ public class DescriptionStatusPersist { this.id = id; } - public DescriptionStatus getStatus() { - return status; + public UUID getStatusId() { + return statusId; } - public void setStatus(DescriptionStatus status) { - this.status = status; + public void setStatusId(UUID statusId) { + this.statusId = statusId; } public String getHash() { @@ -82,7 +81,7 @@ public class DescriptionStatusPersist { .must(() -> this.isValidHash(item.getHash())) .failOn(DescriptionStatusPersist._hash).failWith(messageSource.getMessage("Validation_Required", new Object[]{DescriptionStatusPersist._hash}, LocaleContextHolder.getLocale())), this.spec() - .must(() -> !this.isNull(item.getStatus())) + .must(() -> this.isValidGuid(item.getStatusId())) .failOn(DescriptionStatusPersist._status).failWith(messageSource.getMessage("Validation_Required", new Object[]{DescriptionStatusPersist._status}, LocaleContextHolder.getLocale())) ); } diff --git a/backend/core/src/main/java/org/opencdmp/query/DescriptionQuery.java b/backend/core/src/main/java/org/opencdmp/query/DescriptionQuery.java index 0521c6165..24de0469c 100644 --- a/backend/core/src/main/java/org/opencdmp/query/DescriptionQuery.java +++ b/backend/core/src/main/java/org/opencdmp/query/DescriptionQuery.java @@ -400,6 +400,8 @@ public class DescriptionQuery extends QueryBase { return DescriptionEntity._properties; else if (item.match(Description._status) || item.match(PublicDescription._status)) return DescriptionEntity._status; + else if (item.prefix(Description._status) || item.prefix(PublicDescription._status)) + return DescriptionEntity._statusId; else if (item.match(Description._description) || item.match(PublicDescription._description)) return DescriptionEntity._description; else if (item.match(Description._createdBy)) @@ -444,6 +446,7 @@ public class DescriptionQuery extends QueryBase { item.setLabel(QueryBase.convertSafe(tuple, columns, DescriptionEntity._label, String.class)); item.setProperties(QueryBase.convertSafe(tuple, columns, DescriptionEntity._properties, String.class)); item.setStatus(QueryBase.convertSafe(tuple, columns, DescriptionEntity._status, DescriptionStatus.class)); + item.setStatusId(QueryBase.convertSafe(tuple, columns, DescriptionEntity._statusId, UUID.class)); item.setDescription(QueryBase.convertSafe(tuple, columns, DescriptionEntity._description, String.class)); item.setCreatedAt(QueryBase.convertSafe(tuple, columns, DescriptionEntity._createdAt, Instant.class)); item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, DescriptionEntity._updatedAt, Instant.class)); diff --git a/backend/core/src/main/java/org/opencdmp/service/dashborad/DashboardServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/dashborad/DashboardServiceImpl.java index 48d6e11b7..20ab26aff 100644 --- a/backend/core/src/main/java/org/opencdmp/service/dashborad/DashboardServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/dashborad/DashboardServiceImpl.java @@ -77,9 +77,9 @@ public class DashboardServiceImpl implements DashboardService { descriptionLookup.getPage().setOffset(0); descriptionLookup.getPage().setSize(model.getPage().getSize()+model.getPage().getOffset()); - QueryResult descriptions = this.elasticQueryHelperService.collect(descriptionLookup, AuthorizationFlags.AllExceptPublic, new BaseFieldSet().ensure(Description._id).ensure(Description._updatedAt).ensure(Description._status).ensure(Description._label)); + QueryResult descriptions = this.elasticQueryHelperService.collect(descriptionLookup, AuthorizationFlags.AllExceptPublic, new BaseFieldSet().ensure(Description._id).ensure(Description._updatedAt).ensure(String.join(".",Description._status, org.opencdmp.model.descriptionstatus.DescriptionStatus._internalStatus)).ensure(Description._label)); if (!this.conventionService.isListNullOrEmpty(descriptions.getItems())) { - for (Description description : descriptions.getItems()) recentActivityItemEntities.add(new RecentActivityItemEntity(RecentActivityItemType.Description, description.getId(), description.getUpdatedAt(), description.getLabel(), description.getStatus().getValue())); + for (Description description : descriptions.getItems()) recentActivityItemEntities.add(new RecentActivityItemEntity(RecentActivityItemType.Description, description.getId(), description.getUpdatedAt(), description.getLabel(), description.getStatus().getInternalStatus().getValue())); } } diff --git a/backend/core/src/main/java/org/opencdmp/service/description/DescriptionServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/description/DescriptionServiceImpl.java index 81a216a51..4146f9b06 100644 --- a/backend/core/src/main/java/org/opencdmp/service/description/DescriptionServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/description/DescriptionServiceImpl.java @@ -69,6 +69,7 @@ import org.opencdmp.model.referencetype.ReferenceType; import org.opencdmp.query.*; import org.opencdmp.service.accounting.AccountingService; import org.opencdmp.service.descriptiontemplate.DescriptionTemplateService; +import org.opencdmp.service.descriptionworkflow.DescriptionWorkflowService; import org.opencdmp.service.elastic.ElasticService; import org.opencdmp.service.filetransformer.FileTransformerService; import org.opencdmp.service.responseutils.ResponseUtilsService; @@ -141,6 +142,7 @@ public class DescriptionServiceImpl implements DescriptionService { private final TagService tagService; private final UsageLimitService usageLimitService; private final AccountingService accountingService; + private final DescriptionWorkflowService descriptionWorkflowService; @Autowired public DescriptionServiceImpl( @@ -155,7 +157,7 @@ public class DescriptionServiceImpl implements DescriptionService { QueryFactory queryFactory, JsonHandlingService jsonHandlingService, UserScope userScope, - XmlHandlingService xmlHandlingService, NotifyIntegrationEventHandler eventHandler, NotificationProperties notificationProperties, FileTransformerService fileTransformerService, ElasticService elasticService, ValidatorFactory validatorFactory, StorageFileProperties storageFileConfig, StorageFileService storageFileService, AuthorizationContentResolver authorizationContentResolver, AnnotationEntityTouchedIntegrationEventHandler annotationEntityTouchedIntegrationEventHandler, AnnotationEntityRemovalIntegrationEventHandler annotationEntityRemovalIntegrationEventHandler, TenantScope tenantScope, ResponseUtilsService responseUtilsService, DescriptionTemplateService descriptionTemplateService, TagService tagService, UsageLimitService usageLimitService, AccountingService accountingService) { + XmlHandlingService xmlHandlingService, NotifyIntegrationEventHandler eventHandler, NotificationProperties notificationProperties, FileTransformerService fileTransformerService, ElasticService elasticService, ValidatorFactory validatorFactory, StorageFileProperties storageFileConfig, StorageFileService storageFileService, AuthorizationContentResolver authorizationContentResolver, AnnotationEntityTouchedIntegrationEventHandler annotationEntityTouchedIntegrationEventHandler, AnnotationEntityRemovalIntegrationEventHandler annotationEntityRemovalIntegrationEventHandler, TenantScope tenantScope, ResponseUtilsService responseUtilsService, DescriptionTemplateService descriptionTemplateService, TagService tagService, UsageLimitService usageLimitService, AccountingService accountingService, DescriptionWorkflowService descriptionWorkflowService) { this.entityManager = entityManager; this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; @@ -184,6 +186,7 @@ public class DescriptionServiceImpl implements DescriptionService { this.tagService = tagService; this.usageLimitService = usageLimitService; this.accountingService = accountingService; + this.descriptionWorkflowService = descriptionWorkflowService; } @Override @@ -223,9 +226,17 @@ public class DescriptionServiceImpl implements DescriptionService { data = this.entityManager.find(DescriptionEntity.class, model.getId()); if (data == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Description.class.getSimpleName()}, LocaleContextHolder.getLocale())); if (!this.conventionService.hashValue(data.getUpdatedAt()).equals(model.getHash())) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage()); - if (data.getStatus().equals(DescriptionStatus.Finalized)) throw new MyValidationException(this.errors.getDescriptionIsFinalized().getCode(), this.errors.getDescriptionIsFinalized().getMessage()); + DescriptionStatusEntity oldDescriptionStatusEntity = this.entityManager.find(DescriptionStatusEntity.class, data.getStatusId(), true); + if (oldDescriptionStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{data.getStatusId(), org.opencdmp.model.descriptionstatus.DescriptionStatus.class.getSimpleName()}, LocaleContextHolder.getLocale())); + if (oldDescriptionStatusEntity.getInternalStatus() != null && oldDescriptionStatusEntity.getInternalStatus().equals(DescriptionStatus.Finalized)) throw new MyValidationException(this.errors.getDescriptionIsFinalized().getCode(), this.errors.getDescriptionIsFinalized().getMessage()); if (!data.getPlanId().equals(model.getPlanId())) throw new MyValidationException(this.errors.getPlanCanNotChange().getCode(), this.errors.getPlanCanNotChange().getMessage()); if (!data.getPlanDescriptionTemplateId().equals(model.getPlanDescriptionTemplateId())) throw new MyValidationException(this.errors.getPlanDescriptionTemplateCanNotChange().getCode(), this.errors.getPlanDescriptionTemplateCanNotChange().getMessage()); + if (model.getStatusId() != null && !model.getStatusId().equals(data.getStatusId())) { + data.setStatusId(model.getStatusId()); + DescriptionStatusEntity newDescriptionStatusEntity = this.entityManager.find(DescriptionStatusEntity.class, model.getStatusId(), true); + if (newDescriptionStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getStatusId(), org.opencdmp.model.descriptionstatus.DescriptionStatus.class.getSimpleName()}, LocaleContextHolder.getLocale())); + if (newDescriptionStatusEntity.getInternalStatus() != null && newDescriptionStatusEntity.getInternalStatus().equals(DescriptionStatus.Finalized)) data.setFinalizedAt(Instant.now()); + } } else { this.usageLimitService.checkIncrease(UsageLimitTargetMetric.DESCRIPTION_COUNT); @@ -244,6 +255,8 @@ public class DescriptionServiceImpl implements DescriptionService { data.setCreatedById(this.userScope.getUserId()); data.setPlanId(model.getPlanId()); data.setPlanDescriptionTemplateId(model.getPlanDescriptionTemplateId()); + data.setStatus(DescriptionStatus.Draft); + data.setStatusId(this.descriptionWorkflowService.getWorkFlowDefinition().getStartingStatusId()); } DescriptionTemplateEntity descriptionTemplateEntity = this.entityManager.find(DescriptionTemplateEntity.class, model.getDescriptionTemplateId(), true); @@ -257,8 +270,6 @@ public class DescriptionServiceImpl implements DescriptionService { if (plan.getStatus().equals(PlanStatus.Finalized) && isUpdate) throw new MyValidationException(this.errors.getPlanIsFinalized().getCode(), this.errors.getPlanIsFinalized().getMessage()); data.setLabel(model.getLabel()); - data.setStatus(model.getStatus()); - if (model.getStatus() == DescriptionStatus.Finalized) data.setFinalizedAt(Instant.now()); data.setDescription(model.getDescription()); data.setDescriptionTemplateId(model.getDescriptionTemplateId()); data.setUpdatedAt(Instant.now()); @@ -376,6 +387,10 @@ public class DescriptionServiceImpl implements DescriptionService { } private void sendNotification(DescriptionEntity description, Boolean isUpdate) throws InvalidApplicationException { + DescriptionStatusEntity descriptionStatusEntity = this.entityManager.find(DescriptionStatusEntity.class, description.getStatusId(), true); + if (descriptionStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{description, DescriptionStatus.class.getSimpleName()}, LocaleContextHolder.getLocale())); + if (descriptionStatusEntity.getInternalStatus() == null || descriptionStatusEntity.getInternalStatus().equals(DescriptionStatus.Canceled)) return; + List existingUsers = this.queryFactory.query(PlanUserQuery.class).disableTracking() .planIds(description.getPlanId()) .isActives(IsActive.Active) @@ -388,7 +403,7 @@ public class DescriptionServiceImpl implements DescriptionService { if (!planUser.getUserId().equals(this.userScope.getUserIdSafe())){ UserEntity user = this.queryFactory.query(UserQuery.class).disableTracking().ids(planUser.getUserId()).first(); if (user == null || user.getIsActive().equals(IsActive.Inactive)) throw new MyValidationException(this.errors.getPlanInactiveUser().getCode(), this.errors.getPlanInactiveUser().getMessage()); - this.createDescriptionNotificationEvent(description, user, isUpdate); + this.createDescriptionNotificationEvent(description, descriptionStatusEntity.getInternalStatus(), user, isUpdate); } } } @@ -437,11 +452,11 @@ public class DescriptionServiceImpl implements DescriptionService { return cleanData; } - private void createDescriptionNotificationEvent(DescriptionEntity description, UserEntity user, Boolean isUpdate) throws InvalidApplicationException { + private void createDescriptionNotificationEvent(DescriptionEntity description, DescriptionStatus internalStatus, UserEntity user, Boolean isUpdate) throws InvalidApplicationException { NotifyIntegrationEvent event = new NotifyIntegrationEvent(); event.setUserId(user.getId()); - this.applyNotificationType(description.getStatus(), event, isUpdate); + this.applyNotificationType(internalStatus, event, isUpdate); NotificationFieldData data = new NotificationFieldData(); List fieldInfoList = new ArrayList<>(); fieldInfoList.add(new FieldInfo("{recipient}", DataType.String, user.getName())); @@ -482,16 +497,20 @@ public class DescriptionServiceImpl implements DescriptionService { DescriptionEntity data = this.entityManager.find(DescriptionEntity.class, model.getId()); if (data == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Description.class.getSimpleName()}, LocaleContextHolder.getLocale())); if (!this.conventionService.hashValue(data.getUpdatedAt()).equals(model.getHash())) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage()); - if (!data.getStatus().equals(model.getStatus())){ - if (data.getStatus().equals(DescriptionStatus.Finalized)){ + if (!data.getStatusId().equals(model.getStatusId())){ + DescriptionStatusEntity oldStatusEntity = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().ids(data.getStatusId()).isActive(IsActive.Active).firstAs(new BaseFieldSet().ensure(org.opencdmp.model.descriptionstatus.DescriptionStatus._id).ensure(org.opencdmp.model.descriptionstatus.DescriptionStatus._internalStatus)); + if (oldStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{data.getStatusId(), DescriptionStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + if (oldStatusEntity.getInternalStatus() != null && oldStatusEntity.getInternalStatus().equals(DescriptionStatus.Finalized)){ this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.descriptionAffiliation(model.getId())), Permission.FinalizeDescription); PlanEntity planEntity = this.entityManager.find(PlanEntity.class, data.getPlanId(), true); if (planEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{data.getPlanId(), PlanEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); if(!planEntity.getStatus().equals(PlanStatus.Draft)) throw new MyValidationException(this.errors.getPlanIsFinalized().getCode(), this.errors.getPlanIsFinalized().getMessage()); } - data.setStatus(model.getStatus()); - if (model.getStatus() == DescriptionStatus.Finalized) data.setFinalizedAt(Instant.now()); + data.setStatusId(model.getStatusId()); + DescriptionStatusEntity newStatusEntity = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().ids(model.getStatusId()).isActive(IsActive.Active).firstAs(new BaseFieldSet().ensure(org.opencdmp.model.descriptionstatus.DescriptionStatus._id).ensure(org.opencdmp.model.descriptionstatus.DescriptionStatus._internalStatus)); + if (newStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getStatusId(), DescriptionStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + if (newStatusEntity.getInternalStatus() != null && newStatusEntity.getInternalStatus().equals(DescriptionStatus.Finalized)) data.setFinalizedAt(Instant.now()); data.setUpdatedAt(Instant.now()); this.entityManager.merge(data); @@ -501,7 +520,7 @@ public class DescriptionServiceImpl implements DescriptionService { this.eventBroker.emit(new DescriptionTouchedEvent(data.getId())); this.annotationEntityTouchedIntegrationEventHandler.handleDescription(data.getId()); - if (data.getStatus().equals(DescriptionStatus.Finalized)) this.sendNotification(data, true); + if (newStatusEntity.getInternalStatus() != null && newStatusEntity.getInternalStatus().equals(DescriptionStatus.Finalized)) this.sendNotification(data, true); } return this.builderFactory.builder(DescriptionBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(BaseFieldSet.build(fields, Description._id), data); } @@ -1003,7 +1022,8 @@ public class DescriptionServiceImpl implements DescriptionService { persist.setId(data.getId()); persist.setLabel(data.getLabel()); - persist.setStatus(DescriptionStatus.Finalized); + DescriptionStatusEntity statusEntity = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().internalStatuses(DescriptionStatus.Finalized).isActive(IsActive.Active).firstAs(new BaseFieldSet().ensure(org.opencdmp.model.descriptionstatus.DescriptionStatus._id)); + if (statusEntity != null) persist.setStatusId(statusEntity.getId()); persist.setDescription(data.getDescription()); persist.setDescriptionTemplateId(data.getDescriptionTemplateId()); persist.setPlanId(data.getPlanId()); @@ -1382,7 +1402,8 @@ public class DescriptionServiceImpl implements DescriptionService { DescriptionPersist persist = new DescriptionPersist(); persist.setLabel(descriptionXml.getLabel()); persist.setDescription(descriptionXml.getDescription()); - persist.setStatus(DescriptionStatus.Draft); + DescriptionStatusEntity statusEntity = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().isActive(IsActive.Active).internalStatuses(DescriptionStatus.Draft).firstAs(new BaseFieldSet().ensure(org.opencdmp.model.descriptionstatus.DescriptionStatus._id)); + if (statusEntity != null) persist.setStatusId(statusEntity.getId()); persist.setPlanId(planId); persist.setDescriptionTemplateId(this.xmlToDescriptionTemplatePersist(descriptionXml)); persist.setPlanDescriptionTemplateId(this.xmlToPlanDescriptionTemplatePersist(descriptionXml, planId)); @@ -1598,7 +1619,8 @@ public class DescriptionServiceImpl implements DescriptionService { DescriptionPersist persist = new DescriptionPersist(); persist.setLabel(model.getLabel()); persist.setDescription(model.getDescription()); - persist.setStatus(DescriptionStatus.Draft); + DescriptionStatusEntity statusEntity = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().isActive(IsActive.Active).internalStatuses(DescriptionStatus.Draft).firstAs(new BaseFieldSet().ensure(org.opencdmp.model.descriptionstatus.DescriptionStatus._id)); + if (statusEntity != null) persist.setStatusId(statusEntity.getId()); persist.setPlanId(planId); persist.setDescriptionTemplateId(this.commonModelToDescriptionTemplatePersist(model)); persist.setPlanDescriptionTemplateId(this.commonModelTToPlanDescriptionTemplatePersist(model, planId)); diff --git a/backend/core/src/main/java/org/opencdmp/service/descriptionstatus/DescriptionStatusService.java b/backend/core/src/main/java/org/opencdmp/service/descriptionstatus/DescriptionStatusService.java index 4d90894d1..7c8ce622f 100644 --- a/backend/core/src/main/java/org/opencdmp/service/descriptionstatus/DescriptionStatusService.java +++ b/backend/core/src/main/java/org/opencdmp/service/descriptionstatus/DescriptionStatusService.java @@ -10,6 +10,7 @@ import org.opencdmp.model.descriptionstatus.DescriptionStatus; import org.opencdmp.model.persist.descriptionstatus.DescriptionStatusPersist; import javax.management.InvalidApplicationException; +import java.util.List; import java.util.UUID; public interface DescriptionStatusService { @@ -17,4 +18,6 @@ public interface DescriptionStatusService { DescriptionStatus persist(DescriptionStatusPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JAXBException; void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException; + + List getAvailableTransitionStatuses(UUID descriptionId) throws InvalidApplicationException; } diff --git a/backend/core/src/main/java/org/opencdmp/service/descriptionstatus/DescriptionStatusServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/descriptionstatus/DescriptionStatusServiceImpl.java index 13f6f235c..75f948c86 100644 --- a/backend/core/src/main/java/org/opencdmp/service/descriptionstatus/DescriptionStatusServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/descriptionstatus/DescriptionStatusServiceImpl.java @@ -3,6 +3,7 @@ package org.opencdmp.service.descriptionstatus; import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.data.builder.BuilderFactory; import gr.cite.tools.data.deleter.DeleterFactory; +import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.exception.MyApplicationException; import gr.cite.tools.exception.MyForbiddenException; import gr.cite.tools.exception.MyNotFoundException; @@ -12,22 +13,29 @@ import gr.cite.tools.fieldset.FieldSet; import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.MapLogEntry; import jakarta.xml.bind.JAXBException; +import org.opencdmp.authorization.AuthorizationFlags; import org.opencdmp.authorization.Permission; import org.opencdmp.commons.XmlHandlingService; import org.opencdmp.commons.enums.IsActive; import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionAuthorizationEntity; import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionAuthorizationItemEntity; import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionEntity; +import org.opencdmp.commons.types.descriptionworkflow.DescriptionWorkflowDefinitionEntity; +import org.opencdmp.commons.types.descriptionworkflow.DescriptionWorkflowDefinitionTransitionEntity; import org.opencdmp.convention.ConventionService; +import org.opencdmp.data.DescriptionEntity; import org.opencdmp.data.DescriptionStatusEntity; import org.opencdmp.data.TenantEntityManager; import org.opencdmp.model.builder.descriptionstatus.DescriptionStatusBuilder; import org.opencdmp.model.deleter.DescriptionStatusDeleter; +import org.opencdmp.model.description.Description; import org.opencdmp.model.descriptionstatus.DescriptionStatus; import org.opencdmp.model.persist.descriptionstatus.DescriptionStatusDefinitionAuthorizationItemPersist; import org.opencdmp.model.persist.descriptionstatus.DescriptionStatusDefinitionAuthorizationPersist; import org.opencdmp.model.persist.descriptionstatus.DescriptionStatusDefinitionPersist; import org.opencdmp.model.persist.descriptionstatus.DescriptionStatusPersist; +import org.opencdmp.query.DescriptionStatusQuery; +import org.opencdmp.service.descriptionworkflow.DescriptionWorkflowService; import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; @@ -35,8 +43,10 @@ import org.springframework.stereotype.Service; import javax.management.InvalidApplicationException; import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; @Service public class DescriptionStatusServiceImpl implements DescriptionStatusService { @@ -51,8 +61,10 @@ public class DescriptionStatusServiceImpl implements DescriptionStatusService { private final ConventionService conventionService; private final MessageSource messageSource; private final XmlHandlingService xmlHandlingService; + private final QueryFactory queryFactory; + private final DescriptionWorkflowService descriptionWorkflowService; - public DescriptionStatusServiceImpl(BuilderFactory builderFactory, DeleterFactory deleterFactory, AuthorizationService authService, TenantEntityManager entityManager, ConventionService conventionService, MessageSource messageSource, XmlHandlingService xmlHandlingService) { + public DescriptionStatusServiceImpl(BuilderFactory builderFactory, DeleterFactory deleterFactory, AuthorizationService authService, TenantEntityManager entityManager, ConventionService conventionService, MessageSource messageSource, XmlHandlingService xmlHandlingService, QueryFactory queryFactory, DescriptionWorkflowService descriptionWorkflowService) { this.builderFactory = builderFactory; this.deleterFactory = deleterFactory; @@ -61,6 +73,8 @@ public class DescriptionStatusServiceImpl implements DescriptionStatusService { this.conventionService = conventionService; this.messageSource = messageSource; this.xmlHandlingService = xmlHandlingService; + this.queryFactory = queryFactory; + this.descriptionWorkflowService = descriptionWorkflowService; } @@ -141,4 +155,20 @@ public class DescriptionStatusServiceImpl implements DescriptionStatusService { return data; } + + public List getAvailableTransitionStatuses(UUID descriptionId) throws InvalidApplicationException { + DescriptionWorkflowDefinitionEntity definition = this.descriptionWorkflowService.getWorkFlowDefinition(); + + DescriptionEntity description = this.entityManager.find(DescriptionEntity.class, descriptionId); + if (description == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{descriptionId, Description.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + List availableTransitions = definition.getStatusTransitions().stream().filter(x -> x.getFromStatusId().equals(description.getStatusId())).collect(Collectors.toList()); + if (!this.conventionService.isListNullOrEmpty(availableTransitions)){ + DescriptionStatusQuery query = this.queryFactory.query(DescriptionStatusQuery.class).authorize(AuthorizationFlags.AllExceptPublic).isActive(IsActive.Active).ids(availableTransitions.stream().map(DescriptionWorkflowDefinitionTransitionEntity::getToStatusId).distinct().toList()); + FieldSet fieldSet = new BaseFieldSet().ensure(DescriptionStatus._id).ensure(DescriptionStatus._name).ensure(DescriptionStatus._internalStatus); + return this.builderFactory.builder(DescriptionStatusBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(fieldSet, query.collectAs(fieldSet)); + } + + return new ArrayList<>(); + } } diff --git a/backend/core/src/main/java/org/opencdmp/service/descriptionworkflow/DescriptionWorkflowService.java b/backend/core/src/main/java/org/opencdmp/service/descriptionworkflow/DescriptionWorkflowService.java index f7c74adeb..8ae86de1d 100644 --- a/backend/core/src/main/java/org/opencdmp/service/descriptionworkflow/DescriptionWorkflowService.java +++ b/backend/core/src/main/java/org/opencdmp/service/descriptionworkflow/DescriptionWorkflowService.java @@ -1,6 +1,7 @@ package org.opencdmp.service.descriptionworkflow; import gr.cite.tools.fieldset.FieldSet; +import org.opencdmp.commons.types.descriptionworkflow.DescriptionWorkflowDefinitionEntity; import org.opencdmp.model.descriptionworkflow.DescriptionWorkflow; import org.opencdmp.model.persist.descriptionworkflow.DescriptionWorkflowPersist; @@ -11,4 +12,6 @@ public interface DescriptionWorkflowService { DescriptionWorkflow persist(DescriptionWorkflowPersist persist, FieldSet fields) throws InvalidApplicationException; void deleteAndSave(UUID id) throws InvalidApplicationException; + + DescriptionWorkflowDefinitionEntity getWorkFlowDefinition() throws InvalidApplicationException; } diff --git a/backend/core/src/main/java/org/opencdmp/service/descriptionworkflow/DescriptionWorkflowServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/descriptionworkflow/DescriptionWorkflowServiceImpl.java index 0b72412ea..f299cbf2a 100644 --- a/backend/core/src/main/java/org/opencdmp/service/descriptionworkflow/DescriptionWorkflowServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/descriptionworkflow/DescriptionWorkflowServiceImpl.java @@ -3,15 +3,19 @@ package org.opencdmp.service.descriptionworkflow; import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.data.builder.BuilderFactory; import gr.cite.tools.data.deleter.DeleterFactory; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.exception.MyApplicationException; import gr.cite.tools.exception.MyNotFoundException; import gr.cite.tools.exception.MyValidationException; import gr.cite.tools.fieldset.BaseFieldSet; import gr.cite.tools.fieldset.FieldSet; import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.MapLogEntry; +import org.opencdmp.authorization.AuthorizationFlags; import org.opencdmp.authorization.Permission; import org.opencdmp.commons.XmlHandlingService; import org.opencdmp.commons.enums.IsActive; +import org.opencdmp.commons.scope.tenant.TenantScope; import org.opencdmp.commons.types.descriptionworkflow.DescriptionWorkflowDefinitionEntity; import org.opencdmp.commons.types.descriptionworkflow.DescriptionWorkflowDefinitionTransitionEntity; import org.opencdmp.convention.ConventionService; @@ -24,6 +28,7 @@ import org.opencdmp.model.descriptionworkflow.DescriptionWorkflow; import org.opencdmp.model.persist.descriptionworkflow.DescriptionWorkflowDefinitionPersist; import org.opencdmp.model.persist.descriptionworkflow.DescriptionWorkflowDefinitionTransitionPersist; import org.opencdmp.model.persist.descriptionworkflow.DescriptionWorkflowPersist; +import org.opencdmp.query.DescriptionWorkflowQuery; import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; @@ -48,8 +53,10 @@ public class DescriptionWorkflowServiceImpl implements DescriptionWorkflowServic private final TenantEntityManager entityManager; private final MessageSource messageSource; private final ErrorThesaurusProperties errors; + private final TenantScope tenantScope; + private final QueryFactory queryFactory; - public DescriptionWorkflowServiceImpl(AuthorizationService authService, ConventionService conventionService, XmlHandlingService xmlHandlingService, BuilderFactory builderFactory, DeleterFactory deleterFactory, TenantEntityManager entityManager, MessageSource messageSource, ErrorThesaurusProperties errors) { + public DescriptionWorkflowServiceImpl(AuthorizationService authService, ConventionService conventionService, XmlHandlingService xmlHandlingService, BuilderFactory builderFactory, DeleterFactory deleterFactory, TenantEntityManager entityManager, MessageSource messageSource, ErrorThesaurusProperties errors, TenantScope tenantScope, QueryFactory queryFactory) { this.authService = authService; this.conventionService = conventionService; this.xmlHandlingService = xmlHandlingService; @@ -58,6 +65,8 @@ public class DescriptionWorkflowServiceImpl implements DescriptionWorkflowServic this.entityManager = entityManager; this.messageSource = messageSource; this.errors = errors; + this.tenantScope = tenantScope; + this.queryFactory = queryFactory; } @@ -132,4 +141,22 @@ public class DescriptionWorkflowServiceImpl implements DescriptionWorkflowServic return data; } + + @Override + public DescriptionWorkflowDefinitionEntity getWorkFlowDefinition() throws InvalidApplicationException { + DescriptionWorkflowQuery query = this.queryFactory.query(DescriptionWorkflowQuery.class).authorize(AuthorizationFlags.AllExceptPublic).isActives(IsActive.Active); + + if (this.tenantScope.isDefaultTenant()) + query = query.defaultTenant(true); + else + query = query.tenantIds(this.tenantScope.getTenant()); + + DescriptionWorkflowEntity entity = query.first(); + if (entity == null) throw new MyApplicationException("Description workflow not found!"); + + DescriptionWorkflowDefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(DescriptionWorkflowDefinitionEntity.class, entity.getDefinition()); + if (definition == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{entity.getId(), DescriptionWorkflowDefinitionEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + return definition; + } } diff --git a/backend/core/src/main/java/org/opencdmp/service/plan/PlanServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/plan/PlanServiceImpl.java index 769066c20..d7107a022 100644 --- a/backend/core/src/main/java/org/opencdmp/service/plan/PlanServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/plan/PlanServiceImpl.java @@ -98,6 +98,7 @@ import org.opencdmp.service.accounting.AccountingService; import org.opencdmp.service.actionconfirmation.ActionConfirmationService; import org.opencdmp.service.description.DescriptionService; import org.opencdmp.service.descriptiontemplate.DescriptionTemplateService; +import org.opencdmp.service.descriptionworkflow.DescriptionWorkflowService; import org.opencdmp.service.elastic.ElasticService; import org.opencdmp.service.filetransformer.FileTransformerService; import org.opencdmp.service.planblueprint.PlanBlueprintService; @@ -183,6 +184,7 @@ public class PlanServiceImpl implements PlanService { private final PlanBlueprintService planBlueprintService; private final UsageLimitService usageLimitService; private final AccountingService accountingService; + private final DescriptionWorkflowService descriptionWorkflowService; @Autowired public PlanServiceImpl( @@ -205,7 +207,7 @@ public class PlanServiceImpl implements PlanService { FileTransformerService fileTransformerService, ValidatorFactory validatorFactory, ElasticService elasticService, DescriptionTemplateService descriptionTemplateService, - AnnotationEntityTouchedIntegrationEventHandler annotationEntityTouchedIntegrationEventHandler, AnnotationEntityRemovalIntegrationEventHandler annotationEntityRemovalIntegrationEventHandler, AuthorizationContentResolver authorizationContentResolver, TenantScope tenantScope, ResponseUtilsService responseUtilsService, PlanBlueprintService planBlueprintService, UsageLimitService usageLimitService, AccountingService accountingService) { + AnnotationEntityTouchedIntegrationEventHandler annotationEntityTouchedIntegrationEventHandler, AnnotationEntityRemovalIntegrationEventHandler annotationEntityRemovalIntegrationEventHandler, AuthorizationContentResolver authorizationContentResolver, TenantScope tenantScope, ResponseUtilsService responseUtilsService, PlanBlueprintService planBlueprintService, UsageLimitService usageLimitService, AccountingService accountingService, DescriptionWorkflowService descriptionWorkflowService) { this.entityManager = entityManager; this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; @@ -234,6 +236,7 @@ public class PlanServiceImpl implements PlanService { this.planBlueprintService = planBlueprintService; this.usageLimitService = usageLimitService; this.accountingService = accountingService; + this.descriptionWorkflowService = descriptionWorkflowService; } public Plan persist(PlanPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JAXBException, IOException { @@ -620,6 +623,7 @@ public class PlanServiceImpl implements PlanService { newDescription.setLabel(existing.getLabel()); newDescription.setDescription(existing.getDescription()); newDescription.setStatus(DescriptionStatus.Draft); + newDescription.setStatusId(this.descriptionWorkflowService.getWorkFlowDefinition().getStartingStatusId()); newDescription.setProperties(existing.getProperties()); newDescription.setPlanId(planId); if (newPlanDescriptionTemplateId == null && planDescriptionTemplateRemap != null) newDescription.setPlanDescriptionTemplateId(planDescriptionTemplateRemap.get(existing.getPlanDescriptionTemplateId())); @@ -740,6 +744,7 @@ public class PlanServiceImpl implements PlanService { newDescription.setLabel(existing.getLabel()); newDescription.setDescription(existing.getDescription()); newDescription.setStatus(DescriptionStatus.Draft); + newDescription.setStatusId(this.descriptionWorkflowService.getWorkFlowDefinition().getStartingStatusId()); newDescription.setProperties(existing.getProperties()); newDescription.setPlanId(planId); if (planDescriptionTemplateRemap != null) newDescription.setPlanDescriptionTemplateId(planDescriptionTemplateRemap.get(existing.getPlanDescriptionTemplateId())); @@ -1593,23 +1598,34 @@ public class PlanServiceImpl implements PlanService { List descriptions = this.queryFactory.query(DescriptionQuery.class) .authorize(AuthorizationFlags.AllExceptPublic).planIds(id).isActive(IsActive.Active).collect(); - for (DescriptionEntity description: descriptions) { - if (descriptionIds.contains(description.getId())){ - // description to be finalized - if (description.getStatus().equals(DescriptionStatus.Finalized)){ - throw new MyApplicationException("Description is already finalized"); - } - if (this.descriptionService.validate(List.of(description.getId())).getFirst().getResult().equals(DescriptionValidationOutput.Invalid)){ - throw new MyApplicationException("Description is invalid"); + if (!this.conventionService.isListNullOrEmpty(descriptions)) { + List statusEntities = this.queryFactory.query(DescriptionStatusQuery.class).authorize(AuthorizationFlags.AllExceptPublic).ids(descriptions.stream().map(DescriptionEntity::getStatusId).distinct().toList()).isActive(IsActive.Active).collect(); + + if (this.conventionService.isListNullOrEmpty(statusEntities)) throw new MyApplicationException("Not found description statuses"); + DescriptionStatusEntity finalizedStatusEntity = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().internalStatuses(DescriptionStatus.Finalized).isActive(IsActive.Active).firstAs(new BaseFieldSet().ensure(org.opencdmp.model.descriptionstatus.DescriptionStatus._id)); + if (finalizedStatusEntity == null) throw new MyApplicationException("finalized status not found"); + + DescriptionStatusEntity canceledStatusEntity = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().internalStatuses(DescriptionStatus.Canceled).isActive(IsActive.Active).firstAs(new BaseFieldSet().ensure(org.opencdmp.model.descriptionstatus.DescriptionStatus._id)); + if (canceledStatusEntity == null) throw new MyApplicationException("canceled status not found"); + for (DescriptionEntity description: descriptions) { + DescriptionStatusEntity currentStatusEntity = statusEntities.stream().filter(x -> x.getId().equals(description.getStatusId())).findFirst().orElse(null); + if (descriptionIds.contains(description.getId())){ + // description to be finalized + if (currentStatusEntity != null && currentStatusEntity.getInternalStatus()!= null && currentStatusEntity.getInternalStatus().equals(DescriptionStatus.Finalized)){ + throw new MyApplicationException("Description is already finalized"); + } + if (this.descriptionService.validate(List.of(description.getId())).getFirst().getResult().equals(DescriptionValidationOutput.Invalid)){ + throw new MyApplicationException("Description is invalid"); + } + if (finalizedStatusEntity != null) description.setStatusId(finalizedStatusEntity.getId()); + description.setUpdatedAt(Instant.now()); + description.setFinalizedAt(Instant.now()); + this.entityManager.merge(description); + } else if (currentStatusEntity != null && currentStatusEntity.getInternalStatus()!= null && !currentStatusEntity.getInternalStatus().equals(DescriptionStatus.Finalized)) { + // description to be canceled + description.setStatusId(canceledStatusEntity.getId()); + this.deleterFactory.deleter(DescriptionDeleter.class).delete(List.of(description), true); } - description.setStatus(DescriptionStatus.Finalized); - description.setUpdatedAt(Instant.now()); - description.setFinalizedAt(Instant.now()); - this.entityManager.merge(description); - } else if (description.getStatus().equals(DescriptionStatus.Draft)) { - // description to be canceled - description.setStatus(DescriptionStatus.Canceled); - this.deleterFactory.deleter(DescriptionDeleter.class).delete(List.of(description), true); } } diff --git a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionStatusController.java b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionStatusController.java index 531aaed82..ea4a77cd9 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionStatusController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionStatusController.java @@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.xml.bind.JAXBException; import org.opencdmp.audit.AuditableAction; import org.opencdmp.authorization.AuthorizationFlags; +import org.opencdmp.commons.enums.IsActive; import org.opencdmp.controllers.swagger.SwaggerHelpers; import org.opencdmp.controllers.swagger.annotation.OperationWithTenantHeader; import org.opencdmp.controllers.swagger.annotation.Swagger400; @@ -27,11 +28,15 @@ import org.opencdmp.controllers.swagger.annotation.Swagger404; import org.opencdmp.controllers.swagger.annotation.SwaggerCommonErrorResponses; import org.opencdmp.data.DescriptionStatusEntity; import org.opencdmp.model.builder.descriptionstatus.DescriptionStatusBuilder; +import org.opencdmp.model.builder.descriptionworkflow.DescriptionWorkflowBuilder; import org.opencdmp.model.censorship.descriptionstatus.DescriptionStatusCensor; +import org.opencdmp.model.censorship.descriptionworkflow.DescriptionWorkflowCensor; import org.opencdmp.model.descriptionstatus.DescriptionStatus; +import org.opencdmp.model.descriptionworkflow.DescriptionWorkflow; import org.opencdmp.model.persist.descriptionstatus.DescriptionStatusPersist; import org.opencdmp.model.result.QueryResult; import org.opencdmp.query.DescriptionStatusQuery; +import org.opencdmp.query.DescriptionWorkflowQuery; import org.opencdmp.query.lookup.DescriptionStatusLookup; import org.opencdmp.service.descriptionstatus.DescriptionStatusService; import org.slf4j.LoggerFactory; @@ -183,4 +188,23 @@ public class DescriptionStatusController { this.auditService.track(AuditableAction.DescriptionStatus_Delete, "id", id); } + + @GetMapping("available-transitions/{descriptionId}") + @OperationWithTenantHeader(summary = "Get available status transitions for description", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DescriptionWorkflow.class + )) + )) + @Swagger404 + public List GetAvailableTransitions( + @Parameter(name = "descriptionId", description = "The id of description", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable UUID descriptionId + ) throws InvalidApplicationException { + logger.debug(new MapLogEntry("retrieving available statuses" + DescriptionStatus.class.getSimpleName())); + + List availableTransitionStatuses = this.descriptionStatusService.getAvailableTransitionStatuses(descriptionId); + + this.auditService.track(AuditableAction.DescriptionStatus_Delete, "descriptionId", descriptionId); + return availableTransitionStatuses; + } } diff --git a/frontend/src/app/core/model/description/description.ts b/frontend/src/app/core/model/description/description.ts index dacfd5f27..2ee633917 100644 --- a/frontend/src/app/core/model/description/description.ts +++ b/frontend/src/app/core/model/description/description.ts @@ -7,9 +7,11 @@ import { Reference, ReferencePersist } from "../reference/reference"; import { Tag, TagPersist } from "../tag/tag"; import { User } from "../user/user"; import { AppPermission } from "@app/core/common/enum/permission.enum"; +import { DescriptionStatus } from "../description-status/description-status"; export interface Description extends BaseDescription { label?: string; + status?: DescriptionStatus; properties?: DescriptionPropertyDefinition; description?: string; createdBy?: User; @@ -76,7 +78,7 @@ export interface DescriptionPersist extends BaseEntityPersist { planId: Guid; planDescriptionTemplateId: Guid; descriptionTemplateId: Guid; - status: DescriptionStatusEnum; + statusId: Guid; description: string; properties: DescriptionPropertyDefinitionPersist; tags: string[]; @@ -120,7 +122,7 @@ export interface DescriptionReferencePersist { export interface DescriptionStatusPersist { id: Guid; - status: DescriptionStatusEnum; + statusId?: Guid; hash: string; } @@ -130,7 +132,7 @@ export interface DescriptionStatusPersist { export interface PublicDescription extends BaseDescription { label?: string; - status?: DescriptionStatusEnum; + status?: PublicDescriptionStatus; description?: string; finalizedAt?: Date; descriptionTemplate?: PublicDescriptionTemplate; @@ -148,6 +150,13 @@ export interface PublicDescriptionTemplate { label: string; description: string; } + +export interface PublicDescriptionStatus { + id: Guid; + name: string; + internalStatus: DescriptionStatusEnum; +} + export interface DescriptionSectionPermissionResolver { planId: Guid; sectionIds: Guid[]; @@ -161,5 +170,4 @@ export interface UpdateDescriptionTemplatePersist { export interface BaseDescription extends BaseEntity { tenantId?: Guid; - status?: DescriptionStatusEnum; } diff --git a/frontend/src/app/core/services/description-status/description-status.service.ts b/frontend/src/app/core/services/description-status/description-status.service.ts index 97617d65b..1c3f26282 100644 --- a/frontend/src/app/core/services/description-status/description-status.service.ts +++ b/frontend/src/app/core/services/description-status/description-status.service.ts @@ -56,6 +56,15 @@ export class DescriptionStatusService { catchError((error: any) => throwError(() => error))); } + getAvailableTransitions(descriptionId: Guid, reqFields: string[] = []): Observable> { + const url = `${this.apiBase}/available-transitions/${descriptionId}`; + const options = { params: { f: reqFields } }; + + return this.http + .get>(url, options).pipe( + catchError((error: any) => throwError(() => error))); + } + buildLookup(params: { like?: string, excludedIds?: Guid[], diff --git a/frontend/src/app/ui/dashboard/recent-edited-activity/recent-edited-activity.component.ts b/frontend/src/app/ui/dashboard/recent-edited-activity/recent-edited-activity.component.ts index 36c761abf..1b46896b5 100644 --- a/frontend/src/app/ui/dashboard/recent-edited-activity/recent-edited-activity.component.ts +++ b/frontend/src/app/ui/dashboard/recent-edited-activity/recent-edited-activity.component.ts @@ -28,6 +28,7 @@ import { BehaviorSubject } from 'rxjs'; import { debounceTime, map, takeUntil } from 'rxjs/operators'; import { nameof } from 'ts-simple-nameof'; import { ActivityListingType } from '../dashboard.component'; +import { DescriptionStatus } from '@app/core/model/description-status/description-status'; @Component({ selector: 'app-recent-edited-activity', @@ -232,16 +233,16 @@ export class RecentEditedActivityComponent extends BaseComponent implements OnIn if (item.plan){ if (item.plan.descriptions) { if (item.plan.status == PlanStatusEnum.Finalized) { - item.plan.descriptions = item.plan.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatusEnum.Finalized); + item.plan.descriptions = item.plan.descriptions.filter(x => x.isActive === IsActive.Active && x.status?.internalStatus === DescriptionStatusEnum.Finalized); } else { - item.plan.descriptions = item.plan.descriptions.filter(x => x.isActive === IsActive.Active && x.status != DescriptionStatusEnum.Canceled); + item.plan.descriptions = item.plan.descriptions.filter(x => x.isActive === IsActive.Active && x.status?.internalStatus != DescriptionStatusEnum.Canceled); } } item.plan.planUsers = item.plan.planUsers.filter(x=> x.isActive === IsActive.Active); this.listingItems.push(item); } if (item.description){ - if (item.description.status != DescriptionStatusEnum.Canceled) this.listingItems.push(item); + if (item.description?.status?.internalStatus != DescriptionStatusEnum.Canceled) this.listingItems.push(item); } }) @@ -288,7 +289,9 @@ export class RecentEditedActivityComponent extends BaseComponent implements OnIn [nameof(x => x.plan), nameof(x => x.authorizationFlags), AppPermission.EditPlan].join('.'), [nameof(x => x.plan), nameof(x => x.descriptions), nameof(x => x.id)].join('.'), [nameof(x => x.plan), nameof(x => x.descriptions), nameof(x => x.label)].join('.'), - [nameof(x => x.plan), nameof(x => x.descriptions), nameof(x => x.status)].join('.'), + [nameof(x => x.plan), nameof(x => x.descriptions), nameof(x => x.status), nameof(x => x.id)].join('.'), + [nameof(x => x.plan), nameof(x => x.descriptions), nameof(x => x.status), nameof(x => x.name)].join('.'), + [nameof(x => x.plan), nameof(x => x.descriptions), nameof(x => x.status), nameof(x => x.internalStatus)].join('.'), [nameof(x => x.plan), nameof(x => x.descriptions), nameof(x => x.isActive)].join('.'), [nameof(x => x.plan), nameof(x => x.blueprint), nameof(x => x.id)].join('.'), [nameof(x => x.plan), nameof(x => x.blueprint), nameof(x => x.label)].join('.'), @@ -318,7 +321,9 @@ export class RecentEditedActivityComponent extends BaseComponent implements OnIn return [ [nameof(x => x.description), nameof(x => x.id)].join('.'), [nameof(x => x.description), nameof(x => x.label)].join('.'), - [nameof(x => x.description), nameof(x => x.status)].join('.'), + [nameof(x => x.description), nameof(x => x.status), nameof(x => x.id)].join('.'), + [nameof(x => x.description), nameof(x => x.status), nameof(x => x.name)].join('.'), + [nameof(x => x.description), nameof(x => x.status), nameof(x => x.internalStatus)].join('.'), [nameof(x => x.description), nameof(x => x.updatedAt)].join('.'), [nameof(x => x.description), nameof(x => x.isActive)].join('.'), [nameof(x => x.description), nameof(x => x.authorizationFlags), AppPermission.EditDescription].join('.'), diff --git a/frontend/src/app/ui/description/editor/description-base-fields-editor/description-base-fields-editor.component.ts b/frontend/src/app/ui/description/editor/description-base-fields-editor/description-base-fields-editor.component.ts index 12f7fbd9d..a776f0667 100644 --- a/frontend/src/app/ui/description/editor/description-base-fields-editor/description-base-fields-editor.component.ts +++ b/frontend/src/app/ui/description/editor/description-base-fields-editor/description-base-fields-editor.component.ts @@ -46,7 +46,7 @@ export class DescriptionBaseFieldsEditorComponent extends BaseComponent { if (this.description?.descriptionTemplate != null) { const isPreviousVersion: boolean = this.description.descriptionTemplate.versionStatus === DescriptionTemplateVersionStatus.Previous; if (isPreviousVersion === true) { - if (this.description.status === DescriptionStatusEnum.Draft) { + if (this.description.status?.internalStatus === DescriptionStatusEnum.Draft) { this.openDeprecatedDescriptionTemplateDialog(); } else { this.availableDescriptionTemplates.push(this.description.descriptionTemplate); diff --git a/frontend/src/app/ui/description/editor/description-editor.component.html b/frontend/src/app/ui/description/editor/description-editor.component.html index de23fe349..abe0a7f15 100644 --- a/frontend/src/app/ui/description/editor/description-editor.component.html +++ b/frontend/src/app/ui/description/editor/description-editor.component.html @@ -67,9 +67,12 @@ - + + + + - + diff --git a/frontend/src/app/ui/description/editor/description-editor.component.ts b/frontend/src/app/ui/description/editor/description-editor.component.ts index e7ceefefb..6395ba5d8 100644 --- a/frontend/src/app/ui/description/editor/description-editor.component.ts +++ b/frontend/src/app/ui/description/editor/description-editor.component.ts @@ -45,6 +45,8 @@ import { DescriptionEditorEntityResolver } from './resolvers/description-editor- import { ToCEntry } from './table-of-contents/models/toc-entry'; import { TableOfContentsService } from './table-of-contents/services/table-of-contents-service'; import { TableOfContentsComponent } from './table-of-contents/table-of-contents.component'; +import { DescriptionStatusService } from '@app/core/services/description-status/description-status.service'; +import { DescriptionStatus } from '@app/core/model/description-status/description-status'; @Component({ selector: 'app-description-editor-component', @@ -82,6 +84,9 @@ export class DescriptionEditorComponent extends BaseEditor; + availableStatusesTransitions: DescriptionStatus[]; + oldStatusId: Guid; + constructor( // BaseFormEditor injected dependencies public routerUtils: RouterUtilsService, @@ -111,6 +116,7 @@ export class DescriptionEditorComponent extends BaseEditor { - this.finalize(); + const finalizedStatus = this.availableStatusesTransitions?.find(x => x.internalStatus === DescriptionStatusEnum.Finalized) || null; + if (finalizedStatus) this.finalize(finalizedStatus.id); }, 0); } }); @@ -215,6 +222,10 @@ export class DescriptionEditorComponent extends BaseEditor x.isActive === IsActive.Active); this.item = data; this.initialTemplateId = data?.descriptionTemplate?.id?.toString(); @@ -227,6 +238,13 @@ export class DescriptionEditorComponent extends BaseEditor { + this.availableStatusesTransitions = statuses; + }, + (error) => this.httpErrorHandlingService.handleBackedRequestError(error) + ); } + calculateMultiplicityRejectedPlanDescriptionTemplates(section: PlanBlueprintDefinitionSection, descriptions: Description[]): PlanDescriptionTemplate[] { if (section.descriptionTemplates?.length > 0) { descriptions = descriptions?.filter(x => x?.planDescriptionTemplate?.sectionId === section.id) || []; @@ -302,17 +322,18 @@ export class DescriptionEditorComponent extends BaseEditor void): void { const formData = this.formService.getValue(this.formGroup.value) as DescriptionPersist; + const finalizedStatus = this.availableStatusesTransitions?.find(x => x.internalStatus === DescriptionStatusEnum.Finalized) || null; this.descriptionService.persist(formData) .pipe(takeUntil(this._destroyed)).subscribe( complete => { onSuccess ? onSuccess(complete) : this.onCallbackSuccess(complete); this.descriptionIsOnceSaved = true; - if (this.formGroup.get('status').value == DescriptionStatusEnum.Finalized) this.isFinalized = true; + if (finalizedStatus && this.formGroup.get('statusId').value == finalizedStatus.id) this.isFinalized = true; }, error => { - if (this.formGroup.get('status').value == DescriptionStatusEnum.Finalized) { - this.formGroup.get('status').setValue(DescriptionStatusEnum.Draft); + if (finalizedStatus && this.formGroup.get('statusId').value == finalizedStatus.id) { + this.formGroup.get('statusId').setValue(this.oldStatusId); this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.UNSUCCESSFUL-FINALIZE'), SnackBarNotificationLevel.Error); } else { this.onCallbackError(error); @@ -324,7 +345,7 @@ export class DescriptionEditorComponent extends BaseEditor void): void { this.formService.removeAllBackEndErrors(this.formGroup); if (this.formGroup.get('label').valid && this.formGroup.get('planId').valid && this.formGroup.get('planDescriptionTemplateId').valid - && this.formGroup.get('descriptionTemplateId').valid && this.formGroup.get('status').valid) { + && this.formGroup.get('descriptionTemplateId').valid) {// && this.formGroup.get('statusId').valid) { this.persistEntity(onSuccess); } else { const errorMessages = this._buildSemiFormErrorMessages(); @@ -698,7 +719,19 @@ export class DescriptionEditorComponent extends BaseEditor { if (result) { - this.formGroup.get('status').setValue(DescriptionStatusEnum.Finalized); + this.formGroup.get('statusId').setValue(statusId); this.persistEntity(); } }); } - reverse() { + reverse(statusId: Guid) { const dialogRef = this.dialog.open(ConfirmationDialogComponent, { restoreFocus: false, data: { @@ -747,7 +780,7 @@ export class DescriptionEditorComponent extends BaseEditor x.isActive === IsActive.Active).map(x => x.tag?.label); this.properties = new DescriptionPropertyDefinitionEditorModel(this.validationErrorModel).fromModel(item.properties, descriptionTemplate, item.descriptionReferences); @@ -53,7 +53,7 @@ export class DescriptionEditorModel extends BaseEditorModel implements Descripti planId: [{ value: this.planId, disabled: disabled }, context.getValidation('planId').validators], planDescriptionTemplateId: [{ value: this.planDescriptionTemplateId, disabled: disabled }, context.getValidation('planDescriptionTemplateId').validators], descriptionTemplateId: [{ value: this.descriptionTemplateId, disabled: disabled }, context.getValidation('descriptionTemplateId').validators], - status: [{ value: this.status, disabled: disabled }, context.getValidation('status').validators], + statusId: [{ value: this.statusId, disabled: disabled }, context.getValidation('statusId').validators], description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators], tags: [{ value: this.tags, disabled: disabled }, context.getValidation('tags').validators], properties: this.buildProperties(visibilityRulesService), @@ -76,7 +76,7 @@ export class DescriptionEditorModel extends BaseEditorModel implements Descripti baseValidationArray.push({ key: 'planId', validators: [CustomValidators.required(), BackendErrorValidator(this.validationErrorModel, 'planId')] }); baseValidationArray.push({ key: 'planDescriptionTemplateId', validators: [CustomValidators.required(), BackendErrorValidator(this.validationErrorModel, 'planDescriptionTemplateId')] }); baseValidationArray.push({ key: 'descriptionTemplateId', validators: [CustomValidators.required(), BackendErrorValidator(this.validationErrorModel, 'descriptionTemplateId')] }); - baseValidationArray.push({ key: 'status', validators: [CustomValidators.required(), BackendErrorValidator(this.validationErrorModel, 'status')] }); + baseValidationArray.push({ key: 'statusId', validators: [BackendErrorValidator(this.validationErrorModel, 'statusId')] }); baseValidationArray.push({ key: 'description', validators: [BackendErrorValidator(this.validationErrorModel, 'description')] }); baseValidationArray.push({ key: 'tags', validators: [BackendErrorValidator(this.validationErrorModel, 'tags')] }); baseValidationArray.push({ key: 'hash', validators: [] }); diff --git a/frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts b/frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts index bed8915f2..3debef331 100644 --- a/frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts +++ b/frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts @@ -1,8 +1,8 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; -import { DescriptionStatusEnum } from '@app/core/common/enum/description-status'; import { IsActive } from '@app/core/common/enum/is-active.enum'; import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { DescriptionStatus } from '@app/core/model/description-status/description-status'; import { DescriptionTemplate } from '@app/core/model/description-template/description-template'; import { Description, DescriptionExternalIdentifier, DescriptionField, DescriptionPropertyDefinition, DescriptionPropertyDefinitionFieldSet, DescriptionPropertyDefinitionFieldSetItem, DescriptionReference, DescriptionReferenceData, DescriptionTag } from '@app/core/model/description/description'; import { DescriptionTemplatesInSection, PlanBlueprint, PlanBlueprintDefinition, PlanBlueprintDefinitionSection } from '@app/core/model/plan-blueprint/plan-blueprint'; @@ -52,9 +52,10 @@ export class DescriptionEditorEntityResolver extends BaseEditorResolver { ...BaseEditorResolver.lookupFields(), nameof(x => x.id), nameof(x => x.label), - nameof(x => x.status), nameof(x => x.description), - nameof(x => x.status), + [nameof(x => x.status), nameof(x => x.id)].join('.'), + [nameof(x => x.status), nameof(x => x.name)].join('.'), + [nameof(x => x.status), nameof(x => x.internalStatus)].join('.'), [nameof(x => x.authorizationFlags), AppPermission.EditDescription].join('.'), [nameof(x => x.authorizationFlags), AppPermission.DeleteDescription].join('.'), @@ -203,7 +204,7 @@ export class DescriptionEditorEntityResolver extends BaseEditorResolver { description.hash = null; description.isActive = IsActive.Active; description.belongsToCurrentTenant = true; - description.status = DescriptionStatusEnum.Draft; + description.status = null; description.plan = plan; description.planDescriptionTemplate = { id: plan.planDescriptionTemplates.filter(x => x.sectionId == Guid.parse(planSectionId) && x.descriptionTemplateGroupId == description.descriptionTemplate.groupId)[0].id, diff --git a/frontend/src/app/ui/description/listing/description-listing.component.ts b/frontend/src/app/ui/description/listing/description-listing.component.ts index 4547ba18a..befdde935 100644 --- a/frontend/src/app/ui/description/listing/description-listing.component.ts +++ b/frontend/src/app/ui/description/listing/description-listing.component.ts @@ -43,6 +43,7 @@ import { InterceptorType } from '@common/http/interceptors/interceptor-type'; import { PrincipalService } from '@app/core/services/http/principal.service'; import { DescriptionListingFilters } from './filtering/description-filter.component'; import { MatSelectChange } from '@angular/material/select'; +import { DescriptionStatus } from '@app/core/model/description-status/description-status'; @Component({ selector: 'app-description-listing-component', @@ -490,7 +491,9 @@ export class DescriptionListingComponent extends BaseListingComponent(x => x.id), nameof(x => x.tenantId), nameof(x => x.label), - nameof(x => x.status), + [nameof(x => x.status), nameof(x => x.id)].join('.'), + [nameof(x => x.status), nameof(x => x.name)].join('.'), + [nameof(x => x.status), nameof(x => x.internalStatus)].join('.'), nameof(x => x.updatedAt), nameof(x => x.belongsToCurrentTenant), nameof(x => x.finalizedAt), diff --git a/frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.html b/frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.html index dbf954c53..06d4aeabb 100644 --- a/frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.html +++ b/frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.html @@ -13,15 +13,18 @@ -
{{description.label}}
-
{{description.label}}
+
{{description.label}}
+
{{description.label}}
+
{{description.label}}
+
{{description.label}}
{{ enumUtils.toPlanUserRolesString(planService.getCurrentUserRolesInPlan(description?.plan?.planUsers)) }} . - public{{'DESCRIPTION-LISTING.STATES.PUBLIC' | translate}} - done{{ enumUtils.toDescriptionStatusString(description.status) }} - create{{ enumUtils.toDescriptionStatusString(description.status) }} - visibility{{ enumUtils.toDescriptionStatusString(description.status) }} + public{{'DESCRIPTION-LISTING.STATES.PUBLIC' | translate}} + done{{ description.status.name }} + create{{ description.status.name }} + visibility{{ description.status.name }} + {{ description.status.name }} . {{'DESCRIPTION-LISTING.GRANT' | translate}}: {{referenceService.getReferencesForTypesFirstSafe(description?.plan?.planReferences, [this.referenceTypeService.getGrantReferenceType()])?.reference?.label}}
diff --git a/frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.ts b/frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.ts index 50851de5d..0b71f7c9a 100644 --- a/frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.ts +++ b/frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.ts @@ -82,7 +82,7 @@ export class DescriptionListingItemComponent extends BaseComponent implements On this.analyticsService.trackPageView(AnalyticsService.DescriptionListingItem); if (this.description.isActive === IsActive.Inactive) { this.isDeleted = true; - } else if (this.description.status === DescriptionStatusEnum.Draft) { + } else if (this.description?.status?.internalStatus === DescriptionStatusEnum.Draft) { this.isDraft = true; this.isDeleted = false; } else { diff --git a/frontend/src/app/ui/description/overview/description-overview.component.html b/frontend/src/app/ui/description/overview/description-overview.component.html index 211c6e34f..e32a8b52e 100644 --- a/frontend/src/app/ui/description/overview/description-overview.component.html +++ b/frontend/src/app/ui/description/overview/description-overview.component.html @@ -46,7 +46,7 @@
- @if(isActive && (canEdit || canAnnotate) && isDraftDescription(description) && !isLocked){ + @if(isActive && (canEdit || canAnnotate) && isNotFinalizedDescription(description) && !isLocked){ @@ -134,32 +134,25 @@
- -
-
- + +
+
+
+ + +
+
+

{{ status.name }}

+
-
-

{{ 'DESCRIPTION-OVERVIEW.ACTIONS.FINALIZE' | translate }}

-
-
-
-
-
-
-
-
- -
-
- -
-
-

{{ 'DESCRIPTION-OVERVIEW.ACTIONS.REVERSE' | translate }}

+
+
+
+
diff --git a/frontend/src/app/ui/description/overview/description-overview.component.ts b/frontend/src/app/ui/description/overview/description-overview.component.ts index 3199a5b2c..4955accbb 100644 --- a/frontend/src/app/ui/description/overview/description-overview.component.ts +++ b/frontend/src/app/ui/description/overview/description-overview.component.ts @@ -44,6 +44,8 @@ import { map, takeUntil } from 'rxjs/operators'; import { nameof } from 'ts-simple-nameof'; import { DescriptionCopyDialogComponent } from '../description-copy-dialog/description-copy-dialog.component'; import { RouterUtilsService } from '@app/core/services/router/router-utils.service'; +import { DescriptionStatus } from '@app/core/model/description-status/description-status'; +import { DescriptionStatusService } from '@app/core/services/description-status/description-status.service'; @Component({ @@ -73,6 +75,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni canFinalize = false; canAnnotate = false; canInvitePlanUsers = false; + availableStatusesTransitions: DescriptionStatus[]; canAssignPlanUsers(): boolean { const authorizationFlags = !this.isPublicView ? (this.description?.plan as Plan)?.authorizationFlags : []; return (authorizationFlags?.some(x => x === AppPermission.AssignPlanUsers) || this.authentication.hasPermission(AppPermission.AssignPlanUsers)) && @@ -106,6 +109,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni private breadcrumbService: BreadcrumbService, private httpErrorHandlingService: HttpErrorHandlingService, private userService: UserService, + private descriptionStatusService: DescriptionStatusService ) { super(); } @@ -135,6 +139,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni this.breadcrumbService.addIdResolvedValue(data.id.toString(), data.label); this.description = data; + this.getAvailableStatuses(this.description.id); this.description.plan.planUsers = this.isActive ? data.plan.planUsers.filter(x => x.isActive === IsActive.Active) : data.plan.planUsers; this.researchers = this.referenceService.getReferencesForTypes(this.description?.plan?.planReferences, [this.referenceTypeService.getResearcherReferenceType()]); this.checkLockStatus(this.description.id); @@ -216,6 +221,19 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni return this.language.instant('DESCRIPTION-OVERVIEW.INFOS.UNAUTHORIZED-ORCID'); } + getAvailableStatuses(id: Guid){ + this.descriptionStatusService.getAvailableTransitions(id).pipe(takeUntil(this._destroyed)) + .subscribe( + (statuses) => { + this.availableStatusesTransitions = statuses; + }, + (error) => this.httpErrorHandlingService.handleBackedRequestError(error) + ); } + + hasAvailableFinalizeStatus() { + return this.availableStatusesTransitions?.find(x => x.internalStatus === DescriptionStatusEnum.Finalized) != null; + } + checkLockStatus(id: Guid) { this.lockService.checkLockStatus(id).pipe(takeUntil(this._destroyed)) .subscribe({ @@ -290,8 +308,8 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni return this.authentication.currentAccountIsAuthenticated(); } - isDraftDescription(description: Description) { - return description.status == DescriptionStatusEnum.Draft; + isNotFinalizedDescription(description: Description) { + return description?.status?.internalStatus != DescriptionStatusEnum.Finalized; } editClicked(description: Description) { @@ -426,7 +444,30 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni }); } - finalize(description: Description) { + persistStatus(status: DescriptionStatus, description: Description) { + if (status.internalStatus != null && status.internalStatus === DescriptionStatusEnum.Finalized) { + this.finalize(description, status.id); + } else if (status.internalStatus != null && description.status.internalStatus === DescriptionStatusEnum.Finalized){ + this.reverseFinalization(description, status.id); + } else { + // other statuses + const descriptionStatusPersist: DescriptionStatusPersist = { + id: description.id, + statusId: status.id, + hash: description.hash + }; + this.descriptionService.persistStatus(descriptionStatusPersist).pipe(takeUntil(this._destroyed)) + .subscribe({ + next: () => { + this.reloadPage(); + this.onUpdateCallbackSuccess() + }, + error: (error: any) => this.onUpdateCallbackError(error) + }) + } + } + + finalize(description: Description, statusId: Guid) { this.descriptionService.validate([description.id]).pipe(takeUntil(this._destroyed)) .subscribe({ @@ -448,7 +489,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni if (result) { const descriptionStatusPersist: DescriptionStatusPersist = { id: description.id, - status: DescriptionStatusEnum.Finalized, + statusId: statusId, hash: description.hash }; this.descriptionService.persistStatus(descriptionStatusPersist).pipe(takeUntil(this._destroyed)) @@ -472,10 +513,10 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni } hasReversableStatus(description: Description): boolean { - return description.plan.status == PlanStatusEnum.Draft && description.status == DescriptionStatusEnum.Finalized && this.canFinalize + return description.plan.status == PlanStatusEnum.Draft && description?.status?.internalStatus == DescriptionStatusEnum.Finalized && this.canFinalize && this.availableStatusesTransitions?.find(x => x.internalStatus === DescriptionStatusEnum.Draft) != null } - reverseFinalization(description: Description) { + reverseFinalization(description: Description, statusId: Guid) { const dialogRef = this.dialog.open(ConfirmationDialogComponent, { restoreFocus: false, data: { @@ -489,7 +530,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni if (result) { const planUserRemovePersist: DescriptionStatusPersist = { id: description.id, - status: DescriptionStatusEnum.Draft, + statusId: statusId, hash: description.hash }; this.descriptionService.persistStatus(planUserRemovePersist).pipe(takeUntil(this._destroyed)) @@ -512,7 +553,9 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni nameof(x => x.id), nameof(x => x.label), nameof(x => x.description), - nameof(x => x.status), + [nameof(x => x.status), nameof(x => x.id)].join('.'), + [nameof(x => x.status), nameof(x => x.name)].join('.'), + [nameof(x => x.status), nameof(x => x.internalStatus)].join('.'), nameof(x => x.updatedAt), nameof(x => x.belongsToCurrentTenant), nameof(x => x.hash), diff --git a/frontend/src/app/ui/plan/listing/plan-listing.component.ts b/frontend/src/app/ui/plan/listing/plan-listing.component.ts index 853283cb8..4542aeea3 100644 --- a/frontend/src/app/ui/plan/listing/plan-listing.component.ts +++ b/frontend/src/app/ui/plan/listing/plan-listing.component.ts @@ -44,6 +44,7 @@ import { PlanFilterDialogComponent } from './filtering/plan-filter-dialog/plan-f import { PlanListingFilters } from './filtering/plan-filter.component'; import { Lookup } from '@common/model/lookup'; import { MatSelectChange } from '@angular/material/select'; +import { DescriptionStatus } from '@app/core/model/description-status/description-status'; @Component({ selector: 'app-plan-listing-component', @@ -513,10 +514,11 @@ export class PlanListingComponent extends BaseListingComponent(x => x.descriptions), nameof(x => x.id)].join('.'), [nameof(x => x.descriptions), nameof(x => x.label)].join('.'), - [nameof(x => x.descriptions), nameof(x => x.status)].join('.'), - [nameof(x => x.descriptions), nameof(x => x.descriptionTemplate), nameof(x => x.groupId)].join('.'), - [nameof(x => x.descriptions), nameof(x => x.planDescriptionTemplate), nameof(x => x.sectionId)].join('.'), - [nameof(x => x.descriptions), nameof(x => x.isActive)].join('.'), + [nameof(x => x.descriptions), nameof(x => x.status), nameof(x => x.id)].join('.'), + [nameof(x => x.descriptions), nameof(x => x.status), nameof(x => x.name)].join('.'), + [nameof(x => x.descriptions), nameof(x => x.status), nameof(x => x.internalStatus)].join('.'), [nameof(x => x.descriptions), nameof(x => x.descriptionTemplate), nameof(x => x.groupId)].join('.'), + [nameof(x => x.descriptions), nameof(x => x.descriptions), nameof(x => x.planDescriptionTemplate), nameof(x => x.sectionId)].join('.'), + [nameof(x => x.descriptions), nameof(x => x.descriptions), nameof(x => x.isActive)].join('.'), [nameof(x => x.blueprint), nameof(x => x.id)].join('.'), [nameof(x => x.blueprint), nameof(x => x.label)].join('.'), diff --git a/frontend/src/app/ui/plan/overview/plan-overview.component.ts b/frontend/src/app/ui/plan/overview/plan-overview.component.ts index 9d7cb8fab..d2288a3ab 100644 --- a/frontend/src/app/ui/plan/overview/plan-overview.component.ts +++ b/frontend/src/app/ui/plan/overview/plan-overview.component.ts @@ -50,6 +50,7 @@ import { PlanFinalizeDialogComponent, PlanFinalizeDialogOutput } from '../plan-f import { PlanInvitationDialogComponent } from '../invitation/dialog/plan-invitation-dialog.component'; import { NewVersionPlanDialogComponent } from '../new-version-dialog/plan-new-version-dialog.component'; import { RouterUtilsService } from '@app/core/services/router/router-utils.service'; +import { DescriptionStatus } from '@app/core/model/description-status/description-status'; @Component({ selector: 'app-plan-overview', @@ -133,9 +134,9 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { this.plan.otherPlanVersions = data.otherPlanVersions?.filter(x => x.isActive === IsActive.Active) || null; if (this.plan.descriptions) { if (this.plan.status == PlanStatusEnum.Finalized) { - this.plan.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatusEnum.Finalized); + this.plan.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status?.internalStatus === DescriptionStatusEnum.Finalized); } else { - this.plan.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status !== DescriptionStatusEnum.Canceled); + this.plan.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status?.internalStatus !== DescriptionStatusEnum.Canceled); } } if (data.entityDois && data.entityDois.length > 0) this.plan.entityDois = data.entityDois.filter(x => x.isActive === IsActive.Active); @@ -660,7 +661,9 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { [nameof(x => x.entityDois), nameof(x => x.isActive)].join('.'), [nameof(x => x.descriptions), nameof(x => x.id)].join('.'), [nameof(x => x.descriptions), nameof(x => x.label)].join('.'), - [nameof(x => x.descriptions), nameof(x => x.status)].join('.'), + [nameof(x => x.descriptions), nameof(x => x.status), nameof(x => x.id)].join('.'), + [nameof(x => x.descriptions), nameof(x => x.status), nameof(x => x.name)].join('.'), + [nameof(x => x.descriptions), nameof(x => x.status), nameof(x => x.internalStatus)].join('.'), [nameof(x => x.descriptions), nameof(x => x.isActive)].join('.'), [nameof(x => x.planUsers), nameof(x => x.id)].join('.'), [nameof(x => x.planUsers), nameof(x => x.sectionId)].join('.'), diff --git a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.ts b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.ts index 6e5f31059..6fd461922 100644 --- a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.ts +++ b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.ts @@ -263,9 +263,9 @@ export class PlanEditorComponent extends BaseEditor imple if (data) { if (data.descriptions) { if (data.status == PlanStatusEnum.Finalized) { - data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatusEnum.Finalized); + data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status.internalStatus === DescriptionStatusEnum.Finalized); } else { - data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status !== DescriptionStatusEnum.Canceled); + data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status.internalStatus !== DescriptionStatusEnum.Canceled); } } if (data.planDescriptionTemplates) { diff --git a/frontend/src/app/ui/plan/plan-editor-blueprint/resolvers/plan-editor-enitity.resolver.ts b/frontend/src/app/ui/plan/plan-editor-blueprint/resolvers/plan-editor-enitity.resolver.ts index 8a88dc588..ea915d420 100644 --- a/frontend/src/app/ui/plan/plan-editor-blueprint/resolvers/plan-editor-enitity.resolver.ts +++ b/frontend/src/app/ui/plan/plan-editor-blueprint/resolvers/plan-editor-enitity.resolver.ts @@ -15,6 +15,7 @@ import { Guid } from '@common/types/guid'; import { takeUntil, tap } from 'rxjs/operators'; import { nameof } from 'ts-simple-nameof'; import { EntityDoi } from '@app/core/model/entity-doi/entity-doi'; +import { DescriptionStatus } from '@app/core/model/description-status/description-status'; @Injectable() export class PlanEditorEntityResolver extends BaseEditorResolver { @@ -56,7 +57,9 @@ export class PlanEditorEntityResolver extends BaseEditorResolver { [nameof(x => x.descriptions), nameof(x => x.id)].join('.'), [nameof(x => x.descriptions), nameof(x => x.label)].join('.'), - [nameof(x => x.descriptions), nameof(x => x.status)].join('.'), + [nameof(x => x.descriptions), nameof(x => x.status), nameof(x => x.id)].join('.'), + [nameof(x => x.descriptions), nameof(x => x.status), nameof(x => x.name)].join('.'), + [nameof(x => x.descriptions), nameof(x => x.status), nameof(x => x.internalStatus)].join('.'), [nameof(x => x.descriptions), nameof(x => x.isActive)].join('.'), [nameof(x => x.descriptions), nameof(x => x.planDescriptionTemplate), nameof(x => x.id)].join('.'), [nameof(x => x.descriptions), nameof(x => x.planDescriptionTemplate), nameof(x => x.sectionId)].join('.'), diff --git a/frontend/src/app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component.html b/frontend/src/app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component.html index 606677774..688abe730 100644 --- a/frontend/src/app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component.html +++ b/frontend/src/app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component.html @@ -30,10 +30,10 @@
- bookmark - bookmark -

- {{ 'TYPES.DESCRIPTION-STATUS.DRAFT' | translate }} + bookmark + bookmark +

+ {{ description?.status?.name}} ({{'PLAN-FINALISE-DIALOG.INVALID' | translate}}) @@ -41,7 +41,7 @@ {{ description.label }}

-

{{ description.label }}

+

{{ description.label }}

{{ 'PLAN-FINALISE-DIALOG.EMPTY' | translate }}
diff --git a/frontend/src/app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component.ts b/frontend/src/app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component.ts index e9de2c915..e60bf9285 100644 --- a/frontend/src/app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component.ts +++ b/frontend/src/app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component.ts @@ -74,7 +74,7 @@ export class PlanFinalizeDialogComponent extends BaseComponent implements OnInit getFinalizedDescriptions() { if (!this.plan.descriptions) return []; - const finalizedDescriptions = this.plan.descriptions.filter(x => x.status === DescriptionStatusEnum.Finalized); + const finalizedDescriptions = this.plan.descriptions.filter(x => x.status.internalStatus === DescriptionStatusEnum.Finalized); if (finalizedDescriptions?.length > 0){ finalizedDescriptions.forEach(finalize => { this.descriptionValidationOutputMap.set(finalize.id, DescriptionValidationOutput.Valid); @@ -88,16 +88,16 @@ export class PlanFinalizeDialogComponent extends BaseComponent implements OnInit } validateDescriptions(plan: Plan) { - if (!plan.descriptions?.some(x => x.status == DescriptionStatusEnum.Draft)) return; + if (!plan.descriptions?.some(x => x.status.internalStatus == DescriptionStatusEnum.Draft)) return; - const draftDescriptions = this.plan.descriptions.filter(x => x.status == DescriptionStatusEnum.Draft) || []; + const draftDescriptions = this.plan.descriptions.filter(x => x.status.internalStatus == DescriptionStatusEnum.Draft) || []; if ( draftDescriptions.length > 0){ draftDescriptions.forEach(draft => { this.descriptionValidationOutputMap.set(draft.id, DescriptionValidationOutput.Pending); }); } - this.descriptionService.validate(plan.descriptions.filter(x => x.status == DescriptionStatusEnum.Draft).map(x => x.id)).pipe(takeUntil(this._destroyed),) + this.descriptionService.validate(plan.descriptions.filter(x => x.status.internalStatus == DescriptionStatusEnum.Draft).map(x => x.id)).pipe(takeUntil(this._destroyed),) .subscribe(result => { this.validationResults = result; },