diff --git a/backend/core/pom.xml b/backend/core/pom.xml index 754a12321..6f168c8a7 100644 --- a/backend/core/pom.xml +++ b/backend/core/pom.xml @@ -52,17 +52,17 @@ gr.cite oidc-authz - 2.1.0 + 2.2.0 org.opencdmp repositorydepositbase - 2.0.16 + 2.0.17 org.opencdmp common-models - 0.0.19 + 0.0.20 gr.cite @@ -72,7 +72,7 @@ org.opencdmp file-transformer-base - 0.0.24 + 0.0.25 gr.cite diff --git a/backend/core/src/main/java/org/opencdmp/audit/AuditableAction.java b/backend/core/src/main/java/org/opencdmp/audit/AuditableAction.java index 73325a616..c8d18b37d 100644 --- a/backend/core/src/main/java/org/opencdmp/audit/AuditableAction.java +++ b/backend/core/src/main/java/org/opencdmp/audit/AuditableAction.java @@ -50,6 +50,7 @@ public class AuditableAction { public static final EventId Plan_GetPublicXml = new EventId(5017, "Plan_GetPublicXml"); public static final EventId Plan_ExportPublic = new EventId(5018, "Plan_ExportPublic"); public static final EventId Plan_PublicClone = new EventId(5019, "Plan_PublicClone"); + public static final EventId Plan_SetStatus = new EventId(5020, "Plan_SetStatus"); public static final EventId Description_Query = new EventId(6000, "Description_Query"); diff --git a/backend/core/src/main/java/org/opencdmp/commons/enums/DescriptionStatusAvailableActionType.java b/backend/core/src/main/java/org/opencdmp/commons/enums/DescriptionStatusAvailableActionType.java new file mode 100644 index 000000000..a1b37cc66 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/commons/enums/DescriptionStatusAvailableActionType.java @@ -0,0 +1,30 @@ +package org.opencdmp.commons.enums; + +import com.fasterxml.jackson.annotation.JsonValue; +import org.opencdmp.data.converters.enums.DatabaseEnum; + +import java.util.Map; + +public enum DescriptionStatusAvailableActionType implements DatabaseEnum { + + Export((short) 0); + + private final Short value; + + DescriptionStatusAvailableActionType(Short value) { + this.value = value; + } + + @Override + @JsonValue + public Short getValue() { + return this.value; + } + + private static final Map map = EnumUtils.getEnumValueMap(DescriptionStatusAvailableActionType.class); + + public static DescriptionStatusAvailableActionType of(Short i) { + return map.get(i); + } + +} diff --git a/backend/core/src/main/java/org/opencdmp/commons/enums/PlanStatusAvailableActionType.java b/backend/core/src/main/java/org/opencdmp/commons/enums/PlanStatusAvailableActionType.java new file mode 100644 index 000000000..5dd7d7aba --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/commons/enums/PlanStatusAvailableActionType.java @@ -0,0 +1,31 @@ +package org.opencdmp.commons.enums; + +import com.fasterxml.jackson.annotation.JsonValue; +import org.opencdmp.data.converters.enums.DatabaseEnum; + +import java.util.Map; + +public enum PlanStatusAvailableActionType implements DatabaseEnum { + + Deposit((short) 0), + Export((short) 1); + + private final Short value; + + PlanStatusAvailableActionType(Short value) { + this.value = value; + } + + @Override + @JsonValue + public Short getValue() { + return this.value; + } + + private static final Map map = EnumUtils.getEnumValueMap(PlanStatusAvailableActionType.class); + + public static PlanStatusAvailableActionType of(Short i) { + return map.get(i); + } + +} diff --git a/backend/core/src/main/java/org/opencdmp/commons/notification/NotificationProperties.java b/backend/core/src/main/java/org/opencdmp/commons/notification/NotificationProperties.java index 8df77a337..c5e549c9a 100644 --- a/backend/core/src/main/java/org/opencdmp/commons/notification/NotificationProperties.java +++ b/backend/core/src/main/java/org/opencdmp/commons/notification/NotificationProperties.java @@ -11,11 +11,13 @@ public class NotificationProperties { private UUID planInvitationExistingUserType; private UUID planModifiedType; private UUID planFinalisedType; + private UUID planStatusChangedType; private UUID planAnnotationCreatedType; private UUID planAnnotationStatusChangedType; private UUID descriptionCreatedType; private UUID descriptionModifiedType; private UUID descriptionFinalisedType; + private UUID descriptionStatusChangedType; private UUID descriptionAnnotationCreatedType; private UUID descriptionAnnotationStatusChangedType; private UUID mergeAccountConfirmationType; @@ -61,6 +63,14 @@ public class NotificationProperties { this.planFinalisedType = planFinalisedType; } + public UUID getPlanStatusChangedType() { + return planStatusChangedType; + } + + public void setPlanStatusChangedType(UUID planStatusChangedType) { + this.planStatusChangedType = planStatusChangedType; + } + public UUID getPlanAnnotationCreatedType() { return planAnnotationCreatedType; } @@ -101,6 +111,14 @@ public class NotificationProperties { this.descriptionFinalisedType = descriptionFinalisedType; } + public UUID getDescriptionStatusChangedType() { + return descriptionStatusChangedType; + } + + public void setDescriptionStatusChangedType(UUID descriptionStatusChangedType) { + this.descriptionStatusChangedType = descriptionStatusChangedType; + } + public UUID getMergeAccountConfirmationType() { return this.mergeAccountConfirmationType; } diff --git a/backend/core/src/main/java/org/opencdmp/commons/types/dashborad/RecentActivityItemEntity.java b/backend/core/src/main/java/org/opencdmp/commons/types/dashborad/RecentActivityItemEntity.java index 8e7b23592..5fd70fdd4 100644 --- a/backend/core/src/main/java/org/opencdmp/commons/types/dashborad/RecentActivityItemEntity.java +++ b/backend/core/src/main/java/org/opencdmp/commons/types/dashborad/RecentActivityItemEntity.java @@ -10,14 +10,14 @@ public class RecentActivityItemEntity { private UUID id; private Instant updatedAt; private String label; - private Short statusValue; + private UUID statusId; - public RecentActivityItemEntity(RecentActivityItemType type, UUID id, Instant updatedAt, String label, Short statusValue) { + public RecentActivityItemEntity(RecentActivityItemType type, UUID id, Instant updatedAt, String label, UUID statusId) { this.type = type; this.id = id; this.updatedAt = updatedAt; this.label = label; - this.statusValue = statusValue; + this.statusId = statusId; } public RecentActivityItemType getType() { @@ -52,11 +52,11 @@ public class RecentActivityItemEntity { this.label = label; } - public Short getStatusValue() { - return statusValue; + public UUID getStatusId() { + return statusId; } - public void setStatusValue(Short statusValue) { - this.statusValue = statusValue; + public void setStatusId(UUID statusId) { + this.statusId = statusId; } } diff --git a/backend/core/src/main/java/org/opencdmp/commons/types/description/importexport/DescriptionImportExport.java b/backend/core/src/main/java/org/opencdmp/commons/types/description/importexport/DescriptionImportExport.java index 8da1d9d6c..7bf4ee965 100644 --- a/backend/core/src/main/java/org/opencdmp/commons/types/description/importexport/DescriptionImportExport.java +++ b/backend/core/src/main/java/org/opencdmp/commons/types/description/importexport/DescriptionImportExport.java @@ -22,6 +22,9 @@ public class DescriptionImportExport { @XmlElement(name = "label") private String label; + @XmlElement(name = "status") + private DescriptionStatusImportExport status; + @XmlElement(name = "finalizedAt") @XmlJavaTypeAdapter(InstantXmlAdapter.class) private Instant finalizedAt; @@ -67,6 +70,14 @@ public class DescriptionImportExport { this.label = label; } + public DescriptionStatusImportExport getStatus() { + return status; + } + + public void setStatus(DescriptionStatusImportExport status) { + this.status = status; + } + public Instant getFinalizedAt() { return this.finalizedAt; } diff --git a/backend/core/src/main/java/org/opencdmp/commons/types/description/importexport/DescriptionStatusImportExport.java b/backend/core/src/main/java/org/opencdmp/commons/types/description/importexport/DescriptionStatusImportExport.java new file mode 100644 index 000000000..00d4398c9 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/commons/types/description/importexport/DescriptionStatusImportExport.java @@ -0,0 +1,26 @@ +package org.opencdmp.commons.types.description.importexport; + +import java.util.UUID; + +public class DescriptionStatusImportExport { + + private UUID id; + + private String name; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/commons/types/descriptionstatus/DescriptionStatusDefinitionEntity.java b/backend/core/src/main/java/org/opencdmp/commons/types/descriptionstatus/DescriptionStatusDefinitionEntity.java index f7a4a4f84..e2a5d4d0a 100644 --- a/backend/core/src/main/java/org/opencdmp/commons/types/descriptionstatus/DescriptionStatusDefinitionEntity.java +++ b/backend/core/src/main/java/org/opencdmp/commons/types/descriptionstatus/DescriptionStatusDefinitionEntity.java @@ -1,9 +1,9 @@ package org.opencdmp.commons.types.descriptionstatus; -import jakarta.xml.bind.annotation.XmlAccessType; -import jakarta.xml.bind.annotation.XmlAccessorType; -import jakarta.xml.bind.annotation.XmlElement; -import jakarta.xml.bind.annotation.XmlRootElement; +import jakarta.xml.bind.annotation.*; +import org.opencdmp.commons.enums.DescriptionStatusAvailableActionType; + +import java.util.List; @XmlRootElement(name = "definition") @XmlAccessorType(XmlAccessType.FIELD) @@ -12,7 +12,19 @@ public class DescriptionStatusDefinitionEntity { @XmlElement(name = "authorization") private DescriptionStatusDefinitionAuthorizationEntity authorization; + @XmlElementWrapper(name = "availableActions") + @XmlElement(name = "action") + private List availableActions; + public DescriptionStatusDefinitionAuthorizationEntity getAuthorization() { return this.authorization; } public void setAuthorization(DescriptionStatusDefinitionAuthorizationEntity authorization) { this.authorization = authorization; } + + public List getAvailableActions() { + return availableActions; + } + + public void setAvailableActions(List availableActions) { + this.availableActions = availableActions; + } } diff --git a/backend/core/src/main/java/org/opencdmp/commons/types/plan/importexport/PlanImportExport.java b/backend/core/src/main/java/org/opencdmp/commons/types/plan/importexport/PlanImportExport.java index 13eccf64b..1b0fdf210 100644 --- a/backend/core/src/main/java/org/opencdmp/commons/types/plan/importexport/PlanImportExport.java +++ b/backend/core/src/main/java/org/opencdmp/commons/types/plan/importexport/PlanImportExport.java @@ -30,6 +30,9 @@ public class PlanImportExport { @XmlElement(name = "access") private PlanAccessType access; + @XmlElement(name = "status") + private PlanStatusImportExport status; + @XmlElement(name = "version") private Short version; @@ -109,6 +112,14 @@ public class PlanImportExport { this.access = access; } + public PlanStatusImportExport getStatus() { + return status; + } + + public void setStatus(PlanStatusImportExport status) { + this.status = status; + } + public List getContacts() { return this.contacts; } diff --git a/backend/core/src/main/java/org/opencdmp/commons/types/plan/importexport/PlanStatusImportExport.java b/backend/core/src/main/java/org/opencdmp/commons/types/plan/importexport/PlanStatusImportExport.java new file mode 100644 index 000000000..0c7f06a9e --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/commons/types/plan/importexport/PlanStatusImportExport.java @@ -0,0 +1,26 @@ +package org.opencdmp.commons.types.plan.importexport; + +import java.util.UUID; + +public class PlanStatusImportExport { + + private UUID id; + + private String name; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/commons/types/planstatus/PlanStatusDefinitionEntity.java b/backend/core/src/main/java/org/opencdmp/commons/types/planstatus/PlanStatusDefinitionEntity.java index 1d8e1d171..6892612b6 100644 --- a/backend/core/src/main/java/org/opencdmp/commons/types/planstatus/PlanStatusDefinitionEntity.java +++ b/backend/core/src/main/java/org/opencdmp/commons/types/planstatus/PlanStatusDefinitionEntity.java @@ -1,6 +1,9 @@ package org.opencdmp.commons.types.planstatus; import jakarta.xml.bind.annotation.*; +import org.opencdmp.commons.enums.PlanStatusAvailableActionType; + +import java.util.List; @XmlRootElement(name = "definition") @XmlAccessorType(XmlAccessType.FIELD) @@ -9,9 +12,21 @@ public class PlanStatusDefinitionEntity { @XmlElement(name = "authorization") private PlanStatusDefinitionAuthorizationEntity authorization; + @XmlElementWrapper(name = "availableActions") + @XmlElement(name = "action") + private List availableActions; + public PlanStatusDefinitionAuthorizationEntity getAuthorization() { return this.authorization; } public void setAuthorization(PlanStatusDefinitionAuthorizationEntity authorization) { this.authorization = authorization; } + + public List getAvailableActions() { + return availableActions; + } + + public void setAvailableActions(List availableActions) { + this.availableActions = availableActions; + } } 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/data/DescriptionStatusEntity.java b/backend/core/src/main/java/org/opencdmp/data/DescriptionStatusEntity.java index 290500fce..14aad3de9 100644 --- a/backend/core/src/main/java/org/opencdmp/data/DescriptionStatusEntity.java +++ b/backend/core/src/main/java/org/opencdmp/data/DescriptionStatusEntity.java @@ -29,6 +29,11 @@ public class DescriptionStatusEntity extends TenantScopedBaseEntity { private String description; public static final String _description = "description"; + @Column(name = "action", length = DescriptionStatusEntity._nameLength, nullable = true) + private String action; + public static final String _action = "action"; + public static final int _actionLength = 250; + @Column(name = "created_at", nullable = false) private Instant createdAt; public static final String _createdAt = "createdAt"; @@ -74,6 +79,14 @@ public class DescriptionStatusEntity extends TenantScopedBaseEntity { this.createdAt = createdAt; } + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + public Instant getUpdatedAt() { return this.updatedAt; } diff --git a/backend/core/src/main/java/org/opencdmp/data/PlanEntity.java b/backend/core/src/main/java/org/opencdmp/data/PlanEntity.java index f3e6d072d..cd92a36c3 100644 --- a/backend/core/src/main/java/org/opencdmp/data/PlanEntity.java +++ b/backend/core/src/main/java/org/opencdmp/data/PlanEntity.java @@ -47,6 +47,10 @@ public class PlanEntity extends TenantScopedBaseEntity { public static final String _status = "status"; + @Column(name = "status_id", nullable = true) + private UUID statusId; + public static final String _statusId = "statusId"; + @Column(name = "properties", nullable = true) private String properties; @@ -141,6 +145,14 @@ public class PlanEntity extends TenantScopedBaseEntity { this.status = status; } + public UUID getStatusId() { + return statusId; + } + + public void setStatusId(UUID statusId) { + this.statusId = statusId; + } + public String getProperties() { return this.properties; } diff --git a/backend/core/src/main/java/org/opencdmp/data/PlanStatusEntity.java b/backend/core/src/main/java/org/opencdmp/data/PlanStatusEntity.java index 6837d381d..39e3529e2 100644 --- a/backend/core/src/main/java/org/opencdmp/data/PlanStatusEntity.java +++ b/backend/core/src/main/java/org/opencdmp/data/PlanStatusEntity.java @@ -29,6 +29,11 @@ public class PlanStatusEntity extends TenantScopedBaseEntity { private String description; public static final String _description = "description"; + @Column(name = "action", length = PlanStatusEntity._actionLength, nullable = true) + private String action; + public static final String _action = "action"; + public static final int _actionLength = 250; + @Column(name = "created_at", nullable = false) private Instant createdAt; public static final String _createdAt = "createdAt"; @@ -67,6 +72,14 @@ public class PlanStatusEntity extends TenantScopedBaseEntity { public String getDescription() { return this.description; } public void setDescription(String description) { this.description = description;} + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + public Instant getCreatedAt() { return this.createdAt; } diff --git a/backend/core/src/main/java/org/opencdmp/elastic/data/DescriptionElasticEntity.java b/backend/core/src/main/java/org/opencdmp/elastic/data/DescriptionElasticEntity.java index 5b0d8cc8f..15f38125e 100644 --- a/backend/core/src/main/java/org/opencdmp/elastic/data/DescriptionElasticEntity.java +++ b/backend/core/src/main/java/org/opencdmp/elastic/data/DescriptionElasticEntity.java @@ -33,9 +33,9 @@ public class DescriptionElasticEntity { private String description; public final static String _description = "description"; - @Field(value = DescriptionElasticEntity._status, type = FieldType.Short) - private DescriptionStatus status; - public final static String _status = "status"; + @Field(value = DescriptionElasticEntity._statusId, type = FieldType.Keyword) + private UUID statusId; + public final static String _statusId = "statusId"; @Field(value = DescriptionElasticEntity._finalizedAt, type = FieldType.Date) private Date finalizedAt; @@ -97,12 +97,12 @@ public class DescriptionElasticEntity { this.description = description; } - 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 Date getFinalizedAt() { diff --git a/backend/core/src/main/java/org/opencdmp/elastic/data/PlanElasticEntity.java b/backend/core/src/main/java/org/opencdmp/elastic/data/PlanElasticEntity.java index 3d8e3e8b5..9a2350443 100644 --- a/backend/core/src/main/java/org/opencdmp/elastic/data/PlanElasticEntity.java +++ b/backend/core/src/main/java/org/opencdmp/elastic/data/PlanElasticEntity.java @@ -35,9 +35,9 @@ public class PlanElasticEntity { private Short version; public final static String _version = "version"; - @Field(value = PlanElasticEntity._status, type = FieldType.Short) - private PlanStatus status; - public final static String _status = "status"; + @Field(value = PlanElasticEntity._statusId, type = FieldType.Keyword) + private UUID statusId; + public final static String _statusId = "statusId"; @Field(value = PlanElasticEntity._accessType, type = FieldType.Short) private PlanAccessType accessType; @@ -130,12 +130,12 @@ public class PlanElasticEntity { this.version = version; } - public PlanStatus getStatus() { - return this.status; + public UUID getStatusId() { + return statusId; } - public void setStatus(PlanStatus status) { - this.status = status; + public void setStatusId(UUID statusId) { + this.statusId = statusId; } public PlanAccessType getAccessType() { diff --git a/backend/core/src/main/java/org/opencdmp/elastic/data/nested/NestedPlanElasticEntity.java b/backend/core/src/main/java/org/opencdmp/elastic/data/nested/NestedPlanElasticEntity.java index cd9d84e06..1b1a717eb 100644 --- a/backend/core/src/main/java/org/opencdmp/elastic/data/nested/NestedPlanElasticEntity.java +++ b/backend/core/src/main/java/org/opencdmp/elastic/data/nested/NestedPlanElasticEntity.java @@ -35,9 +35,9 @@ public class NestedPlanElasticEntity { private PlanVersionStatus versionStatus; public final static String _versionStatus = "versionStatus"; - @Field(value = NestedPlanElasticEntity._status, type = FieldType.Short) - private PlanStatus status; - public final static String _status = "status"; + @Field(value = NestedPlanElasticEntity._statusId, type = FieldType.Keyword) + private UUID statusId; + public final static String _statusId = "statusId"; @Field(value = NestedPlanElasticEntity._accessType, type = FieldType.Short) private PlanAccessType accessType; @@ -103,12 +103,12 @@ public class NestedPlanElasticEntity { this.version = version; } - public PlanStatus getStatus() { - return status; + public UUID getStatusId() { + return statusId; } - public void setStatus(PlanStatus status) { - this.status = status; + public void setStatusId(UUID statusId) { + this.statusId = statusId; } public PlanAccessType getAccessType() { diff --git a/backend/core/src/main/java/org/opencdmp/elastic/elasticbuilder/DescriptionElasticBuilder.java b/backend/core/src/main/java/org/opencdmp/elastic/elasticbuilder/DescriptionElasticBuilder.java index 6cc4f11a8..6b8aa3ba4 100644 --- a/backend/core/src/main/java/org/opencdmp/elastic/elasticbuilder/DescriptionElasticBuilder.java +++ b/backend/core/src/main/java/org/opencdmp/elastic/elasticbuilder/DescriptionElasticBuilder.java @@ -63,7 +63,7 @@ public class DescriptionElasticBuilder extends BaseElasticBuilder excludedIds; private Collection tenantIds; - private Collection statuses; + private Collection statusIds; private NestedDescriptionTemplateElasticQuery descriptionTemplateSubQuery; private NestedReferenceElasticQuery referenceSubQuery; private NestedTagElasticQuery tagSubQuery; @@ -133,18 +133,18 @@ public class DescriptionElasticQuery extends ElasticQuery values) { - this.statuses = values; + public DescriptionElasticQuery statusIds(Collection values) { + this.statusIds = values; return this; } @@ -189,7 +189,7 @@ public class DescriptionElasticQuery extends ElasticQuery predicates = new ArrayList<>(); if (usePublic ) { predicates.add(this.and( - this.equals(new ElasticField(DescriptionElasticEntity._plan + "." + PlanElasticEntity._status, this.entityClass()).disableInfer(true), PlanStatus.Finalized.getValue()), + this.equals(new ElasticField(DescriptionElasticEntity._plan + "." + PlanElasticEntity._statusId, this.entityClass()).disableInfer(true), PlanStatus.Finalized.getValue()), this.equals(new ElasticField(DescriptionElasticEntity._plan + "." + PlanElasticEntity._accessType, this.entityClass()).disableInfer(true), PlanAccessType.Public.getValue()) )); } @@ -284,8 +284,8 @@ public class DescriptionElasticQuery extends ElasticQuery ids; private Collection excludedIds; - private Collection statuses; + private Collection statusIds; private Collection versionStatuses; private Collection accessTypes; private Collection versions; @@ -108,18 +108,18 @@ public class InnerObjectPlanElasticQuery extends ElasticInnerObjectQuery values) { - this.statuses = values; + public InnerObjectPlanElasticQuery statusIds(Collection values) { + this.statusIds = values; return this; } @@ -203,8 +203,8 @@ public class InnerObjectPlanElasticQuery extends ElasticInnerObjectQuery x.getValue()).collect(Collectors.toList()).toArray(new Short[this.statuses.size()]))._toQuery()); + if (this.statusIds != null) { + predicates.add(this.containsUUID(this.elasticFieldOf(NestedPlanElasticEntity._id).disableInfer(true), this.statusIds)._toQuery()); } if (this.versionStatuses != null) { predicates.add(this.contains(this.elasticFieldOf(NestedPlanElasticEntity._versionStatus).disableInfer(true), this.versionStatuses.stream().map(x-> x.getValue()).collect(Collectors.toList()).toArray(new Short[this.versionStatuses.size()]))._toQuery()); @@ -229,7 +229,7 @@ public class InnerObjectPlanElasticQuery extends ElasticInnerObjectQuery { private String like; private Collection ids; private Collection excludedIds; - private Collection statuses; + private Collection statusIds; private Collection versionStatuses; private Collection accessTypes; private Collection versions; @@ -140,18 +140,18 @@ public class PlanElasticQuery extends ElasticQuery { return this; } - public PlanElasticQuery statuses(PlanStatus value) { - this.statuses = List.of(value); + public PlanElasticQuery statuses(UUID value) { + this.statusIds = List.of(value); return this; } - public PlanElasticQuery statuses(PlanStatus... value) { - this.statuses = Arrays.asList(value); + public PlanElasticQuery statuses(UUID... value) { + this.statusIds = Arrays.asList(value); return this; } - public PlanElasticQuery statuses(Collection values) { - this.statuses = values; + public PlanElasticQuery statuses(Collection values) { + this.statusIds = values; return this; } @@ -224,7 +224,7 @@ public class PlanElasticQuery extends ElasticQuery { @Override protected Boolean isFalseQuery() { - return this.isEmpty(this.ids) || this.isEmpty(this.versionStatuses) || this.isEmpty(this.excludedIds) || this.isEmpty(this.accessTypes)|| this.isEmpty(this.statuses); + return this.isEmpty(this.ids) || this.isEmpty(this.versionStatuses) || this.isEmpty(this.excludedIds) || this.isEmpty(this.accessTypes)|| this.isEmpty(this.statusIds); } @Override @@ -265,7 +265,7 @@ public class PlanElasticQuery extends ElasticQuery { List predicates = new ArrayList<>(); if (usePublic) { predicates.add(this.and( - this.equals(this.elasticFieldOf(PlanElasticEntity._status), PlanStatus.Finalized.getValue()), + this.equals(this.elasticFieldOf(PlanElasticEntity._statusId), PlanStatus.Finalized.getValue()), this.equals(this.elasticFieldOf(PlanElasticEntity._accessType), PlanAccessType.Public.getValue()) )); } @@ -328,8 +328,8 @@ public class PlanElasticQuery extends ElasticQuery { if (this.excludedIds != null) { predicates.add(this.not(this.containsUUID(this.elasticFieldOf(PlanElasticEntity._id), this.excludedIds)._toQuery())._toQuery()); } - if (this.statuses != null) { - predicates.add(this.contains(this.elasticFieldOf(PlanElasticEntity._status), this.statuses.stream().map(PlanStatus::getValue).toList().toArray(new Short[this.statuses.size()]))._toQuery()); + if (this.statusIds != null) { + predicates.add(this.containsUUID(this.elasticFieldOf(PlanElasticEntity._statusId), this.statusIds)._toQuery()); } if (this.versionStatuses != null) { predicates.add(this.contains(this.elasticFieldOf(PlanElasticEntity._versionStatus), this.versionStatuses.stream().map(PlanVersionStatus::getValue).toList().toArray(new Short[this.versionStatuses.size()]))._toQuery()); @@ -363,7 +363,7 @@ public class PlanElasticQuery extends ElasticQuery { if (columns.contains(PlanElasticEntity._id)) mocDoc.setId(FieldBasedMapper.shallowSafeConversion(rawData.get(PlanElasticEntity._id), UUID.class)); if (columns.contains(PlanElasticEntity._label)) mocDoc.setLabel(FieldBasedMapper.shallowSafeConversion(rawData.get(PlanElasticEntity._label), String.class)); if (columns.contains(PlanElasticEntity._description)) mocDoc.setDescription(FieldBasedMapper.shallowSafeConversion(rawData.get(PlanElasticEntity._description), String.class)); - if (columns.contains(PlanElasticEntity._status)) mocDoc.setStatus(FieldBasedMapper.shallowSafeConversion(rawData.get(PlanElasticEntity._status), PlanStatus.class)); + if (columns.contains(PlanElasticEntity._statusId)) mocDoc.setStatusId(FieldBasedMapper.shallowSafeConversion(rawData.get(PlanElasticEntity._statusId), UUID.class)); if (columns.contains(PlanElasticEntity._versionStatus)) mocDoc.setVersionStatus(FieldBasedMapper.shallowSafeConversion(rawData.get(PlanElasticEntity._versionStatus), PlanVersionStatus.class)); if (columns.contains(PlanElasticEntity._version)) mocDoc.setVersion(FieldBasedMapper.shallowSafeConversion(rawData.get(PlanElasticEntity._version), Short.class)); if (columns.contains(PlanElasticEntity._groupId)) mocDoc.setGroupId(FieldBasedMapper.shallowSafeConversion(rawData.get(PlanElasticEntity._groupId), UUID.class)); @@ -383,7 +383,7 @@ public class PlanElasticQuery extends ElasticQuery { if (item.match(PlanElasticEntity._id)) return this.elasticFieldOf(PlanElasticEntity._id); else if (item.match(PlanElasticEntity._label)) return item instanceof OrderingFieldResolver ? this.elasticFieldOf(PlanElasticEntity._label).subfield(ElasticConstants.SubFields.keyword) : this.elasticFieldOf(PlanElasticEntity._label); else if (item.match(PlanElasticEntity._description)) return this.elasticFieldOf(PlanElasticEntity._description); - else if (item.match(PlanElasticEntity._status)) return this.elasticFieldOf(PlanElasticEntity._status); + else if (item.match(PlanElasticEntity._statusId)) return this.elasticFieldOf(PlanElasticEntity._statusId); else if (item.match(PlanElasticEntity._version)) return this.elasticFieldOf(PlanElasticEntity._version); else if (item.match(PlanElasticEntity._versionStatus)) return this.elasticFieldOf(PlanElasticEntity._versionStatus); else if (item.match(PlanElasticEntity._groupId)) return this.elasticFieldOf(PlanElasticEntity._groupId); diff --git a/backend/core/src/main/java/org/opencdmp/event/DescriptionStatusTouchedEvent.java b/backend/core/src/main/java/org/opencdmp/event/DescriptionStatusTouchedEvent.java new file mode 100644 index 000000000..17c793a5d --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/event/DescriptionStatusTouchedEvent.java @@ -0,0 +1,35 @@ +package org.opencdmp.event; + +import java.util.UUID; + +public class DescriptionStatusTouchedEvent { + + public DescriptionStatusTouchedEvent() { + } + + public DescriptionStatusTouchedEvent(UUID id, String tenantCode) { + this.id = id; + this.tenantCode = tenantCode; + } + + private UUID id; + + private String tenantCode; + + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getTenantCode() { + return tenantCode; + } + + public void setTenantCode(String tenantCode) { + this.tenantCode = tenantCode; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/event/EventBroker.java b/backend/core/src/main/java/org/opencdmp/event/EventBroker.java index defc5be38..4d08202dd 100644 --- a/backend/core/src/main/java/org/opencdmp/event/EventBroker.java +++ b/backend/core/src/main/java/org/opencdmp/event/EventBroker.java @@ -53,6 +53,14 @@ public class EventBroker { this.applicationEventPublisher.publishEvent(event); } + public void emit(PlanStatusTouchedEvent event) { + this.applicationEventPublisher.publishEvent(event); + } + + public void emit(DescriptionStatusTouchedEvent event) { + this.applicationEventPublisher.publishEvent(event); + } + public void emit(TagTouchedEvent event) { this.applicationEventPublisher.publishEvent(event); } diff --git a/backend/core/src/main/java/org/opencdmp/event/PlanStatusTouchedEvent.java b/backend/core/src/main/java/org/opencdmp/event/PlanStatusTouchedEvent.java new file mode 100644 index 000000000..dee289cfc --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/event/PlanStatusTouchedEvent.java @@ -0,0 +1,34 @@ +package org.opencdmp.event; + +import java.util.UUID; + +public class PlanStatusTouchedEvent { + + public PlanStatusTouchedEvent() { + } + + public PlanStatusTouchedEvent(UUID id, String tenantCode) { + this.id = id; + this.tenantCode = tenantCode; + } + + private UUID id; + + private String tenantCode; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getTenantCode() { + return tenantCode; + } + + public void setTenantCode(String tenantCode) { + this.tenantCode = tenantCode; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/event/PlanTouchedEvent.java b/backend/core/src/main/java/org/opencdmp/event/PlanTouchedEvent.java index 13f362c3a..edc4feab7 100644 --- a/backend/core/src/main/java/org/opencdmp/event/PlanTouchedEvent.java +++ b/backend/core/src/main/java/org/opencdmp/event/PlanTouchedEvent.java @@ -22,3 +22,4 @@ public class PlanTouchedEvent { } } + 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..5c89d1c2d --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/PublicDescriptionStatus.java @@ -0,0 +1,36 @@ +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 final static String _definition = "definition"; + private PublicDescriptionStatusDefinition definition; + + 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; } + + public PublicDescriptionStatusDefinition getDefinition() { + return definition; + } + + public void setDefinition(PublicDescriptionStatusDefinition definition) { + this.definition = definition; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/model/PublicDescriptionStatusDefinition.java b/backend/core/src/main/java/org/opencdmp/model/PublicDescriptionStatusDefinition.java new file mode 100644 index 000000000..6a0e26f82 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/PublicDescriptionStatusDefinition.java @@ -0,0 +1,20 @@ +package org.opencdmp.model; + +import org.opencdmp.commons.enums.DescriptionStatusAvailableActionType; + +import java.util.List; + +public class PublicDescriptionStatusDefinition { + + + public final static String _availableActions = "availableActions"; + private List availableActions; + + public List getAvailableActions() { + return availableActions; + } + + public void setAvailableActions(List availableActions) { + this.availableActions = availableActions; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/model/PublicPlan.java b/backend/core/src/main/java/org/opencdmp/model/PublicPlan.java index b95aded25..1415cc27a 100644 --- a/backend/core/src/main/java/org/opencdmp/model/PublicPlan.java +++ b/backend/core/src/main/java/org/opencdmp/model/PublicPlan.java @@ -1,7 +1,6 @@ package org.opencdmp.model; import org.opencdmp.commons.enums.PlanAccessType; -import org.opencdmp.commons.enums.PlanStatus; import java.time.Instant; import java.util.List; @@ -36,7 +35,7 @@ public class PublicPlan { public static final String _publishedAt = "publishedAt"; - private PlanStatus status; + private PublicPlanStatus status; public static final String _status = "status"; private UUID groupId; @@ -118,11 +117,11 @@ public class PublicPlan { this.publishedAt = publishedAt; } - public PlanStatus getStatus() { + public PublicPlanStatus getStatus() { return status; } - public void setStatus(PlanStatus status) { + public void setStatus(PublicPlanStatus status) { this.status = status; } diff --git a/backend/core/src/main/java/org/opencdmp/model/PublicPlanStatus.java b/backend/core/src/main/java/org/opencdmp/model/PublicPlanStatus.java new file mode 100644 index 000000000..dc80f6816 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/PublicPlanStatus.java @@ -0,0 +1,43 @@ +package org.opencdmp.model; + + +import org.opencdmp.commons.enums.PlanStatus; + +import java.util.UUID; + +public class PublicPlanStatus { + + 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 PlanStatus internalStatus; + + public final static String _definition = "definition"; + private PublicPlanStatusDefinition definition; + + 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 PlanStatus getInternalStatus() { + return internalStatus; + } + + public void setInternalStatus(PlanStatus internalStatus) { + this.internalStatus = internalStatus; + } + + public PublicPlanStatusDefinition getDefinition() { + return definition; + } + + public void setDefinition(PublicPlanStatusDefinition definition) { + this.definition = definition; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/model/PublicPlanStatusDefinition.java b/backend/core/src/main/java/org/opencdmp/model/PublicPlanStatusDefinition.java new file mode 100644 index 000000000..ad38047bb --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/PublicPlanStatusDefinition.java @@ -0,0 +1,21 @@ +package org.opencdmp.model; + +import org.opencdmp.commons.enums.PlanStatusAvailableActionType; + +import java.util.List; + +public class PublicPlanStatusDefinition { + + + public final static String _availableActions = "availableActions"; + private List availableActions; + + + public List getAvailableActions() { + return availableActions; + } + + public void setAvailableActions(List availableActions) { + this.availableActions = availableActions; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/model/RecentActivityItemLookup.java b/backend/core/src/main/java/org/opencdmp/model/RecentActivityItemLookup.java index 028a96153..7b1d6b499 100644 --- a/backend/core/src/main/java/org/opencdmp/model/RecentActivityItemLookup.java +++ b/backend/core/src/main/java/org/opencdmp/model/RecentActivityItemLookup.java @@ -6,9 +6,7 @@ import org.opencdmp.commons.enums.IsActive; import org.opencdmp.commons.enums.RecentActivityOrder; import org.opencdmp.model.description.Description; import org.opencdmp.model.plan.Plan; -import org.opencdmp.query.lookup.DescriptionLookup; -import org.opencdmp.query.lookup.PlanLookup; -import org.opencdmp.query.lookup.PlanUserLookup; +import org.opencdmp.query.lookup.*; import gr.cite.tools.data.query.Ordering; import gr.cite.tools.data.query.Paging; import gr.cite.tools.fieldset.BaseFieldSet; @@ -97,8 +95,12 @@ public class RecentActivityItemLookup{ DescriptionLookup lookup = new DescriptionLookup(); lookup.setIsActive(List.of(IsActive.Active)); if (this.like != null) lookup.setLike(this.like); - if (this.onlyDraft != null) lookup.setStatuses(List.of(DescriptionStatus.Draft)); - else lookup.setStatuses(List.of(DescriptionStatus.Draft, DescriptionStatus.Finalized)); + if (this.onlyDraft != null) { + DescriptionStatusLookup descriptionStatusLookup = new DescriptionStatusLookup(); + descriptionStatusLookup.setInternalStatuses(List.of(DescriptionStatus.Draft)); + descriptionStatusLookup.setIsActive(List.of(IsActive.Active)); + lookup.setDescriptionStatusSubQuery(descriptionStatusLookup); + } if (this.userIds != null) { PlanLookup planLookup = new PlanLookup(); PlanUserLookup planUserLookup = new PlanUserLookup(); @@ -131,7 +133,12 @@ public class RecentActivityItemLookup{ PlanLookup lookup = new PlanLookup(); lookup.setIsActive(List.of(IsActive.Active)); if (this.like != null) lookup.setLike(this.like); - if (this.onlyDraft != null) lookup.setStatuses(List.of(PlanStatus.Draft)); + if (this.onlyDraft != null) { + PlanStatusLookup planStatusLookup = new PlanStatusLookup(); + planStatusLookup.setInternalStatuses(List.of(PlanStatus.Draft)); + planStatusLookup.setIsActive(List.of(IsActive.Active)); + lookup.setPlanStatusSubQuery(planStatusLookup); + } if (this.userIds != null) { PlanUserLookup planUserLookup = new PlanUserLookup(); planUserLookup.setUserIds(this.userIds); 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..6b8989995 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::getStatusId).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..099d2406b --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/builder/PublicDescriptionStatusBuilder.java @@ -0,0 +1,68 @@ +package org.opencdmp.model.builder; + +import gr.cite.tools.data.builder.BuilderFactory; +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.commons.XmlHandlingService; +import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionEntity; +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 final XmlHandlingService xmlHandlingService; + private final BuilderFactory builderFactory; + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + @Autowired + public PublicDescriptionStatusBuilder( + ConventionService conventionService, XmlHandlingService xmlHandlingService, BuilderFactory builderFactory) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(PublicDescriptionStatusBuilder.class))); + this.xmlHandlingService = xmlHandlingService; + this.builderFactory = builderFactory; + } + + 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<>(); + FieldSet definitionFields = fields.extractPrefixed(this.asPrefix(PublicDescriptionStatus._definition)); + + 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()); + if (!definitionFields.isEmpty() && d.getDefinition() != null) { + DescriptionStatusDefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(DescriptionStatusDefinitionEntity.class, d.getDefinition()); + m.setDefinition(this.builderFactory.builder(PublicDescriptionStatusDefinitionBuilder.class).authorize(this.authorize).build(definitionFields, definition)); + } + 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/PublicDescriptionStatusDefinitionBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/PublicDescriptionStatusDefinitionBuilder.java new file mode 100644 index 000000000..3445595d7 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/builder/PublicDescriptionStatusDefinitionBuilder.java @@ -0,0 +1,51 @@ +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.commons.types.descriptionstatus.DescriptionStatusDefinitionEntity; +import org.opencdmp.convention.ConventionService; + +import org.opencdmp.model.PublicDescriptionStatusDefinition; +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 PublicDescriptionStatusDefinitionBuilder extends BaseBuilder { + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + @Autowired + public PublicDescriptionStatusDefinitionBuilder( + ConventionService conventionService) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(PublicDescriptionStatusDefinitionBuilder.class))); + } + + public PublicDescriptionStatusDefinitionBuilder 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 (DescriptionStatusDefinitionEntity d : data) { + PublicDescriptionStatusDefinition m = new PublicDescriptionStatusDefinition(); + if (fields.hasField(this.asIndexer(PublicDescriptionStatusDefinition._availableActions))) m.setAvailableActions(d.getAvailableActions()); + 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/PublicPlanBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/PublicPlanBuilder.java index 560305ba5..e63666b61 100644 --- a/backend/core/src/main/java/org/opencdmp/model/builder/PublicPlanBuilder.java +++ b/backend/core/src/main/java/org/opencdmp/model/builder/PublicPlanBuilder.java @@ -71,6 +71,9 @@ public class PublicPlanBuilder extends BaseBuilder { FieldSet otherPlanVersionsFields = fields.extractPrefixed(this.asPrefix(PublicPlan._otherPlanVersions)); Map> otherPlanVersionsMap = this.collectOtherPlanVersions(otherPlanVersionsFields, data); + FieldSet planStatusFields = fields.extractPrefixed(this.asPrefix(PublicPlan._status)); + Map planStatusItemsMap = this.collectPlanStatuses(planStatusFields, data); + for (PlanEntity d : data) { PublicPlan m = new PublicPlan(); if (fields.hasField(this.asIndexer(PublicPlan._id))) m.setId(d.getId()); @@ -80,7 +83,7 @@ public class PublicPlanBuilder extends BaseBuilder { if (fields.hasField(this.asIndexer(PublicPlan._finalizedAt))) m.setFinalizedAt(d.getFinalizedAt()); if (fields.hasField(this.asIndexer(PublicPlan._updatedAt))) m.setUpdatedAt(d.getUpdatedAt()); if (fields.hasField(this.asIndexer(PublicPlan._accessType))) m.setAccessType(d.getAccessType()); - if (fields.hasField(this.asIndexer(PublicPlan._status))) m.setStatus(d.getStatus()); + if (!planStatusFields.isEmpty() && planStatusItemsMap != null && planStatusItemsMap.containsKey(d.getStatusId())) m.setStatus(planStatusItemsMap.get(d.getStatusId())); if (fields.hasField(this.asIndexer(PublicPlan._groupId))) m.setGroupId(d.getGroupId()); if (fields.hasField(this.asIndexer(PublicPlan._accessType))) m.setAccessType(d.getAccessType()); @@ -190,4 +193,34 @@ public class PublicPlanBuilder extends BaseBuilder { return itemMap; } + private Map collectPlanStatuses(FieldSet fields, List data) throws MyApplicationException { + if (fields.isEmpty() || data.isEmpty()) + return null; + this.logger.debug("checking related - {}", PublicPlanStatus.class.getSimpleName()); + + Map itemMap; + if (!fields.hasOtherField(this.asIndexer(PublicPlanStatus._id))) { + itemMap = this.asEmpty( + data.stream().map(PlanEntity::getStatusId).distinct().collect(Collectors.toList()), + x -> { + PublicPlanStatus item = new PublicPlanStatus(); + item.setId(x); + return item; + }, + PublicPlanStatus::getId); + } else { + FieldSet clone = new BaseFieldSet(fields.getFields()).ensure(PublicPlanStatus._id); + PlanStatusQuery q = this.queryFactory.query(PlanStatusQuery.class).disableTracking().authorize(this.authorize).ids(data.stream().map(PlanEntity::getStatusId).distinct().collect(Collectors.toList())); + itemMap = this.builderFactory.builder(PublicPlanStatusBuilder.class).authorize(this.authorize).asForeignKey(q, clone, PublicPlanStatus::getId); + } + if (!fields.hasField(PublicPlanStatus._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/PublicPlanStatusBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/PublicPlanStatusBuilder.java new file mode 100644 index 000000000..e1c5a31f6 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/builder/PublicPlanStatusBuilder.java @@ -0,0 +1,68 @@ +package org.opencdmp.model.builder; + +import gr.cite.tools.data.builder.BuilderFactory; +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.commons.XmlHandlingService; +import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionEntity; +import org.opencdmp.convention.ConventionService; +import org.opencdmp.data.PlanStatusEntity; +import org.opencdmp.model.PublicPlanStatus; +import org.opencdmp.model.builder.planstatus.PlanStatusDefinitionBuilder; +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 PublicPlanStatusBuilder extends BaseBuilder { + + private final XmlHandlingService xmlHandlingService; + private final BuilderFactory builderFactory; + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + @Autowired + public PublicPlanStatusBuilder( + ConventionService conventionService, XmlHandlingService xmlHandlingService, BuilderFactory builderFactory) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(PublicPlanStatusBuilder.class))); + this.xmlHandlingService = xmlHandlingService; + this.builderFactory = builderFactory; + } + + public PublicPlanStatusBuilder 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<>(); + + FieldSet definitionFields = fields.extractPrefixed(this.asPrefix(PublicPlanStatus._definition)); + List models = new ArrayList<>(); + + for (PlanStatusEntity d : data) { + PublicPlanStatus m = new PublicPlanStatus(); + if (fields.hasField(this.asIndexer(PublicPlanStatus._id))) m.setId(d.getId()); + if (fields.hasField(this.asIndexer(PublicPlanStatus._name))) m.setName(d.getName()); + if (fields.hasField(this.asIndexer(PublicPlanStatus._internalStatus))) m.setInternalStatus(d.getInternalStatus()); + if (!definitionFields.isEmpty() && d.getDefinition() != null) { + PlanStatusDefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(PlanStatusDefinitionEntity.class, d.getDefinition()); + m.setDefinition(this.builderFactory.builder(PublicPlanStatusDefinitionBuilder.class).authorize(this.authorize).build(definitionFields, definition)); + } + 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/PublicPlanStatusDefinitionBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/PublicPlanStatusDefinitionBuilder.java new file mode 100644 index 000000000..6ba1fcfdb --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/builder/PublicPlanStatusDefinitionBuilder.java @@ -0,0 +1,50 @@ +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.commons.types.planstatus.PlanStatusDefinitionEntity; +import org.opencdmp.convention.ConventionService; +import org.opencdmp.model.PublicPlanStatusDefinition; +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 PublicPlanStatusDefinitionBuilder extends BaseBuilder { + + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + @Autowired + public PublicPlanStatusDefinitionBuilder( + ConventionService conventionService) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(PublicPlanStatusDefinitionBuilder.class))); + } + + public PublicPlanStatusDefinitionBuilder 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 (PlanStatusDefinitionEntity d : data) { + PublicPlanStatusDefinition m = new PublicPlanStatusDefinition(); + if (fields.hasField(this.asIndexer(PublicPlanStatusDefinition._availableActions))) m.setAvailableActions(d.getAvailableActions()); + 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/commonmodels/PlanUserCommonModelBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/PlanUserCommonModelBuilder.java index 27d945528..457278ab4 100644 --- a/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/PlanUserCommonModelBuilder.java +++ b/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/PlanUserCommonModelBuilder.java @@ -78,7 +78,7 @@ public class PlanUserCommonModelBuilder extends BaseCommonModelBuilder itemMap; - UserQuery q = this.queryFactory.query(UserQuery.class).disableTracking().isActive(IsActive.Active).authorize(this.authorize).ids(data.stream().map(PlanUserEntity::getUserId).distinct().collect(Collectors.toList())); + UserQuery q = this.queryFactory.query(UserQuery.class).disableTracking().isActive(IsActive.Active).ids(data.stream().map(PlanUserEntity::getUserId).distinct().collect(Collectors.toList())); itemMap = this.builderFactory.builder(UserCommonModelBuilder.class).authorize(this.authorize).asForeignKey(q, UserEntity::getId); return itemMap; } diff --git a/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/description/DescriptionCommonModelBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/description/DescriptionCommonModelBuilder.java index f8fbb106b..edb4349f8 100644 --- a/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/description/DescriptionCommonModelBuilder.java +++ b/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/description/DescriptionCommonModelBuilder.java @@ -8,6 +8,7 @@ import gr.cite.tools.logging.LoggerService; import org.opencdmp.authorization.AuthorizationFlags; import org.opencdmp.commonmodels.enums.DescriptionStatus; import org.opencdmp.commonmodels.models.description.DescriptionModel; +import org.opencdmp.commonmodels.models.description.DescriptionStatusModel; import org.opencdmp.commonmodels.models.descriptiotemplate.DescriptionTemplateModel; import org.opencdmp.commonmodels.models.plan.PlanModel; import org.opencdmp.commons.JsonHandlingService; @@ -25,9 +26,7 @@ import org.opencdmp.model.builder.commonmodels.CommonModelBuilderItemResponse; import org.opencdmp.model.builder.commonmodels.descriptiontemplate.DescriptionTemplateCommonModelBuilder; import org.opencdmp.model.builder.commonmodels.plan.PlanCommonModelBuilder; import org.opencdmp.model.descriptiontemplate.DescriptionTemplate; -import org.opencdmp.query.DescriptionTemplateQuery; -import org.opencdmp.query.PlanDescriptionTemplateQuery; -import org.opencdmp.query.PlanQuery; +import org.opencdmp.query.*; import org.opencdmp.service.visibility.VisibilityService; import org.opencdmp.service.visibility.VisibilityServiceImpl; import org.slf4j.LoggerFactory; @@ -103,18 +102,15 @@ public class DescriptionCommonModelBuilder extends BaseCommonModelBuilder definitionEntityMap = this.collectDescriptionTemplateDefinitions(data); Map planDescriptionTemplateSections = this.collectPlanDescriptionTemplateSections(data); + Map descriptionStatuses = this.collectDescriptionStatuses(data); + List> models = new ArrayList<>(); for (DescriptionEntity d : data) { DescriptionModel m = new DescriptionModel(); m.setId(d.getId()); m.setLabel(d.getLabel()); m.setDescription(d.getDescription()); - switch (d.getStatus()){ - case Finalized -> m.setStatus(DescriptionStatus.Finalized); - case Draft -> m.setStatus(DescriptionStatus.Draft); - case Canceled -> m.setStatus(DescriptionStatus.Canceled); - default -> throw new MyApplicationException("unrecognized type " + d.getStatus()); - } + if (descriptionStatuses != null && d.getStatusId() != null && descriptionStatuses.containsKey(d.getStatusId())) m.setStatus(descriptionStatuses.get(d.getStatusId())); m.setCreatedAt(d.getCreatedAt()); m.setDescription(d.getDescription()); if (plans != null && d.getPlanId() != null && plans.containsKey(d.getPlanId())) m.setPlan(plans.get(d.getPlanId())); @@ -197,7 +193,8 @@ public class DescriptionCommonModelBuilder extends BaseCommonModelBuilder collectDescriptionStatuses(List data) throws MyApplicationException { + if (data.isEmpty()) + return null; + this.logger.debug("checking related - {}", DescriptionStatusModel.class.getSimpleName()); + + Map itemMap; + 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(DescriptionStatusCommonModelBuilder.class).authorize(this.authorize).asForeignKey(q, DescriptionStatusEntity::getId); + + return itemMap; + } + } diff --git a/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/description/DescriptionStatusCommonModelBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/description/DescriptionStatusCommonModelBuilder.java new file mode 100644 index 000000000..48a055f80 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/description/DescriptionStatusCommonModelBuilder.java @@ -0,0 +1,67 @@ +package org.opencdmp.model.builder.commonmodels.description; + +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.logging.LoggerService; +import org.opencdmp.authorization.AuthorizationFlags; +import org.opencdmp.commonmodels.models.description.DescriptionStatusModel; +import org.opencdmp.convention.ConventionService; +import org.opencdmp.data.DescriptionStatusEntity; +import org.opencdmp.model.builder.commonmodels.BaseCommonModelBuilder; +import org.opencdmp.model.builder.commonmodels.CommonModelBuilderItemResponse; + + +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.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class DescriptionStatusCommonModelBuilder extends BaseCommonModelBuilder { + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + @Autowired + public DescriptionStatusCommonModelBuilder( + ConventionService conventionService + ) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(DescriptionStatusCommonModelBuilder.class))); + } + + public DescriptionStatusCommonModelBuilder authorize(EnumSet values) { + this.authorize = values; + return this; + } + + + @Override + protected List> buildInternal(List data) throws MyApplicationException { + this.logger.debug("building for {}", Optional.ofNullable(data).map(List::size).orElse(0)); + if (data == null || data.isEmpty()) return new ArrayList<>(); + + List> models = new ArrayList<>(); + for (DescriptionStatusEntity d : data) { + DescriptionStatusModel m = new DescriptionStatusModel(); + m.setId(d.getId()); + m.setName(d.getName()); + if (d.getInternalStatus() != null) { + switch (d.getInternalStatus()){ + case Finalized -> m.setInternalStatus(org.opencdmp.commonmodels.enums.DescriptionStatus.Finalized); + case Draft -> m.setInternalStatus(org.opencdmp.commonmodels.enums.DescriptionStatus.Draft); + case Canceled -> m.setInternalStatus(org.opencdmp.commonmodels.enums.DescriptionStatus.Canceled); + default -> throw new MyApplicationException("unrecognized type " + d.getInternalStatus()); + } + } + + models.add(new CommonModelBuilderItemResponse<>(m, d)); + } + + 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/commonmodels/plan/PlanCommonModelBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/plan/PlanCommonModelBuilder.java index d99bb0f88..1a4ace692 100644 --- a/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/plan/PlanCommonModelBuilder.java +++ b/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/plan/PlanCommonModelBuilder.java @@ -15,6 +15,7 @@ import org.opencdmp.commonmodels.models.FileEnvelopeModel; import org.opencdmp.commonmodels.models.UserModel; import org.opencdmp.commonmodels.models.description.DescriptionModel; import org.opencdmp.commonmodels.models.plan.PlanModel; +import org.opencdmp.commonmodels.models.plan.PlanStatusModel; import org.opencdmp.commonmodels.models.planblueprint.PlanBlueprintModel; import org.opencdmp.commonmodels.models.planreference.PlanReferenceModel; import org.opencdmp.commons.JsonHandlingService; @@ -127,6 +128,7 @@ public class PlanCommonModelBuilder extends BaseCommonModelBuilder creators = this.collectCreators(data); Map planBlueprints = this.collectPlanBlueprints(data); Map definitionEntityMap = this.collectPlanBlueprintDefinitions(data); + Map planStatuses = this.collectPlanStatuses(data); for (PlanEntity d : data) { PlanModel m = new PlanModel(); @@ -137,11 +139,7 @@ public class PlanCommonModelBuilder extends BaseCommonModelBuilder m.setStatus(PlanStatus.Finalized); - case Draft -> m.setStatus(PlanStatus.Draft); - default -> throw new MyApplicationException("unrecognized type " + d.getStatus()); - } + if (planStatuses != null && !planStatuses.isEmpty() && d.getStatusId() != null && planStatuses.containsKey(d.getStatusId())) m.setStatus(planStatuses.get(d.getStatusId())); if (entityDois != null && !entityDois.isEmpty() && entityDois.containsKey(d.getId())) m.setEntityDois(entityDois.get(d.getId())); if (creators != null && !creators.isEmpty() && d.getCreatorId() != null && creators.containsKey(d.getCreatorId())) m.setCreator(creators.get(d.getCreatorId())); if (planBlueprints != null && !planBlueprints.isEmpty() && d.getBlueprintId() != null && planBlueprints.containsKey(d.getBlueprintId())) m.setPlanBlueprint(planBlueprints.get(d.getBlueprintId())); @@ -194,8 +192,8 @@ public class PlanCommonModelBuilder extends BaseCommonModelBuilder> itemMap; - PlanUserQuery query = this.queryFactory.query(PlanUserQuery.class).disableTracking().isActives(IsActive.Active).authorize(this.authorize).planIds(data.stream().map(PlanEntity::getId).distinct().collect(Collectors.toList())); - itemMap = this.builderFactory.builder(PlanUserCommonModelBuilder.class).authorize(this.authorize).asMasterKey(query, PlanUserEntity::getPlanId); + PlanUserQuery query = this.queryFactory.query(PlanUserQuery.class).disableTracking().isActives(IsActive.Active).planIds(data.stream().map(PlanEntity::getId).distinct().collect(Collectors.toList())); + itemMap = this.builderFactory.builder(PlanUserCommonModelBuilder.class).asMasterKey(query, PlanUserEntity::getPlanId); return itemMap; } @@ -219,7 +217,8 @@ public class PlanCommonModelBuilder extends BaseCommonModelBuilder collectPlanStatuses(List data) throws MyApplicationException { + if (data.isEmpty()) + return null; + this.logger.debug("checking related - {}", PlanStatusModel.class.getSimpleName()); + + Map itemMap; + PlanStatusQuery q = this.queryFactory.query(PlanStatusQuery.class).isActives(IsActive.Active).authorize(this.authorize).ids(data.stream().filter(x-> x.getStatusId() != null).map(PlanEntity::getStatusId).distinct().collect(Collectors.toList())); + itemMap = this.builderFactory.builder(PlanStatusCommonModelBuilder.class).authorize(this.authorize).asForeignKey(q, PlanStatusEntity::getId); + return itemMap; + } + } diff --git a/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/plan/PlanStatusCommonModelBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/plan/PlanStatusCommonModelBuilder.java new file mode 100644 index 000000000..eb8628c6e --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/builder/commonmodels/plan/PlanStatusCommonModelBuilder.java @@ -0,0 +1,64 @@ +package org.opencdmp.model.builder.commonmodels.plan; + +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.logging.LoggerService; +import org.opencdmp.authorization.AuthorizationFlags; +import org.opencdmp.commonmodels.models.plan.PlanStatusModel; +import org.opencdmp.convention.ConventionService; +import org.opencdmp.data.PlanStatusEntity; +import org.opencdmp.model.builder.commonmodels.BaseCommonModelBuilder; +import org.opencdmp.model.builder.commonmodels.CommonModelBuilderItemResponse; +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.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; + +@Component +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class PlanStatusCommonModelBuilder extends BaseCommonModelBuilder { + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + @Autowired + public PlanStatusCommonModelBuilder( + ConventionService conventionService + ) { + super(conventionService, new LoggerService(LoggerFactory.getLogger(PlanStatusCommonModelBuilder.class))); + } + + public PlanStatusCommonModelBuilder authorize(EnumSet values) { + this.authorize = values; + return this; + } + + + @Override + protected List> buildInternal(List data) throws MyApplicationException { + this.logger.debug("building for {}", Optional.ofNullable(data).map(List::size).orElse(0)); + if (data == null || data.isEmpty()) return new ArrayList<>(); + + List> models = new ArrayList<>(); + for (PlanStatusEntity d : data) { + PlanStatusModel m = new PlanStatusModel(); + m.setId(d.getId()); + m.setName(d.getName()); + if (d.getInternalStatus() != null) { + switch (d.getInternalStatus()){ + case Finalized -> m.setInternalStatus(org.opencdmp.commonmodels.enums.PlanStatus.Finalized); + case Draft -> m.setInternalStatus(org.opencdmp.commonmodels.enums.PlanStatus.Draft); + default -> throw new MyApplicationException("unrecognized type " + d.getInternalStatus()); + } + } + + models.add(new CommonModelBuilderItemResponse<>(m, d)); + } + + 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..c2e209f43 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 @@ -13,12 +13,12 @@ import org.opencdmp.authorization.AuthorizationFlags; import org.opencdmp.authorization.authorizationcontentresolver.AuthorizationContentResolver; import org.opencdmp.commons.JsonHandlingService; import org.opencdmp.commons.XmlHandlingService; +import org.opencdmp.commons.enums.IsActive; import org.opencdmp.commons.scope.tenant.TenantScope; import org.opencdmp.commons.types.description.PropertyDefinitionEntity; import org.opencdmp.commons.types.descriptiontemplate.DefinitionEntity; import org.opencdmp.convention.ConventionService; -import org.opencdmp.data.DescriptionEntity; -import org.opencdmp.data.DescriptionTemplateEntity; +import org.opencdmp.data.*; import org.opencdmp.model.DescriptionTag; import org.opencdmp.model.PlanDescriptionTemplate; import org.opencdmp.model.builder.BaseBuilder; @@ -26,14 +26,19 @@ 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.planstatus.PlanStatusDefinitionAuthorization; import org.opencdmp.model.user.User; import org.opencdmp.query.*; +import org.opencdmp.service.custompolicy.CustomPolicyService; +import org.opencdmp.service.descriptionstatus.DescriptionStatusService; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.ConfigurableBeanFactory; @@ -55,14 +60,16 @@ public class DescriptionBuilder extends BaseBuilder authorize = EnumSet.of(AuthorizationFlags.None); @Autowired public DescriptionBuilder( - ConventionService conventionService, - QueryFactory queryFactory, - BuilderFactory builderFactory, JsonHandlingService jsonHandlingService, XmlHandlingService xmlHandlingService, AuthorizationService authorizationService, AuthorizationContentResolver authorizationContentResolver, TenantScope tenantScope) { + ConventionService conventionService, + QueryFactory queryFactory, + BuilderFactory builderFactory, JsonHandlingService jsonHandlingService, XmlHandlingService xmlHandlingService, AuthorizationService authorizationService, AuthorizationContentResolver authorizationContentResolver, TenantScope tenantScope, CustomPolicyService customPolicyService, DescriptionStatusService descriptionStatusService) { super(conventionService, new LoggerService(LoggerFactory.getLogger(DescriptionBuilder.class))); this.queryFactory = queryFactory; this.builderFactory = builderFactory; @@ -71,6 +78,8 @@ public class DescriptionBuilder extends BaseBuilder values) { @@ -85,6 +94,12 @@ public class DescriptionBuilder extends BaseBuilder(); + FieldSet statusFields = fields.extractPrefixed(this.asPrefix(Description._status)); + Map statusItemsMap = this.collectDescriptionStatuses(statusFields, data); + + FieldSet availableStatusesFields = fields.extractPrefixed(this.asPrefix(Description._availableStatuses)); + Map> avaialbleStatusesItemsMap = this.collectAvailableDescriptionStatuses(availableStatusesFields, data); + FieldSet planDescriptionTemplateFields = fields.extractPrefixed(this.asPrefix(Description._planDescriptionTemplate)); Map planDescriptionTemplateItemsMap = this.collectPlanDescriptionTemplates(planDescriptionTemplateFields, data); @@ -110,13 +125,15 @@ public class DescriptionBuilder extends BaseBuilder authorizationFlags = this.extractAuthorizationFlags(fields, Description._authorizationFlags, this.authorizationContentResolver.getPermissionNames()); Map affiliatedResourceMap = authorizationFlags == null || authorizationFlags.isEmpty() ? null : this.authorizationContentResolver.descriptionsAffiliation(data.stream().map(DescriptionEntity::getId).collect(Collectors.toList())); + FieldSet statusAuthorizationFlags = fields.extractPrefixed(this.asPrefix(Description._statusAuthorizationFlags)); List models = new ArrayList<>(); for (DescriptionEntity d : data) { Description m = new Description(); if (fields.hasField(this.asIndexer(Description._id))) m.setId(d.getId()); if (fields.hasField(this.asIndexer(Description._tenantId))) m.setTenantId(d.getTenantId()); if (fields.hasField(this.asIndexer(Description._label))) m.setLabel(d.getLabel()); - if (fields.hasField(this.asIndexer(Description._status))) m.setStatus(d.getStatus()); + if (!statusFields.isEmpty() && statusItemsMap != null && statusItemsMap.containsKey(d.getStatusId())) m.setStatus(statusItemsMap.get(d.getStatusId())); + if (avaialbleStatusesItemsMap != null && !avaialbleStatusesItemsMap.isEmpty() && avaialbleStatusesItemsMap.containsKey(d.getId())) m.setAvailableStatuses(avaialbleStatusesItemsMap.get(d.getId())); if (fields.hasField(this.asIndexer(Description._description))) m.setDescription(d.getDescription()); if (fields.hasField(this.asIndexer(Description._createdAt))) m.setCreatedAt(d.getCreatedAt()); if (fields.hasField(this.asIndexer(Description._updatedAt))) m.setUpdatedAt(d.getUpdatedAt()); @@ -135,6 +152,9 @@ 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> collectAvailableDescriptionStatuses(FieldSet fields, List data) throws MyApplicationException { + if (fields.isEmpty() || data.isEmpty()) return null; + this.logger.debug("checking related - {}", DescriptionStatus.class.getSimpleName()); + + Map> itemMap = new HashMap<>(); + FieldSet fieldSet = new BaseFieldSet(fields.getFields()).ensure(DescriptionStatus._id); + Map> itemStatusIdsMap = this.descriptionStatusService.getAuthorizedAvailableStatusIds(data.stream().map(DescriptionEntity::getId).collect(Collectors.toList())); + + List statusEntities = this.queryFactory.query(DescriptionStatusQuery.class).authorize(this.authorize).isActive(IsActive.Active).ids(itemStatusIdsMap.values().stream().flatMap(List::stream).distinct().collect(Collectors.toList())).collectAs(fieldSet); + List descriptionStatuses = this.builderFactory.builder(DescriptionStatusBuilder.class).authorize(this.authorize).build(fieldSet, statusEntities); + + for (DescriptionEntity entity: data) { + itemMap.put(entity.getId(), new ArrayList<>()); + List statusIds = itemStatusIdsMap.getOrDefault(entity.getId(), new ArrayList<>()); + for (UUID statusId: statusIds) { + itemMap.get(entity.getId()).addAll(descriptionStatuses.stream().filter(x -> x.getId().equals(statusId)).collect(Collectors.toList())); + } + } + + return itemMap; + } + private Map collectUsers(FieldSet fields, List data) throws MyApplicationException { if (fields.isEmpty() || data.isEmpty()) return null; @@ -315,4 +387,22 @@ public class DescriptionBuilder extends BaseBuilder evaluateStatusAuthorizationFlags(AuthorizationService authorizationService, FieldSet statusAuthorizationFlags, DescriptionEntity description) { + List allowed = new ArrayList<>(); + if (statusAuthorizationFlags == null) return allowed; + if (authorizationService == null) return allowed; + if (description == null) return allowed; + + String editPermission = this.customPolicyService.getDescriptionStatusCanEditStatusPermission(description.getStatusId()); + AffiliatedResource affiliatedResource = this.authorizationContentResolver.planAffiliation(description.getPlanId()); + for (String permission : statusAuthorizationFlags.getFields()) { + if (statusAuthorizationFlags.hasField(this.asIndexer(PlanStatusDefinitionAuthorization._edit))) { + Boolean isAllowed = affiliatedResource == null ? this.authorizationService.authorize(editPermission) : this.authorizationService.authorizeAtLeastOne(List.of(affiliatedResource), editPermission); + if (isAllowed) allowed.add(permission); + } + } + + return allowed; + } + } diff --git a/backend/core/src/main/java/org/opencdmp/model/builder/descriptionstatus/DescriptionStatusBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/descriptionstatus/DescriptionStatusBuilder.java index fea2d62ed..cf01a84d1 100644 --- a/backend/core/src/main/java/org/opencdmp/model/builder/descriptionstatus/DescriptionStatusBuilder.java +++ b/backend/core/src/main/java/org/opencdmp/model/builder/descriptionstatus/DescriptionStatusBuilder.java @@ -56,6 +56,7 @@ public class DescriptionStatusBuilder extends BaseBuilder { private final AuthorizationService authorizationService; private final AuthorizationContentResolver authorizationContentResolver; private final TenantScope tenantScope; + private final CustomPolicyService customPolicyService; + private final PlanStatusService planStatusService; private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); @Autowired public PlanBuilder(ConventionService conventionService, QueryFactory queryFactory, - BuilderFactory builderFactory, JsonHandlingService jsonHandlingService, XmlHandlingService xmlHandlingService, AuthorizationService authorizationService, AuthorizationContentResolver authorizationContentResolver, TenantScope tenantScope) { + BuilderFactory builderFactory, JsonHandlingService jsonHandlingService, XmlHandlingService xmlHandlingService, AuthorizationService authorizationService, AuthorizationContentResolver authorizationContentResolver, TenantScope tenantScope, CustomPolicyService customPolicyService, PlanStatusService planStatusService) { super(conventionService, new LoggerService(LoggerFactory.getLogger(PlanBuilder.class))); this.queryFactory = queryFactory; this.builderFactory = builderFactory; @@ -69,6 +78,8 @@ public class PlanBuilder extends BaseBuilder { this.authorizationService = authorizationService; this.authorizationContentResolver = authorizationContentResolver; this.tenantScope = tenantScope; + this.customPolicyService = customPolicyService; + this.planStatusService = planStatusService; } public PlanBuilder authorize(EnumSet values) { @@ -85,6 +96,12 @@ public class PlanBuilder extends BaseBuilder { List models = new ArrayList<>(); + FieldSet statusFields = fields.extractPrefixed(this.asPrefix(Plan._status)); + Map statusItemsMap = this.collectPlanStatuses(statusFields, data); + + FieldSet availableStatusesFields = fields.extractPrefixed(this.asPrefix(Plan._availableStatuses)); + Map> avaialbleStatusesItemsMap = this.collectAvailablePlanStatuses(availableStatusesFields, data); + FieldSet entityDoisFields = fields.extractPrefixed(this.asPrefix(Plan._entityDois)); Map> entityDoisMap = this.collectEntityDois(entityDoisFields, data); @@ -114,14 +131,16 @@ public class PlanBuilder extends BaseBuilder { Set authorizationFlags = this.extractAuthorizationFlags(fields, Plan._authorizationFlags, this.authorizationContentResolver.getPermissionNames()); Map affiliatedResourceMap = authorizationFlags == null || authorizationFlags.isEmpty() ? null : this.authorizationContentResolver.plansAffiliation(data.stream().map(PlanEntity::getId).collect(Collectors.toList())); - + + FieldSet statusAuthorizationFlags = fields.extractPrefixed(this.asPrefix(Plan._statusAuthorizationFlags)); for (PlanEntity d : data) { Plan m = new Plan(); if (fields.hasField(this.asIndexer(Plan._id))) m.setId(d.getId()); if (fields.hasField(this.asIndexer(Plan._tenantId))) m.setTenantId(d.getTenantId()); if (fields.hasField(this.asIndexer(Plan._label))) m.setLabel(d.getLabel()); if (fields.hasField(this.asIndexer(Plan._version))) m.setVersion(d.getVersion()); - if (fields.hasField(this.asIndexer(Plan._status))) m.setStatus(d.getStatus()); + if (!statusFields.isEmpty() && statusItemsMap != null && statusItemsMap.containsKey(d.getStatusId())) m.setStatus(statusItemsMap.get(d.getStatusId())); + if (avaialbleStatusesItemsMap != null && !avaialbleStatusesItemsMap.isEmpty() && avaialbleStatusesItemsMap.containsKey(d.getId())) m.setAvailableStatuses(avaialbleStatusesItemsMap.get(d.getId())); if (fields.hasField(this.asIndexer(Plan._groupId))) m.setGroupId(d.getGroupId()); if (fields.hasField(this.asIndexer(Plan._description))) m.setDescription(d.getDescription()); if (fields.hasField(this.asIndexer(Plan._createdAt))) m.setCreatedAt(d.getCreatedAt()); @@ -150,6 +169,9 @@ public class PlanBuilder extends BaseBuilder { m.setProperties(this.builderFactory.builder(PlanPropertiesBuilder.class).withDefinition(definitionEntityMap != null ? definitionEntityMap.getOrDefault(d.getBlueprintId(), null) : null).authorize(this.authorize).build(planPropertiesFields, propertyDefinition)); } if (affiliatedResourceMap != null && !authorizationFlags.isEmpty()) m.setAuthorizationFlags(this.evaluateAuthorizationFlags(this.authorizationService, authorizationFlags, affiliatedResourceMap.getOrDefault(d.getId(), null))); + if (!statusAuthorizationFlags.isEmpty() && !this.conventionService.isListNullOrEmpty(m.getAvailableStatuses())) { + m.setStatusAuthorizationFlags(this.evaluateStatusAuthorizationFlags(this.authorizationService, statusAuthorizationFlags, d)); + } models.add(m); } this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0)); @@ -157,6 +179,58 @@ public class PlanBuilder extends BaseBuilder { return models; } + private Map collectPlanStatuses(FieldSet fields, List data) throws MyApplicationException { + if (fields.isEmpty() || data.isEmpty()) + return null; + this.logger.debug("checking related - {}", PlanStatus.class.getSimpleName()); + + Map itemMap; + if (!fields.hasOtherField(this.asIndexer(PlanStatus._id))) { + itemMap = this.asEmpty( + data.stream().map(PlanEntity::getStatusId).distinct().collect(Collectors.toList()), + x -> { + PlanStatus item = new PlanStatus(); + item.setId(x); + return item; + }, + PlanStatus::getId); + } else { + FieldSet clone = new BaseFieldSet(fields.getFields()).ensure(PlanStatus._id); + PlanStatusQuery q = this.queryFactory.query(PlanStatusQuery.class).disableTracking().authorize(this.authorize).ids(data.stream().map(PlanEntity::getStatusId).distinct().collect(Collectors.toList())); + itemMap = this.builderFactory.builder(PlanStatusBuilder.class).authorize(this.authorize).asForeignKey(q, clone, PlanStatus::getId); + } + if (!fields.hasField(PlanStatus._id)) { + itemMap.forEach((id, item) -> { + if (item != null) + item.setId(null); + }); + } + + return itemMap; + } + + private Map> collectAvailablePlanStatuses(FieldSet fields, List data) throws MyApplicationException { + if (fields.isEmpty() || data.isEmpty()) return null; + this.logger.debug("checking related - {}", PlanStatus.class.getSimpleName()); + + Map> itemMap = new HashMap<>(); + FieldSet fieldSet = new BaseFieldSet(fields.getFields()).ensure(PlanStatus._id); + Map> itemStatusIdsMap = this.planStatusService.getAuthorizedAvailableStatusIds(data.stream().map(PlanEntity::getId).collect(Collectors.toList())); + + List statusEntities = this.queryFactory.query(PlanStatusQuery.class).authorize(this.authorize).isActives(IsActive.Active).ids(itemStatusIdsMap.values().stream().flatMap(List::stream).distinct().collect(Collectors.toList())).collectAs(fieldSet); + List planStatuses = this.builderFactory.builder(PlanStatusBuilder.class).authorize(this.authorize).build(fieldSet, statusEntities); + + for (PlanEntity entity: data) { + itemMap.put(entity.getId(), new ArrayList<>()); + List statusIds = itemStatusIdsMap.getOrDefault(entity.getId(), new ArrayList<>()); + for (UUID statusId: statusIds) { + itemMap.get(entity.getId()).addAll(planStatuses.stream().filter(x -> x.getId().equals(statusId)).collect(Collectors.toList())); + } + } + + return itemMap; + } + private Map> collectPlanReferences(FieldSet fields, List data) throws MyApplicationException { if (fields.isEmpty() || data.isEmpty()) return null; this.logger.debug("checking related - {}", PlanReference.class.getSimpleName()); @@ -342,4 +416,22 @@ public class PlanBuilder extends BaseBuilder { return itemMap; } + private List evaluateStatusAuthorizationFlags(AuthorizationService authorizationService, FieldSet statusAuthorizationFlags, PlanEntity plan) { + List allowed = new ArrayList<>(); + if (statusAuthorizationFlags == null) return allowed; + if (authorizationService == null) return allowed; + if (plan == null) return allowed; + + String editPermission = this.customPolicyService.getPlanStatusCanEditStatusPermission(plan.getStatusId()); + AffiliatedResource affiliatedResource = this.authorizationContentResolver.planAffiliation(plan.getId()); + for (String permission : statusAuthorizationFlags.getFields()) { + if (statusAuthorizationFlags.hasField(this.asIndexer(PlanStatusDefinitionAuthorization._edit))) { + Boolean isAllowed = affiliatedResource == null ? this.authorizationService.authorize(editPermission) : this.authorizationService.authorizeAtLeastOne(List.of(affiliatedResource), editPermission); + if (isAllowed) allowed.add(permission); + } + } + + return allowed; + } + } diff --git a/backend/core/src/main/java/org/opencdmp/model/builder/planstatus/PlanStatusBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/planstatus/PlanStatusBuilder.java index 71a23b6a6..7bfe20829 100644 --- a/backend/core/src/main/java/org/opencdmp/model/builder/planstatus/PlanStatusBuilder.java +++ b/backend/core/src/main/java/org/opencdmp/model/builder/planstatus/PlanStatusBuilder.java @@ -68,6 +68,8 @@ public class PlanStatusBuilder extends BaseBuilder m.setIsActive(d.getIsActive()); if (fields.hasField(this.asIndexer(PlanStatus._description))) m.setDescription(d.getDescription()); + if (fields.hasField(this.asIndexer(PlanStatus._action))) + m.setAction(d.getAction()); if (fields.hasField(this.asIndexer(PlanStatus._internalStatus))) m.setInternalStatus(d.getInternalStatus()); if (fields.hasField(this.asIndexer(PlanStatus._hash))) diff --git a/backend/core/src/main/java/org/opencdmp/model/builder/planstatus/PlanStatusDefinitionBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/planstatus/PlanStatusDefinitionBuilder.java index b4d6c8181..b342b2b9b 100644 --- a/backend/core/src/main/java/org/opencdmp/model/builder/planstatus/PlanStatusDefinitionBuilder.java +++ b/backend/core/src/main/java/org/opencdmp/model/builder/planstatus/PlanStatusDefinitionBuilder.java @@ -53,6 +53,8 @@ public class PlanStatusDefinitionBuilder extends BaseBuilder availableStatuses; + public static final String _availableStatuses = "availableStatuses"; + + private List statusAuthorizationFlags; + public static final String _statusAuthorizationFlags = "statusAuthorizationFlags"; + private Boolean belongsToCurrentTenant; public static final String _belongsToCurrentTenant = "belongsToCurrentTenant"; @@ -231,6 +237,22 @@ public class Description { this.authorizationFlags = authorizationFlags; } + public List getAvailableStatuses() { + return availableStatuses; + } + + public void setAvailableStatuses(List availableStatuses) { + this.availableStatuses = availableStatuses; + } + + public List getStatusAuthorizationFlags() { + return statusAuthorizationFlags; + } + + public void setStatusAuthorizationFlags(List statusAuthorizationFlags) { + this.statusAuthorizationFlags = statusAuthorizationFlags; + } + public Boolean getBelongsToCurrentTenant() { return belongsToCurrentTenant; } diff --git a/backend/core/src/main/java/org/opencdmp/model/descriptionstatus/DescriptionStatus.java b/backend/core/src/main/java/org/opencdmp/model/descriptionstatus/DescriptionStatus.java index 3279b5a86..45c7094d8 100644 --- a/backend/core/src/main/java/org/opencdmp/model/descriptionstatus/DescriptionStatus.java +++ b/backend/core/src/main/java/org/opencdmp/model/descriptionstatus/DescriptionStatus.java @@ -16,6 +16,9 @@ public class DescriptionStatus { public final static String _description = "description"; private String description; + public static final String _action = "action"; + private String action; + public final static String _createdAt = "createdAt"; private Instant createdAt; @@ -46,6 +49,14 @@ public class DescriptionStatus { public String getDescription() { return this.description; } public void setDescription(String description) { this.description = description; } + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + public Instant getCreatedAt() { return this.createdAt; } public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } diff --git a/backend/core/src/main/java/org/opencdmp/model/descriptionstatus/DescriptionStatusDefinition.java b/backend/core/src/main/java/org/opencdmp/model/descriptionstatus/DescriptionStatusDefinition.java index 340f8f499..403049942 100644 --- a/backend/core/src/main/java/org/opencdmp/model/descriptionstatus/DescriptionStatusDefinition.java +++ b/backend/core/src/main/java/org/opencdmp/model/descriptionstatus/DescriptionStatusDefinition.java @@ -1,11 +1,26 @@ package org.opencdmp.model.descriptionstatus; +import org.opencdmp.commons.enums.DescriptionStatusAvailableActionType; + +import java.util.List; + public class DescriptionStatusDefinition { public final static String _authorization = "authorization"; private DescriptionStatusDefinitionAuthorization authorization; + public final static String _availableActions = "availableActions"; + private List availableActions; + public DescriptionStatusDefinitionAuthorization getAuthorization() { return this.authorization; } public void setAuthorization(DescriptionStatusDefinitionAuthorization authorization) { this.authorization = authorization; } + + public List getAvailableActions() { + return availableActions; + } + + public void setAvailableActions(List availableActions) { + this.availableActions = availableActions; + } } 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/model/persist/PlanPersist.java b/backend/core/src/main/java/org/opencdmp/model/persist/PlanPersist.java index 4173d902b..225bc8b80 100644 --- a/backend/core/src/main/java/org/opencdmp/model/persist/PlanPersist.java +++ b/backend/core/src/main/java/org/opencdmp/model/persist/PlanPersist.java @@ -36,9 +36,9 @@ public class PlanPersist { public static final String _label = "label"; - private PlanStatus status; + private UUID statusId; - public static final String _status = "status"; + public static final String _statusId = "statusId"; private PlanPropertiesPersist properties; @@ -84,12 +84,12 @@ public class PlanPersist { this.label = label; } - public PlanStatus getStatus() { - return this.status; + public UUID getStatusId() { + return statusId; } - public void setStatus(PlanStatus status) { - this.status = status; + public void setStatusId(UUID statusId) { + this.statusId = statusId; } public PlanPropertiesPersist getProperties() { @@ -186,9 +186,10 @@ public class PlanPersist { @Override protected List specifications(PlanPersist item) { PlanBlueprintEntity planBlueprintEntity = null; + PlanStatusEntity statusEntity = null; try { planBlueprintEntity = this.isValidGuid(item.getBlueprint()) ? this.entityManager.find(PlanBlueprintEntity.class, item.getBlueprint(), true) : null; - + statusEntity = this.isValidGuid(item.getStatusId()) ? this.entityManager.find(PlanStatusEntity.class, item.getStatusId(), true) : null; } catch (InvalidApplicationException e) { throw new RuntimeException(e); } @@ -203,6 +204,7 @@ public class PlanPersist { accessFieldLabel = this.getSystemFieldLabel(definition, PlanBlueprintSystemFieldType.AccessRights); } + PlanStatusEntity finalStatusEntity = statusEntity; return Arrays.asList( this.spec() .iff(() -> this.isValidGuid(item.getId())) @@ -220,40 +222,41 @@ public class PlanPersist { .must(() -> this.lessEqualLength(item.getLabel(), PlanEntity._labelLength)) .failOn(PlanPersist._label).failWith(this.messageSource.getMessage("Validation_MaxLength", new Object[]{PlanPersist._label}, LocaleContextHolder.getLocale())), this.spec() - .iff(() -> item.getStatus() == PlanStatus.Finalized) + .iff(() -> finalStatusEntity != null && finalStatusEntity.getInternalStatus() != null && finalStatusEntity.getInternalStatus() == PlanStatus.Finalized) .must(() -> !this.isEmpty(item.getDescription())) .failOn(PlanPersist._description).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{PlanPersist._description}, LocaleContextHolder.getLocale())), this.spec() - .must(() -> !this.isNull(item.getStatus())) - .failOn(PlanPersist._status).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{PlanPersist._status}, LocaleContextHolder.getLocale())), + .iff(() -> this.isValidGuid(item.getId())) + .must(() -> !this.isNull(item.getStatusId())) + .failOn(PlanPersist._statusId).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{PlanPersist._statusId}, LocaleContextHolder.getLocale())), this.spec() - .iff(() -> item.getStatus() == PlanStatus.Finalized) + .iff(() -> finalStatusEntity != null && finalStatusEntity.getInternalStatus() != null && finalStatusEntity.getInternalStatus() == PlanStatus.Finalized) .must(() -> this.isValidGuid(item.getBlueprint())) .failOn(PlanPersist._blueprint).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{PlanPersist._blueprint}, LocaleContextHolder.getLocale())), this.spec() - .iff(() -> item.getStatus() == PlanStatus.Finalized) + .iff(() -> finalStatusEntity != null && finalStatusEntity.getInternalStatus() != null && finalStatusEntity.getInternalStatus() == PlanStatus.Finalized) .must(() -> !this.isNull(item.getProperties())) .failOn(PlanPersist._properties).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{PlanPersist._properties}, LocaleContextHolder.getLocale())), this.spec() - .iff(() -> item.getStatus() == PlanStatus.Finalized) + .iff(() -> finalStatusEntity != null && finalStatusEntity.getInternalStatus() != null && finalStatusEntity.getInternalStatus() == PlanStatus.Finalized) .must(() -> this.isDescriptionTemplateMultiplicityValid(finalPlanBlueprintEntity, item.getId())) .failOn(PlanPersist._descriptionTemplates).failWith(this.messageSource.getMessage("Validation.InvalidDescriptionTemplateMultiplicityOnPlan", new Object[]{PlanPersist._descriptionTemplates}, LocaleContextHolder.getLocale())), this.refSpec() - .iff(() -> !this.isNull(item.getProperties())) + .iff(() -> !this.isNull(item.getProperties()) && finalStatusEntity != null) .on(PlanPersist._properties) .over(item.getProperties()) - .using(() -> this.validatorFactory.validator(PlanPropertiesPersist.PlanPropertiesPersistValidator.class).setStatus(item.getStatus()).withDefinition(definition)), + .using(() -> this.validatorFactory.validator(PlanPropertiesPersist.PlanPropertiesPersistValidator.class).setStatus(finalStatusEntity.getInternalStatus()).withDefinition(definition)), this.spec() - .iff(() -> item.getStatus() == PlanStatus.Finalized) + .iff(() -> finalStatusEntity != null && finalStatusEntity.getInternalStatus() != null && finalStatusEntity.getInternalStatus() == PlanStatus.Finalized) .must(() -> !this.isNull(item.getLanguage())) .failOn(PlanPersist._language).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{languageFieldLabel != null ? languageFieldLabel : PlanBlueprintSystemFieldType.Language.name()}, LocaleContextHolder.getLocale())), this.spec() - .iff(() -> item.getStatus() == PlanStatus.Finalized) + .iff(() -> finalStatusEntity != null && finalStatusEntity.getInternalStatus() != null && finalStatusEntity.getInternalStatus() == PlanStatus.Finalized) .must(() -> !this.isNull(item.getAccessType())) .failOn(PlanPersist._accessType).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{accessFieldLabel != null ? accessFieldLabel : PlanBlueprintSystemFieldType.AccessRights.name()}, LocaleContextHolder.getLocale())), this.spec() - .iff(() -> item.getStatus() == PlanStatus.Finalized) + .iff(() -> finalStatusEntity != null && finalStatusEntity.getInternalStatus() != null && finalStatusEntity.getInternalStatus() == PlanStatus.Finalized) .must(() -> !this.isListNullOrEmpty(item.getDescriptionTemplates())) .failOn(PlanPersist._descriptionTemplates).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{PlanPersist._descriptionTemplates}, LocaleContextHolder.getLocale())), this.navSpec() diff --git a/backend/core/src/main/java/org/opencdmp/model/persist/descriptionstatus/DescriptionStatusDefinitionAuthorizationItemPersist.java b/backend/core/src/main/java/org/opencdmp/model/persist/descriptionstatus/DescriptionStatusDefinitionAuthorizationItemPersist.java index f402d41b3..2fda7fca8 100644 --- a/backend/core/src/main/java/org/opencdmp/model/persist/descriptionstatus/DescriptionStatusDefinitionAuthorizationItemPersist.java +++ b/backend/core/src/main/java/org/opencdmp/model/persist/descriptionstatus/DescriptionStatusDefinitionAuthorizationItemPersist.java @@ -62,11 +62,6 @@ public class DescriptionStatusDefinitionAuthorizationItemPersist { @Override protected List specifications(DescriptionStatusDefinitionAuthorizationItemPersist item) { return Arrays.asList( - this.spec() - .must(() -> !this.isListNullOrEmpty(item.getRoles())) - .failOn(PlanStatusDefinitionAuthorizationItemPersist._roles).failWith(messageSource.getMessage("Validation_Required", new Object[]{PlanStatusDefinitionAuthorizationItemPersist._roles}, LocaleContextHolder.getLocale())), this.spec() - .must(() -> !this.isListNullOrEmpty(item.getPlanRoles())) - .failOn(PlanStatusDefinitionAuthorizationItemPersist._planRoles).failWith(messageSource.getMessage("Validation_Required", new Object[]{PlanStatusDefinitionAuthorizationItemPersist._planRoles}, LocaleContextHolder.getLocale())) ); } } diff --git a/backend/core/src/main/java/org/opencdmp/model/persist/descriptionstatus/DescriptionStatusDefinitionPersist.java b/backend/core/src/main/java/org/opencdmp/model/persist/descriptionstatus/DescriptionStatusDefinitionPersist.java index 6e2121027..7f406d40f 100644 --- a/backend/core/src/main/java/org/opencdmp/model/persist/descriptionstatus/DescriptionStatusDefinitionPersist.java +++ b/backend/core/src/main/java/org/opencdmp/model/persist/descriptionstatus/DescriptionStatusDefinitionPersist.java @@ -2,10 +2,10 @@ package org.opencdmp.model.persist.descriptionstatus; import gr.cite.tools.validation.ValidatorFactory; import gr.cite.tools.validation.specification.Specification; +import org.opencdmp.commons.enums.DescriptionStatusAvailableActionType; import org.opencdmp.commons.validation.BaseValidator; import org.opencdmp.convention.ConventionService; import org.opencdmp.errorcode.ErrorThesaurusProperties; -import org.opencdmp.model.descriptionstatus.DescriptionStatusDefinitionAuthorization; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Scope; @@ -20,9 +20,19 @@ public class DescriptionStatusDefinitionPersist { private DescriptionStatusDefinitionAuthorizationPersist authorization; public final static String _authorization = "authorization"; + private List availableActions; + public final static String _availableActions = "availableActions"; + public DescriptionStatusDefinitionAuthorizationPersist getAuthorization() { return authorization; } public void setAuthorization(DescriptionStatusDefinitionAuthorizationPersist authorization) { this.authorization = authorization; } + public List getAvailableActions() { + return availableActions; + } + + public void setAvailableActions(List availableActions) { + this.availableActions = availableActions; + } @Component(DescriptionStatusDefinitionPersistValidator.ValidatorName) @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) diff --git a/backend/core/src/main/java/org/opencdmp/model/persist/descriptionstatus/DescriptionStatusPersist.java b/backend/core/src/main/java/org/opencdmp/model/persist/descriptionstatus/DescriptionStatusPersist.java index db27eb606..e7ac7deb0 100644 --- a/backend/core/src/main/java/org/opencdmp/model/persist/descriptionstatus/DescriptionStatusPersist.java +++ b/backend/core/src/main/java/org/opencdmp/model/persist/descriptionstatus/DescriptionStatusPersist.java @@ -27,6 +27,9 @@ public class DescriptionStatusPersist { private String description; public final static String _description = "description"; + private String action; + public static final String _action = "action"; + private org.opencdmp.commons.enums.DescriptionStatus internalStatus; public final static String _internalStatus = "internalStatus"; @@ -46,6 +49,14 @@ public class DescriptionStatusPersist { public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + public org.opencdmp.commons.enums.DescriptionStatus getInternalStatus() { return internalStatus; } public void setInternalStatus(org.opencdmp.commons.enums.DescriptionStatus internalStatus) { this.internalStatus = internalStatus; } @@ -93,6 +104,10 @@ public class DescriptionStatusPersist { .iff(() -> !this.isEmpty(item.getName())) .must(() -> this.lessEqualLength(item.getName(), DescriptionStatusEntity._nameLength)) .failOn(DescriptionStatusPersist._name).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{DescriptionStatusPersist._name}, LocaleContextHolder.getLocale())), + this.spec() + .iff(() -> !this.isEmpty(item.getAction())) + .must(() -> this.lessEqualLength(item.getAction(), DescriptionStatusEntity._actionLength)) + .failOn(DescriptionStatusPersist._action).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{DescriptionStatusPersist._action}, LocaleContextHolder.getLocale())), this.spec() .iff(() -> item.getInternalStatus() == org.opencdmp.commons.enums.DescriptionStatus.Finalized) .must(() -> !this.isNull(item.getDefinition())) diff --git a/backend/core/src/main/java/org/opencdmp/model/persist/planstatus/PlanStatusDefinitionAuthorizationItemPersist.java b/backend/core/src/main/java/org/opencdmp/model/persist/planstatus/PlanStatusDefinitionAuthorizationItemPersist.java index 10e4c5df8..1c30f022c 100644 --- a/backend/core/src/main/java/org/opencdmp/model/persist/planstatus/PlanStatusDefinitionAuthorizationItemPersist.java +++ b/backend/core/src/main/java/org/opencdmp/model/persist/planstatus/PlanStatusDefinitionAuthorizationItemPersist.java @@ -66,11 +66,6 @@ public class PlanStatusDefinitionAuthorizationItemPersist { @Override protected List specifications(PlanStatusDefinitionAuthorizationItemPersist item) { return Arrays.asList( - this.spec() - .must(() -> !this.isListNullOrEmpty(item.getRoles())) - .failOn(PlanStatusDefinitionAuthorizationItemPersist._roles).failWith(messageSource.getMessage("Validation_Required", new Object[]{PlanStatusDefinitionAuthorizationItemPersist._roles}, LocaleContextHolder.getLocale())), this.spec() - .must(() -> !this.isListNullOrEmpty(item.getPlanRoles())) - .failOn(PlanStatusDefinitionAuthorizationItemPersist._planRoles).failWith(messageSource.getMessage("Validation_Required", new Object[]{PlanStatusDefinitionAuthorizationItemPersist._planRoles}, LocaleContextHolder.getLocale())) ); } } diff --git a/backend/core/src/main/java/org/opencdmp/model/persist/planstatus/PlanStatusDefinitionPersist.java b/backend/core/src/main/java/org/opencdmp/model/persist/planstatus/PlanStatusDefinitionPersist.java index 646f2ad46..ba5cbd03f 100644 --- a/backend/core/src/main/java/org/opencdmp/model/persist/planstatus/PlanStatusDefinitionPersist.java +++ b/backend/core/src/main/java/org/opencdmp/model/persist/planstatus/PlanStatusDefinitionPersist.java @@ -2,6 +2,7 @@ package org.opencdmp.model.persist.planstatus; import gr.cite.tools.validation.ValidatorFactory; import gr.cite.tools.validation.specification.Specification; +import org.opencdmp.commons.enums.PlanStatusAvailableActionType; import org.opencdmp.commons.validation.BaseValidator; import org.opencdmp.convention.ConventionService; import org.opencdmp.errorcode.ErrorThesaurusProperties; @@ -19,11 +20,21 @@ public class PlanStatusDefinitionPersist { public final static String _authorization = "authorization"; private PlanStatusDefinitionAuthorizationPersist authorization = null; + public final static String _availableActions = "availableActions"; + private List availableActions; public PlanStatusDefinitionAuthorizationPersist getAuthorization() { return authorization; } public void setAuthorization(PlanStatusDefinitionAuthorizationPersist authorization) { this.authorization = authorization; } + public List getAvailableActions() { + return availableActions; + } + + public void setAvailableActions(List availableActions) { + this.availableActions = availableActions; + } + @Component(PlanStatusDefinitionPersist.PlanStatusDefinitionPersistValidator.ValidatorName) @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public static class PlanStatusDefinitionPersistValidator extends BaseValidator { diff --git a/backend/core/src/main/java/org/opencdmp/model/persist/planstatus/PlanStatusPersist.java b/backend/core/src/main/java/org/opencdmp/model/persist/planstatus/PlanStatusPersist.java index 65f6781f1..e20b8dea0 100644 --- a/backend/core/src/main/java/org/opencdmp/model/persist/planstatus/PlanStatusPersist.java +++ b/backend/core/src/main/java/org/opencdmp/model/persist/planstatus/PlanStatusPersist.java @@ -26,6 +26,9 @@ public class PlanStatusPersist { private String description; public static final String _description = "description"; + private String action; + public static final String _action = "action"; + private org.opencdmp.commons.enums.PlanStatus internalStatus; public static final String _internalStatus = "internalStatus"; @@ -53,6 +56,14 @@ public class PlanStatusPersist { public void setDescription(String description) { this.description = description; } + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + public org.opencdmp.commons.enums.PlanStatus getInternalStatus() { return this.internalStatus; } public void setInternalStatus(org.opencdmp.commons.enums.PlanStatus internalStatus) { this.internalStatus = internalStatus; } @@ -105,6 +116,10 @@ public class PlanStatusPersist { .iff(() -> !this.isEmpty(item.getName())) .must(() -> this.lessEqualLength(item.getName(), PlanStatusEntity._nameLength)) .failOn(org.opencdmp.model.persist.planstatus.PlanStatusPersist._name).failWith(this.messageSource.getMessage("Validation_MaxLength", new Object[]{org.opencdmp.model.persist.planstatus.PlanStatusPersist._name}, LocaleContextHolder.getLocale())), + this.spec() + .iff(() -> !this.isEmpty(item.getAction())) + .must(() -> this.lessEqualLength(item.getAction(), PlanStatusEntity._actionLength)) + .failOn(PlanStatusPersist._action).failWith(this.messageSource.getMessage("Validation_MaxLength", new Object[]{org.opencdmp.model.persist.planstatus.PlanStatusPersist._action}, LocaleContextHolder.getLocale())), this.spec() .iff(() -> item.getInternalStatus() == org.opencdmp.commons.enums.PlanStatus.Finalized) .must(() -> !this.isNull(item.getDefinition())) diff --git a/backend/core/src/main/java/org/opencdmp/model/plan/Plan.java b/backend/core/src/main/java/org/opencdmp/model/plan/Plan.java index 750b59cee..842840edd 100644 --- a/backend/core/src/main/java/org/opencdmp/model/plan/Plan.java +++ b/backend/core/src/main/java/org/opencdmp/model/plan/Plan.java @@ -1,13 +1,13 @@ package org.opencdmp.model.plan; import org.opencdmp.commons.enums.PlanAccessType; -import org.opencdmp.commons.enums.PlanStatus; import org.opencdmp.commons.enums.PlanVersionStatus; import org.opencdmp.commons.enums.IsActive; import org.opencdmp.model.*; import org.opencdmp.model.description.Description; import org.opencdmp.model.planblueprint.PlanBlueprint; import org.opencdmp.model.planreference.PlanReference; +import org.opencdmp.model.planstatus.PlanStatus; import org.opencdmp.model.user.User; import java.time.Instant; @@ -97,6 +97,11 @@ public class Plan { private List otherPlanVersions; public static final String _otherPlanVersions = "otherPlanVersions"; + private List availableStatuses; + public static final String _availableStatuses = "availableStatuses"; + + private List statusAuthorizationFlags; + public static final String _statusAuthorizationFlags = "statusAuthorizationFlags"; private Boolean belongsToCurrentTenant; public static final String _belongsToCurrentTenant = "belongsToCurrentTenant"; @@ -318,4 +323,20 @@ public class Plan { public void setOtherPlanVersions(List otherPlanVersions) { this.otherPlanVersions = otherPlanVersions; } + + public List getAvailableStatuses() { + return availableStatuses; + } + + public void setAvailableStatuses(List availableStatuses) { + this.availableStatuses = availableStatuses; + } + + public List getStatusAuthorizationFlags() { + return statusAuthorizationFlags; + } + + public void setStatusAuthorizationFlags(List statusAuthorizationFlags) { + this.statusAuthorizationFlags = statusAuthorizationFlags; + } } diff --git a/backend/core/src/main/java/org/opencdmp/model/planstatus/PlanStatus.java b/backend/core/src/main/java/org/opencdmp/model/planstatus/PlanStatus.java index b5be3d177..606fd2613 100644 --- a/backend/core/src/main/java/org/opencdmp/model/planstatus/PlanStatus.java +++ b/backend/core/src/main/java/org/opencdmp/model/planstatus/PlanStatus.java @@ -16,6 +16,9 @@ public class PlanStatus { public final static String _description = "description"; private String description; + public static final String _action = "action"; + private String action; + public final static String _createdAt = "createdAt"; private Instant createdAt; @@ -61,6 +64,14 @@ public class PlanStatus { public void setDescription(String description) { this.description = description; } + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + public PlanStatusDefinition getDefinition() { return this.definition; } diff --git a/backend/core/src/main/java/org/opencdmp/model/planstatus/PlanStatusDefinition.java b/backend/core/src/main/java/org/opencdmp/model/planstatus/PlanStatusDefinition.java index f310f5bc6..c49049dac 100644 --- a/backend/core/src/main/java/org/opencdmp/model/planstatus/PlanStatusDefinition.java +++ b/backend/core/src/main/java/org/opencdmp/model/planstatus/PlanStatusDefinition.java @@ -1,12 +1,27 @@ package org.opencdmp.model.planstatus; +import org.opencdmp.commons.enums.PlanStatusAvailableActionType; + +import java.util.List; + public class PlanStatusDefinition { public final static String _authorization = "authorization"; private PlanStatusDefinitionAuthorization authorization; + public final static String _availableActions = "availableActions"; + private List availableActions; + public PlanStatusDefinitionAuthorization getAuthorization() { return this.authorization; } public void setAuthorization(PlanStatusDefinitionAuthorization authorization) { this.authorization = authorization; } + + public List getAvailableActions() { + return availableActions; + } + + public void setAvailableActions(List availableActions) { + this.availableActions = availableActions; + } } 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..b6d666199 100644 --- a/backend/core/src/main/java/org/opencdmp/query/DescriptionQuery.java +++ b/backend/core/src/main/java/org/opencdmp/query/DescriptionQuery.java @@ -58,7 +58,7 @@ public class DescriptionQuery extends QueryBase { private Collection isActives; - private Collection statuses; + private Collection statusIds; private Collection planIds; @@ -72,6 +72,8 @@ public class DescriptionQuery extends QueryBase { private Collection planDescriptionTemplateIds; + private DescriptionStatusQuery descriptionStatusQuery; + private final TenantEntityManager tenantEntityManager; public DescriptionQuery(UserScope userScope, AuthorizationService authService, QueryUtilsService queryUtilsService, TenantEntityManager tenantEntityManager) { this.userScope = userScope; @@ -210,18 +212,18 @@ public class DescriptionQuery extends QueryBase { return this; } - public DescriptionQuery statuses(DescriptionStatus value) { - this.statuses = List.of(value); + public DescriptionQuery statusIds(UUID value) { + this.statusIds = List.of(value); return this; } - public DescriptionQuery statuses(DescriptionStatus... value) { - this.statuses = Arrays.asList(value); + public DescriptionQuery statusIds(UUID... value) { + this.statusIds = Arrays.asList(value); return this; } - public DescriptionQuery statuses(Collection values) { - this.statuses = values; + public DescriptionQuery statusIds(Collection values) { + this.statusIds = values; return this; } @@ -250,6 +252,11 @@ public class DescriptionQuery extends QueryBase { return this; } + public DescriptionQuery descriptionStatusSubQuery(DescriptionStatusQuery subQuery) { + this.descriptionStatusQuery = subQuery; + return this; + } + @Override protected EntityManager entityManager(){ return this.tenantEntityManager.getEntityManager(); @@ -261,7 +268,7 @@ public class DescriptionQuery extends QueryBase { this.isEmpty(this.ids) || this.isEmpty(this.isActives) || this.isEmpty(this.createdByIds) || this.isEmpty(this.excludedIds) || this.isFalseQuery(this.planQuery) || - this.isEmpty(this.statuses) || this.isFalseQuery(this.planDescriptionTemplateQuery); + this.isEmpty(this.statusIds) || this.isFalseQuery(this.planDescriptionTemplateQuery); } @Override @@ -349,9 +356,9 @@ public class DescriptionQuery extends QueryBase { inClause.value(item); predicates.add(inClause); } - if (this.statuses != null) { - CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(DescriptionEntity._status)); - for (DescriptionStatus item : this.statuses) + if (this.statusIds != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(DescriptionEntity._statusId)); + for (UUID item : this.statusIds) inClause.value(item); predicates.add(inClause); } @@ -381,6 +388,10 @@ public class DescriptionQuery extends QueryBase { inClause.value(item); predicates.add(inClause); } + if (this.descriptionStatusQuery != null) { + QueryContext subQuery = this.applySubQuery(this.descriptionStatusQuery, queryContext, UUID.class, descriptionStatusEntityRoot -> descriptionStatusEntityRoot.get(DescriptionStatusEntity._id)); + predicates.add(queryContext.CriteriaBuilder.in(queryContext.Root.get(DescriptionEntity._statusId)).value(subQuery.Query)); + } if (!predicates.isEmpty()) { Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); @@ -399,7 +410,9 @@ public class DescriptionQuery extends QueryBase { else if (item.prefix(Description._properties)) return DescriptionEntity._properties; else if (item.match(Description._status) || item.match(PublicDescription._status)) - return DescriptionEntity._status; + return DescriptionEntity._statusId; + 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 +457,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/query/DescriptionStatusQuery.java b/backend/core/src/main/java/org/opencdmp/query/DescriptionStatusQuery.java index 486bc155c..4818a4842 100644 --- a/backend/core/src/main/java/org/opencdmp/query/DescriptionStatusQuery.java +++ b/backend/core/src/main/java/org/opencdmp/query/DescriptionStatusQuery.java @@ -188,6 +188,8 @@ public class DescriptionStatusQuery extends QueryBase { return DescriptionStatusEntity._name; else if (item.match(DescriptionStatus._description)) return DescriptionStatusEntity._description; + else if (item.match(DescriptionStatus._action)) + return DescriptionStatusEntity._action; else if (item.match(DescriptionStatus._createdAt)) return DescriptionStatusEntity._createdAt; else if (item.match(DescriptionStatus._updatedAt)) @@ -200,6 +202,8 @@ public class DescriptionStatusQuery extends QueryBase { return DescriptionStatusEntity._internalStatus; else if (item.match(DescriptionStatusEntity._definition)) return DescriptionStatusEntity._definition; + else if (item.prefix(DescriptionStatusEntity._definition)) + return DescriptionStatusEntity._definition; else return null; } @@ -210,6 +214,7 @@ public class DescriptionStatusQuery extends QueryBase { item.setId(QueryBase.convertSafe(tuple, columns, DescriptionStatusEntity._id, UUID.class)); item.setName(QueryBase.convertSafe(tuple, columns, DescriptionStatusEntity._name, String.class)); item.setDescription(QueryBase.convertSafe(tuple, columns, DescriptionStatusEntity._description, String.class)); + item.setAction(QueryBase.convertSafe(tuple, columns, DescriptionStatusEntity._action, String.class)); item.setTenantId(QueryBase.convertSafe(tuple, columns, DescriptionStatusEntity._tenantId, UUID.class)); item.setCreatedAt(QueryBase.convertSafe(tuple, columns, DescriptionStatusEntity._createdAt, Instant.class)); item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, DescriptionStatusEntity._updatedAt, Instant.class)); diff --git a/backend/core/src/main/java/org/opencdmp/query/PlanQuery.java b/backend/core/src/main/java/org/opencdmp/query/PlanQuery.java index 6b195c891..25fe42e6e 100644 --- a/backend/core/src/main/java/org/opencdmp/query/PlanQuery.java +++ b/backend/core/src/main/java/org/opencdmp/query/PlanQuery.java @@ -39,7 +39,7 @@ public class PlanQuery extends QueryBase { private Collection isActives; - private Collection statuses; + private Collection statusIds; private Collection versionStatuses; @@ -61,6 +61,8 @@ public class PlanQuery extends QueryBase { private EntityDoiQuery entityDoiQuery; + private PlanStatusQuery planStatusQuery; + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); private final UserScope userScope; @@ -177,18 +179,18 @@ public class PlanQuery extends QueryBase { return this; } - public PlanQuery statuses(PlanStatus value) { - this.statuses = List.of(value); + public PlanQuery statusIds(UUID value) { + this.statusIds = List.of(value); return this; } - public PlanQuery statuses(PlanStatus... value) { - this.statuses = Arrays.asList(value); + public PlanQuery statusIds(UUID... value) { + this.statusIds = Arrays.asList(value); return this; } - public PlanQuery statuses(Collection values) { - this.statuses = values; + public PlanQuery statusIds(Collection values) { + this.statusIds = values; return this; } @@ -253,6 +255,11 @@ public class PlanQuery extends QueryBase { return this; } + public PlanQuery planStatusSubQuery(PlanStatusQuery subQuery) { + this.planStatusQuery = subQuery; + return this; + } + public PlanQuery authorize(EnumSet values) { this.authorize = values; return this; @@ -275,7 +282,7 @@ public class PlanQuery extends QueryBase { @Override protected Boolean isFalseQuery() { - return this.isEmpty(this.ids) || this.isEmpty(this.creatorIds) || this.isEmpty(this.isActives) || this.isEmpty(this.versionStatuses) || this.isEmpty(this.excludedIds) || this.isEmpty(this.accessTypes) || this.isEmpty(this.statuses) || this.isFalseQuery(this.planDescriptionTemplateQuery) || this.isFalseQuery(this.planUserQuery); + return this.isEmpty(this.ids) || this.isEmpty(this.creatorIds) || this.isEmpty(this.isActives) || this.isEmpty(this.versionStatuses) || this.isEmpty(this.excludedIds) || this.isEmpty(this.accessTypes) || this.isEmpty(this.statusIds) || this.isFalseQuery(this.planDescriptionTemplateQuery) || this.isFalseQuery(this.planUserQuery); } @Override @@ -347,9 +354,9 @@ public class PlanQuery extends QueryBase { inClause.value(item); predicates.add(inClause); } - if (this.statuses != null) { - CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(PlanEntity._status)); - for (PlanStatus item : this.statuses) + if (this.statusIds != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(PlanEntity._statusId)); + for (UUID item : this.statusIds) inClause.value(item); predicates.add(inClause); } @@ -407,6 +414,11 @@ public class PlanQuery extends QueryBase { predicates.add(queryContext.CriteriaBuilder.in(queryContext.Root.get(PlanEntity._id)).value(subQuery.Query)); } + if (this.planStatusQuery != null) { + QueryContext subQuery = this.applySubQuery(this.planStatusQuery, queryContext, UUID.class, planStatusEntityRoot -> planStatusEntityRoot.get(PlanStatusEntity._id)); + predicates.add(queryContext.CriteriaBuilder.in(queryContext.Root.get(PlanEntity._statusId)).value(subQuery.Query)); + } + if (!predicates.isEmpty()) { Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); return queryContext.CriteriaBuilder.and(predicatesArray); @@ -424,7 +436,9 @@ public class PlanQuery extends QueryBase { else if (item.match(Plan._version) || item.match(PublicPlan._version)) return PlanEntity._version; else if (item.match(Plan._status)) - return PlanEntity._status; + return PlanEntity._statusId; + else if (item.prefix(Plan._status)) + return PlanEntity._statusId; else if (item.match(Plan._properties)) return PlanEntity._properties; else if (item.prefix(Plan._properties)) @@ -475,6 +489,7 @@ public class PlanQuery extends QueryBase { item.setLabel(QueryBase.convertSafe(tuple, columns, PlanEntity._label, String.class)); item.setVersion(QueryBase.convertSafe(tuple, columns, PlanEntity._version, Short.class)); item.setStatus(QueryBase.convertSafe(tuple, columns, PlanEntity._status, PlanStatus.class)); + item.setStatusId(QueryBase.convertSafe(tuple, columns, PlanEntity._statusId, UUID.class)); item.setVersionStatus(QueryBase.convertSafe(tuple, columns, PlanEntity._versionStatus, PlanVersionStatus.class)); item.setProperties(QueryBase.convertSafe(tuple, columns, PlanEntity._properties, String.class)); item.setGroupId(QueryBase.convertSafe(tuple, columns, PlanEntity._groupId, UUID.class)); diff --git a/backend/core/src/main/java/org/opencdmp/query/PlanStatusQuery.java b/backend/core/src/main/java/org/opencdmp/query/PlanStatusQuery.java index 4670f010f..a4125d121 100644 --- a/backend/core/src/main/java/org/opencdmp/query/PlanStatusQuery.java +++ b/backend/core/src/main/java/org/opencdmp/query/PlanStatusQuery.java @@ -36,6 +36,7 @@ public class PlanStatusQuery extends QueryBase { private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); + public PlanStatusQuery like(String value) { this.like = value; return this; @@ -187,8 +188,12 @@ public class PlanStatusQuery extends QueryBase { return PlanStatusEntity._description; else if (item.match(PlanStatus._name)) return PlanStatusEntity._name; + else if (item.match(PlanStatus._action)) + return PlanStatusEntity._action; else if (item.match(PlanStatus._internalStatus)) return PlanStatusEntity._internalStatus; + else if (item.prefix(PlanStatus._definition)) + return PlanStatusEntity._definition; else if (item.match(PlanStatus._definition)) return PlanStatusEntity._definition; else if (item.match(PlanStatus._createdAt)) @@ -211,6 +216,7 @@ public class PlanStatusQuery extends QueryBase { item.setDescription(QueryBase.convertSafe(tuple, columns, PlanStatusEntity._description, String.class)); item.setDefinition(QueryBase.convertSafe(tuple, columns, PlanStatusEntity._definition, String.class)); item.setName(QueryBase.convertSafe(tuple, columns, PlanStatusEntity._name, String.class)); + item.setAction(QueryBase.convertSafe(tuple, columns, PlanStatusEntity._action, String.class)); item.setInternalStatus(QueryBase.convertSafe(tuple, columns, PlanStatusEntity._internalStatus, org.opencdmp.commons.enums.PlanStatus.class)); item.setCreatedAt(QueryBase.convertSafe(tuple, columns, PlanStatusEntity._createdAt, Instant.class)); item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, PlanStatusEntity._updatedAt, Instant.class)); diff --git a/backend/core/src/main/java/org/opencdmp/query/lookup/DescriptionLookup.java b/backend/core/src/main/java/org/opencdmp/query/lookup/DescriptionLookup.java index 94502c3b3..02efe005e 100644 --- a/backend/core/src/main/java/org/opencdmp/query/lookup/DescriptionLookup.java +++ b/backend/core/src/main/java/org/opencdmp/query/lookup/DescriptionLookup.java @@ -3,7 +3,6 @@ package org.opencdmp.query.lookup; import gr.cite.tools.data.query.Lookup; import gr.cite.tools.data.query.QueryFactory; import org.apache.commons.lang3.StringUtils; -import org.opencdmp.commons.enums.DescriptionStatus; import org.opencdmp.commons.enums.IsActive; import org.opencdmp.elastic.query.DescriptionElasticQuery; import org.opencdmp.query.DescriptionQuery; @@ -31,7 +30,8 @@ public class DescriptionLookup extends Lookup { private List isActive; - private List statuses; + private List statusIds; + private DescriptionStatusLookup descriptionStatusSubQuery; public String getLike() { return this.like; @@ -65,12 +65,12 @@ public class DescriptionLookup extends Lookup { this.isActive = isActive; } - public List getStatuses() { - return this.statuses; + public List getStatusIds() { + return statusIds; } - public void setStatuses(List statuses) { - this.statuses = statuses; + public void setStatusIds(List statusIds) { + this.statusIds = statusIds; } public PlanLookup getPlanSubQuery() { @@ -127,6 +127,14 @@ public class DescriptionLookup extends Lookup { this.finalizedBefore = finalizedBefore; } + public DescriptionStatusLookup getDescriptionStatusSubQuery() { + return descriptionStatusSubQuery; + } + + public void setDescriptionStatusSubQuery(DescriptionStatusLookup descriptionStatusSubQuery) { + this.descriptionStatusSubQuery = descriptionStatusSubQuery; + } + public DescriptionQuery enrich(QueryFactory queryFactory) { DescriptionQuery query = queryFactory.query(DescriptionQuery.class); if (this.like != null) query.like(this.like); @@ -138,11 +146,12 @@ public class DescriptionLookup extends Lookup { if (this.descriptionReferenceSubQuery != null) query.descriptionReferenceSubQuery(this.descriptionReferenceSubQuery.enrich(queryFactory)); if (this.descriptionTagSubQuery != null) query.descriptionTagSubQuery(this.descriptionTagSubQuery.enrich(queryFactory)); if (this.isActive != null) query.isActive(this.isActive); - if (this.statuses != null) query.statuses(this.statuses); + if (this.statusIds != null) query.statusIds(this.statusIds); if (this.createdAfter != null) query.createdAfter(this.createdAfter); if (this.createdBefore != null) query.createdBefore(this.createdBefore); if (this.finalizedAfter != null) query.finalizedAfter(this.finalizedAfter); if (this.finalizedBefore != null) query.finalizedBefore(this.finalizedBefore); + if (this.descriptionStatusSubQuery != null) query.descriptionStatusSubQuery(this.descriptionStatusSubQuery.enrich(queryFactory)); this.enrichCommon(query); @@ -154,7 +163,7 @@ public class DescriptionLookup extends Lookup { if (this.like != null) query.like(StringUtils.strip(this.like, "%")); if (this.ids != null) query.ids(this.ids); if (this.excludedIds != null) query.excludedIds(this.excludedIds); - if (this.statuses != null) query.statuses(this.statuses); + if (this.statusIds != null) query.statusIds(this.statusIds); if (this.createdAfter != null) query.createdAfter(this.createdAfter); if (this.createdBefore != null) query.createdBefore(this.createdBefore); if (this.finalizedAfter != null) query.finalizedAfter(this.finalizedAfter); diff --git a/backend/core/src/main/java/org/opencdmp/query/lookup/PlanLookup.java b/backend/core/src/main/java/org/opencdmp/query/lookup/PlanLookup.java index de1cb71ae..e36f03054 100644 --- a/backend/core/src/main/java/org/opencdmp/query/lookup/PlanLookup.java +++ b/backend/core/src/main/java/org/opencdmp/query/lookup/PlanLookup.java @@ -4,7 +4,6 @@ import gr.cite.tools.data.query.Lookup; import gr.cite.tools.data.query.QueryFactory; import org.apache.commons.lang3.StringUtils; import org.opencdmp.commons.enums.PlanAccessType; -import org.opencdmp.commons.enums.PlanStatus; import org.opencdmp.commons.enums.PlanVersionStatus; import org.opencdmp.commons.enums.IsActive; import org.opencdmp.elastic.query.PlanElasticQuery; @@ -27,7 +26,7 @@ public class PlanLookup extends Lookup { private List isActive; private List versionStatuses; - private List statuses; + private List statusIds; private List accessTypes; private List versions; @@ -36,6 +35,7 @@ public class PlanLookup extends Lookup { private PlanUserLookup planUserSubQuery; private PlanBlueprintLookup planBlueprintSubQuery; private PlanReferenceLookup planReferenceSubQuery; + private PlanStatusLookup planStatusSubQuery; public String getLike() { return this.like; @@ -73,12 +73,12 @@ public class PlanLookup extends Lookup { this.isActive = isActive; } - public List getStatuses() { - return this.statuses; + public List getStatusIds() { + return statusIds; } - public void setStatuses(List statuses) { - this.statuses = statuses; + public void setStatusIds(List statusIds) { + this.statusIds = statusIds; } public List getVersions() { @@ -138,6 +138,14 @@ public class PlanLookup extends Lookup { public void setPlanReferenceLookup(PlanReferenceLookup planReferenceSubQuery) { this.planReferenceSubQuery = planReferenceSubQuery; } + public PlanStatusLookup getPlanStatusSubQuery() { + return planStatusSubQuery; + } + + public void setPlanStatusSubQuery(PlanStatusLookup planStatusSubQuery) { + this.planStatusSubQuery = planStatusSubQuery; + } + public PlanQuery enrich(QueryFactory queryFactory) { PlanQuery query = queryFactory.query(PlanQuery.class); if (this.like != null) query.like(this.like); @@ -147,13 +155,14 @@ public class PlanLookup extends Lookup { if (this.excludedIds != null) query.excludedIds(this.excludedIds); if (this.accessTypes != null) query.accessTypes(this.accessTypes); if (this.isActive != null) query.isActive(this.isActive); - if (this.statuses != null) query.statuses(this.statuses); + if (this.statusIds != null) query.statusIds(this.statusIds); if (this.versions != null) query.versions(this.versions); if (this.versionStatuses != null) query.versionStatuses(this.versionStatuses); if (this.planDescriptionTemplateSubQuery != null) query.planDescriptionTemplateSubQuery(this.planDescriptionTemplateSubQuery.enrich(queryFactory)); if (this.planUserSubQuery != null) query.planUserSubQuery(this.planUserSubQuery.enrich(queryFactory)); if (this.planBlueprintSubQuery != null) query.planBlueprintSubQuery(this.planBlueprintSubQuery.enrich(queryFactory)); if (this.planReferenceSubQuery != null) query.planReferenceSubQuery(this.planReferenceSubQuery.enrich(queryFactory)); + if (this.planStatusSubQuery != null) query.planStatusSubQuery(this.planStatusSubQuery.enrich(queryFactory)); this.enrichCommon(query); @@ -167,7 +176,7 @@ public class PlanLookup extends Lookup { if (this.groupIds != null) query.groupIds(this.groupIds); if (this.excludedIds != null) query.excludedIds(this.excludedIds); if (this.accessTypes != null) query.accessTypes(this.accessTypes); - if (this.statuses != null) query.statuses(this.statuses); + if (this.statusIds != null) query.statuses(this.statusIds); if (this.versions != null) query.versions(this.versions); if (this.versionStatuses != null) query.versionStatuses(this.versionStatuses); if (this.planDescriptionTemplateSubQuery != null) query.planDescriptionTemplateSubQuery(this.planDescriptionTemplateSubQuery.enrichElasticInner(queryFactory)); @@ -175,6 +184,7 @@ public class PlanLookup extends Lookup { if (this.planBlueprintSubQuery != null && this.planBlueprintSubQuery.getIds() != null && !this.planBlueprintSubQuery.getIds().isEmpty()) query.blueprintIds(this.planBlueprintSubQuery.getIds()); if (this.planReferenceSubQuery != null) query.referenceSubQuery(this.planReferenceSubQuery.enrichElasticInner(queryFactory)); if (this.tenantSubQuery != null) throw new UnsupportedOperationException(); + if (this.planStatusSubQuery != null) throw new UnsupportedOperationException(); this.enrichCommon(query); @@ -188,7 +198,7 @@ public class PlanLookup extends Lookup { if (this.groupIds != null) query.groupIds(this.groupIds); if (this.excludedIds != null) query.excludedIds(this.excludedIds); if (this.accessTypes != null) query.accessTypes(this.accessTypes); - if (this.statuses != null) query.statuses(this.statuses); + if (this.statusIds != null) query.statusIds(this.statusIds); if (this.versions != null) query.versions(this.versions); if (this.versionStatuses != null) query.versionStatuses(this.versionStatuses); if (this.planDescriptionTemplateSubQuery != null) throw new UnsupportedOperationException(""); @@ -196,6 +206,7 @@ public class PlanLookup extends Lookup { if (this.planBlueprintSubQuery != null && this.planBlueprintSubQuery.getIds() != null && !this.planBlueprintSubQuery.getIds().isEmpty()) throw new UnsupportedOperationException(""); if (this.planReferenceSubQuery != null) throw new UnsupportedOperationException(""); if (this.tenantSubQuery != null) throw new UnsupportedOperationException(); + if (this.planStatusSubQuery != null) throw new UnsupportedOperationException(); return query; } diff --git a/backend/core/src/main/java/org/opencdmp/query/utils/QueryUtilsServiceImpl.java b/backend/core/src/main/java/org/opencdmp/query/utils/QueryUtilsServiceImpl.java index 443f8b447..794f4d08c 100644 --- a/backend/core/src/main/java/org/opencdmp/query/utils/QueryUtilsServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/query/utils/QueryUtilsServiceImpl.java @@ -108,8 +108,7 @@ public class QueryUtilsServiceImpl implements QueryUtilsService { .keyPathFunc((subQueryRoot) -> subQueryRoot.get(PlanUserEntity._planId)) .filterFunc((subQueryRoot, cb) -> userId != null ? cb.and( - cb.equal(subQueryRoot.get(PlanUserEntity._userId), userId), - cb.equal(subQueryRoot.get(PlanUserEntity._isActive), IsActive.Active) + cb.equal(subQueryRoot.get(PlanUserEntity._userId), userId) ) : cb.or() //Creates a false query ) )); diff --git a/backend/core/src/main/java/org/opencdmp/service/custompolicy/CustomPolicyCacheOptions.java b/backend/core/src/main/java/org/opencdmp/service/custompolicy/CustomPolicyCacheOptions.java new file mode 100644 index 000000000..64a9df774 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/service/custompolicy/CustomPolicyCacheOptions.java @@ -0,0 +1,11 @@ +package org.opencdmp.service.custompolicy; + + +import gr.cite.tools.cache.CacheOptions; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "cache.custom-policy-by-tenant") +public class CustomPolicyCacheOptions extends CacheOptions { +} diff --git a/backend/core/src/main/java/org/opencdmp/service/custompolicy/CustomPolicyCacheService.java b/backend/core/src/main/java/org/opencdmp/service/custompolicy/CustomPolicyCacheService.java new file mode 100644 index 000000000..2c2384c61 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/service/custompolicy/CustomPolicyCacheService.java @@ -0,0 +1,86 @@ +package org.opencdmp.service.custompolicy; + + +import gr.cite.tools.cache.CacheService; +import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionEntity; +import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionEntity; +import org.opencdmp.event.DescriptionStatusTouchedEvent; +import org.opencdmp.event.PlanStatusTouchedEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Service +public class CustomPolicyCacheService extends CacheService { + + public static class CustomPolicyCacheValue { + + public CustomPolicyCacheValue() {} + + + public CustomPolicyCacheValue(String tenantCode, Map planStatusDefinitionMap, Map descriptionStatusDefinitionMap) { + this.tenantCode = tenantCode; + this.planStatusDefinitionMap = planStatusDefinitionMap; + this.descriptionStatusDefinitionMap = descriptionStatusDefinitionMap; + } + + private String tenantCode; + private Map planStatusDefinitionMap; + private Map descriptionStatusDefinitionMap; + + public String getTenantCode() { + return tenantCode; + } + + public void setTenantCode(String tenantCode) { + this.tenantCode = tenantCode; + } + + public Map getPlanStatusDefinitionMap() { + return planStatusDefinitionMap; + } + + public void setPlanStatusDefinitionMap(Map planStatusDefinitionMap) { + this.planStatusDefinitionMap = planStatusDefinitionMap; + } + + public Map getDescriptionStatusDefinitionMap() { + return descriptionStatusDefinitionMap; + } + + public void setDescriptionStatusDefinitionMap(Map descriptionStatusDefinitionMap) { + this.descriptionStatusDefinitionMap = descriptionStatusDefinitionMap; + } + } + + public void clearCache(PlanStatusTouchedEvent event) { + this.evict(this.buildKey(event.getTenantCode())); + } + + public void clearCache(DescriptionStatusTouchedEvent event) { + this.evict(this.buildKey(event.getTenantCode())); + } + + @Autowired + public CustomPolicyCacheService(CustomPolicyCacheOptions options) { + super(options); + } + + @Override + protected Class valueClass() {return CustomPolicyCacheValue.class;} + + + public String keyOf(CustomPolicyCacheValue value) { + return this.buildKey(value.getTenantCode()); + } + + public String buildKey(String tenantCode) { + HashMap keyParts = new HashMap<>(); + keyParts.put("$tenantCode$", tenantCode); + return this.generateKey(keyParts); + } + +} diff --git a/backend/core/src/main/java/org/opencdmp/service/custompolicy/CustomPolicyService.java b/backend/core/src/main/java/org/opencdmp/service/custompolicy/CustomPolicyService.java new file mode 100644 index 000000000..2b8d95a75 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/service/custompolicy/CustomPolicyService.java @@ -0,0 +1,26 @@ +package org.opencdmp.service.custompolicy; + + +import gr.cite.commons.web.authz.configuration.Permission; +import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionEntity; +import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionEntity; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public interface CustomPolicyService { + + HashMap buildPlanStatusPolicies(); + + Map buildPlanStatusDefinitionMap(); + + Map buildDescriptionStatusDefinitionMap(); + + String getPlanStatusCanEditStatusPermission(UUID id); + + HashMap buildDescriptionStatusPolicies(); + + String getDescriptionStatusCanEditStatusPermission(UUID id); + +} diff --git a/backend/core/src/main/java/org/opencdmp/service/custompolicy/CustomPolicyServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/custompolicy/CustomPolicyServiceImpl.java new file mode 100644 index 000000000..4f5774555 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/service/custompolicy/CustomPolicyServiceImpl.java @@ -0,0 +1,136 @@ +package org.opencdmp.service.custompolicy; + +import gr.cite.commons.web.authz.configuration.Permission; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.fieldset.BaseFieldSet; +import gr.cite.tools.logging.LoggerService; +import org.opencdmp.commons.XmlHandlingService; +import org.opencdmp.commons.enums.IsActive; +import org.opencdmp.commons.scope.tenant.TenantScope; +import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionEntity; +import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionEntity; +import org.opencdmp.data.DescriptionStatusEntity; +import org.opencdmp.data.PlanStatusEntity; +import org.opencdmp.model.descriptionstatus.DescriptionStatus; +import org.opencdmp.model.descriptionstatus.DescriptionStatusDefinitionAuthorization; +import org.opencdmp.model.planstatus.PlanStatus; +import org.opencdmp.model.planstatus.PlanStatusDefinitionAuthorization; +import org.opencdmp.query.DescriptionStatusQuery; +import org.opencdmp.query.PlanStatusQuery; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import javax.management.InvalidApplicationException; +import java.util.*; + +@Service +public class CustomPolicyServiceImpl implements CustomPolicyService{ + private final QueryFactory queryFactory; + private final XmlHandlingService xmlHandlingService; + private final TenantScope tenantScope; + private final CustomPolicyCacheService customPolicyCacheService; + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(CustomPolicyServiceImpl.class)); + + public CustomPolicyServiceImpl(QueryFactory queryFactory, XmlHandlingService xmlHandlingService, TenantScope tenantScope, CustomPolicyCacheService customPolicyCacheService) { + this.queryFactory = queryFactory; + this.xmlHandlingService = xmlHandlingService; + this.tenantScope = tenantScope; + this.customPolicyCacheService = customPolicyCacheService; + } + + @Override + public HashMap buildPlanStatusPolicies() { + HashMap policies = new HashMap<>(); + Map map = this.buildPlanStatusDefinitionMap(); + if (map == null) return policies; + + for (UUID statusId: map.keySet()) { + PlanStatusDefinitionEntity definition = map.get(statusId); + if (definition != null && definition.getAuthorization() != null && definition.getAuthorization().getEdit() != null) { + policies.put(this.getPlanStatusCanEditStatusPermission(statusId), new Permission(new HashSet<>(definition.getAuthorization().getEdit().getRoles()), new ArrayList<>(), new HashSet<>(), definition.getAuthorization().getEdit().getAllowAnonymous(), definition.getAuthorization().getEdit().getAllowAuthenticated())); + } + } + + return policies; + } + + @Override + public HashMap buildDescriptionStatusPolicies(){ + HashMap policies = new HashMap<>(); + Map map = this.buildDescriptionStatusDefinitionMap(); + if (map == null) return policies; + + for (UUID statusId: map.keySet()) { + DescriptionStatusDefinitionEntity definition = map.get(statusId); + if (definition != null && definition.getAuthorization() != null && definition.getAuthorization().getEdit() != null) { + policies.put(this.getDescriptionStatusCanEditStatusPermission(statusId), new Permission(new HashSet<>(definition.getAuthorization().getEdit().getRoles()), new ArrayList<>(), new HashSet<>(), definition.getAuthorization().getEdit().getAllowAnonymous(), definition.getAuthorization().getEdit().getAllowAuthenticated())); + } + } + + return policies; + } + + @Override + public Map buildPlanStatusDefinitionMap() { + HashMap policies = new HashMap<>(); + String tenantCode = null; + try { + tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : this.tenantScope.getDefaultTenantCode(); + } catch (InvalidApplicationException e) { + throw new RuntimeException(e); + } + CustomPolicyCacheService.CustomPolicyCacheValue cacheValue = this.customPolicyCacheService.lookup(this.customPolicyCacheService.buildKey(tenantCode)); + if (cacheValue == null || cacheValue.getPlanStatusDefinitionMap() == null) { + Map definitionStatusMap = new HashMap<>(); + List entities = this.queryFactory.query(PlanStatusQuery.class).isActives(IsActive.Active).collectAs(new BaseFieldSet().ensure(PlanStatus._id).ensure(PlanStatus._definition)); + for (PlanStatusEntity entity: entities) { + PlanStatusDefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(PlanStatusDefinitionEntity.class, entity.getDefinition()); + if (definition != null) { + definitionStatusMap.put(entity.getId(), definition); + } + } + + cacheValue = new CustomPolicyCacheService.CustomPolicyCacheValue(tenantCode, definitionStatusMap, cacheValue != null ? cacheValue.getDescriptionStatusDefinitionMap() : null); + this.customPolicyCacheService.put(cacheValue); + } + + return cacheValue.getPlanStatusDefinitionMap(); + } + + @Override + public Map buildDescriptionStatusDefinitionMap(){ + HashMap policies = new HashMap<>(); + String tenantCode = null; + try { + tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : this.tenantScope.getDefaultTenantCode(); + } catch (InvalidApplicationException e) { + throw new RuntimeException(e); + } + CustomPolicyCacheService.CustomPolicyCacheValue cacheValue = this.customPolicyCacheService.lookup(this.customPolicyCacheService.buildKey(tenantCode)); + if (cacheValue == null || cacheValue.getDescriptionStatusDefinitionMap() == null) { + Map definitionStatusMap = new HashMap<>(); + List entities = this.queryFactory.query(DescriptionStatusQuery.class).isActive(IsActive.Active).collectAs(new BaseFieldSet().ensure(DescriptionStatus._id).ensure(DescriptionStatus._definition)); + for (DescriptionStatusEntity entity : entities) { + DescriptionStatusDefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(DescriptionStatusDefinitionEntity.class, entity.getDefinition()); + if (definition != null) { + definitionStatusMap.put(entity.getId(), definition); + } + } + + cacheValue = new CustomPolicyCacheService.CustomPolicyCacheValue(tenantCode, cacheValue != null ? cacheValue.getPlanStatusDefinitionMap(): null, definitionStatusMap); + this.customPolicyCacheService.put(cacheValue); + } + + return cacheValue.getDescriptionStatusDefinitionMap(); + } + + @Override + public String getPlanStatusCanEditStatusPermission(UUID id){ + return ("PlanStatus" + "_" + id + "_" + PlanStatusDefinitionAuthorization._edit).toLowerCase(); + } + + @Override + public String getDescriptionStatusCanEditStatusPermission(UUID id){ + return ("DescriptionStatus" + "_" + id + "_" + DescriptionStatusDefinitionAuthorization._edit).toLowerCase(); + } +} 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..9ae8ad73a 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._id)).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().getId())); } } @@ -88,9 +88,9 @@ public class DashboardServiceImpl implements DashboardService { planLookup.getPage().setOffset(0); planLookup.getPage().setSize(model.getPage().getSize()+model.getPage().getOffset()); - QueryResult plans = this.elasticQueryHelperService.collect(planLookup, AuthorizationFlags.AllExceptPublic, new BaseFieldSet().ensure(Plan._id).ensure(Plan._updatedAt).ensure(Plan._label).ensure(Plan._status)); + QueryResult plans = this.elasticQueryHelperService.collect(planLookup, AuthorizationFlags.AllExceptPublic, new BaseFieldSet().ensure(Plan._id).ensure(Plan._updatedAt).ensure(Plan._label).ensure(String.join(".",Plan._status, org.opencdmp.model.planstatus.PlanStatus._id))); if (!this.conventionService.isListNullOrEmpty(plans.getItems())) { - for (Plan plan : plans.getItems()) recentActivityItemEntities.add(new RecentActivityItemEntity(RecentActivityItemType.Plan, plan.getId(), plan.getUpdatedAt(), plan.getLabel(), plan.getStatus().getValue())); + for (Plan plan : plans.getItems()) recentActivityItemEntities.add(new RecentActivityItemEntity(RecentActivityItemType.Plan, plan.getId(), plan.getUpdatedAt(), plan.getLabel(), plan.getStatus().getId())); } } @@ -100,7 +100,7 @@ public class DashboardServiceImpl implements DashboardService { switch (model.getOrderField()){ case Label -> comparator = Comparator.comparing(RecentActivityItemEntity::getLabel).thenComparing(RecentActivityItemEntity::getUpdatedAt); case UpdatedAt -> comparator = Comparator.comparing(RecentActivityItemEntity::getUpdatedAt); - case Status -> comparator = Comparator.comparing(RecentActivityItemEntity::getStatusValue).thenComparing(RecentActivityItemEntity::getUpdatedAt); + case Status -> comparator = Comparator.comparing(RecentActivityItemEntity::getStatusId).thenComparing(RecentActivityItemEntity::getUpdatedAt); default -> throw new IllegalArgumentException("Type not found" + model.getOrderField()) ; } } @@ -118,10 +118,12 @@ public class DashboardServiceImpl implements DashboardService { DashboardStatisticsCacheService.DashboardStatisticsCacheValue cacheValue = this.dashboardStatisticsCacheService.lookup(this.dashboardStatisticsCacheService.buildKey(DashboardStatisticsCacheService.publicKey)); if (cacheValue == null || cacheValue.getDashboardStatistics() == null) { - PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).disableTracking().isActive(IsActive.Active).versionStatuses(PlanVersionStatus.Current).statuses(PlanStatus.Finalized).accessTypes(PlanAccessType.Public); + PlanStatusQuery planStatusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).disableTracking().isActive(IsActive.Active).versionStatuses(PlanVersionStatus.Current).planStatusSubQuery(planStatusQuery).accessTypes(PlanAccessType.Public); DashboardStatistics statistics = new DashboardStatistics(); statistics.setPlanCount(planQuery.authorize(EnumSet.of(Public)).count()); - statistics.setDescriptionCount(this.queryFactory.query(DescriptionQuery.class).disableTracking().isActive(IsActive.Active).planSubQuery(planQuery).statuses(DescriptionStatus.Finalized).authorize(EnumSet.of(Public)).count()); + DescriptionStatusQuery descriptionStatusQuery = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().internalStatuses(DescriptionStatus.Finalized).isActive(IsActive.Active); + statistics.setDescriptionCount(this.queryFactory.query(DescriptionQuery.class).disableTracking().isActive(IsActive.Active).planSubQuery(planQuery).descriptionStatusSubQuery(descriptionStatusQuery).authorize(EnumSet.of(Public)).count()); statistics.setReferenceTypeStatistics(new ArrayList<>()); if (!this.conventionService.isListNullOrEmpty(this.config.getReferenceTypeCounters())){ 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 c327ffe74..bae6f279e 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 @@ -24,6 +24,7 @@ import org.opencdmp.commonmodels.models.reference.ReferenceModel; import org.opencdmp.commons.JsonHandlingService; import org.opencdmp.commons.XmlHandlingService; import org.opencdmp.commons.enums.*; +import org.opencdmp.commons.enums.DescriptionStatus; import org.opencdmp.commons.notification.NotificationProperties; import org.opencdmp.commons.scope.tenant.TenantScope; import org.opencdmp.commons.scope.user.UserScope; @@ -68,7 +69,9 @@ import org.opencdmp.model.reference.Reference; import org.opencdmp.model.referencetype.ReferenceType; import org.opencdmp.query.*; import org.opencdmp.service.accounting.AccountingService; +import org.opencdmp.service.custompolicy.CustomPolicyService; 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 +144,8 @@ public class DescriptionServiceImpl implements DescriptionService { private final TagService tagService; private final UsageLimitService usageLimitService; private final AccountingService accountingService; + private final DescriptionWorkflowService descriptionWorkflowService; + private final CustomPolicyService customPolicyService; @Autowired public DescriptionServiceImpl( @@ -155,7 +160,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, CustomPolicyService customPolicyService) { this.entityManager = entityManager; this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; @@ -184,6 +189,8 @@ public class DescriptionServiceImpl implements DescriptionService { this.tagService = tagService; this.usageLimitService = usageLimitService; this.accountingService = accountingService; + this.descriptionWorkflowService = descriptionWorkflowService; + this.customPolicyService = customPolicyService; } @Override @@ -223,9 +230,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 +259,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); @@ -253,12 +270,12 @@ public class DescriptionServiceImpl implements DescriptionService { PlanEntity plan = this.entityManager.find(PlanEntity.class, data.getPlanId(), true); if (plan == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{data.getPlanId(), Plan.class.getSimpleName()}, LocaleContextHolder.getLocale())); + PlanStatusEntity planStatusEntity = this.entityManager.find(PlanStatusEntity.class, plan.getStatusId(), true); + if (planStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{plan.getStatusId(), PlanStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); - if (plan.getStatus().equals(PlanStatus.Finalized)) throw new MyValidationException(this.errors.getPlanIsFinalized().getCode(), this.errors.getPlanIsFinalized().getMessage()); + if (planStatusEntity.getInternalStatus() != null && planStatusEntity.getInternalStatus().equals(PlanStatus.Finalized)) 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 +393,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 +409,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, user, isUpdate); } } } @@ -437,17 +458,19 @@ public class DescriptionServiceImpl implements DescriptionService { return cleanData; } - private void createDescriptionNotificationEvent(DescriptionEntity description, UserEntity user, Boolean isUpdate) throws InvalidApplicationException { + private void createDescriptionNotificationEvent(DescriptionEntity description, DescriptionStatusEntity descriptionStatus, UserEntity user, Boolean isUpdate) throws InvalidApplicationException { NotifyIntegrationEvent event = new NotifyIntegrationEvent(); event.setUserId(user.getId()); - this.applyNotificationType(description.getStatus(), event, isUpdate); + if (descriptionStatus.getInternalStatus() == null) event.setNotificationType(this.notificationProperties.getDescriptionStatusChangedType()); + else this.applyNotificationType(descriptionStatus.getInternalStatus(), event, isUpdate); NotificationFieldData data = new NotificationFieldData(); List fieldInfoList = new ArrayList<>(); fieldInfoList.add(new FieldInfo("{recipient}", DataType.String, user.getName())); fieldInfoList.add(new FieldInfo("{reasonName}", DataType.String, this.queryFactory.query(UserQuery.class).disableTracking().ids(this.userScope.getUserId()).first().getName())); fieldInfoList.add(new FieldInfo("{name}", DataType.String, description.getLabel())); fieldInfoList.add(new FieldInfo("{id}", DataType.String, description.getId().toString())); + if (descriptionStatus.getInternalStatus() == null) fieldInfoList.add(new FieldInfo("{statusName}", DataType.String, descriptionStatus.getName())); if(this.tenantScope.getTenantCode() != null && !this.tenantScope.getTenantCode().equals(this.tenantScope.getDefaultTenantCode())){ fieldInfoList.add(new FieldInfo("{tenant-url-path}", DataType.String, String.format("/t/%s", this.tenantScope.getTenantCode()))); } @@ -479,19 +502,32 @@ public class DescriptionServiceImpl implements DescriptionService { this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.descriptionAffiliation(model.getId())), Permission.EditDescription); - DescriptionEntity data = this.entityManager.find(DescriptionEntity.class, model.getId()); + DescriptionEntity data = this.queryFactory.query(DescriptionQuery.class).authorize(AuthorizationFlags.AllExceptPublic).ids(model.getId()).isActive(IsActive.Active).first(); if (data == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Description.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + try { + this.authorizationService.authorizeForce(this.customPolicyService.getDescriptionStatusCanEditStatusPermission(model.getStatusId())); + } catch (Exception e) { + this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.planAffiliation(data.getPlanId())), this.customPolicyService.getDescriptionStatusCanEditStatusPermission(model.getStatusId())); + } + 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()); + PlanStatusEntity planStatusEntity = this.entityManager.find(PlanStatusEntity.class, planEntity.getStatusId(), true); + if (planStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{planEntity.getStatusId(), PlanStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + if (planStatusEntity.getInternalStatus() != null && planStatusEntity.getInternalStatus().equals(PlanStatus.Finalized)) 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 +537,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); + this.sendNotification(data, true); } return this.builderFactory.builder(DescriptionBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(BaseFieldSet.build(fields, Description._id), data); } @@ -514,11 +550,14 @@ public class DescriptionServiceImpl implements DescriptionService { return null; } + 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) throw new MyApplicationException("finalized status not found"); + for (DescriptionEntity description: descriptions) { DescriptionValidationResult descriptionValidationResult = new DescriptionValidationResult(description.getId(), DescriptionValidationOutput.Invalid); DescriptionPersist.DescriptionPersistValidator validator = this.validatorFactory.validator(DescriptionPersist.DescriptionPersistValidator.class); - validator.validate(this.buildDescriptionPersist(description)); + validator.validate(this.buildDescriptionPersist(description, statusEntity.getId())); if (validator.result().isValid()) descriptionValidationResult.setResult(DescriptionValidationOutput.Valid); descriptionValidationResults.add(descriptionValidationResult); @@ -994,7 +1033,7 @@ public class DescriptionServiceImpl implements DescriptionService { //region build persist - private @NotNull DescriptionPersist buildDescriptionPersist(DescriptionEntity data) throws InvalidApplicationException { + private @NotNull DescriptionPersist buildDescriptionPersist(DescriptionEntity data, UUID statusId) throws InvalidApplicationException { DescriptionPersist persist = new DescriptionPersist(); if (data == null) return persist; @@ -1003,7 +1042,7 @@ public class DescriptionServiceImpl implements DescriptionService { persist.setId(data.getId()); persist.setLabel(data.getLabel()); - persist.setStatus(DescriptionStatus.Finalized); + persist.setStatusId(statusId); persist.setDescription(data.getDescription()); persist.setDescriptionTemplateId(data.getDescriptionTemplateId()); persist.setPlanId(data.getPlanId()); @@ -1174,7 +1213,8 @@ public class DescriptionServiceImpl implements DescriptionService { } else { try { this.entityManager.disableTenantFilters(); - data = this.queryFactory.query(DescriptionQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).planSubQuery(this.queryFactory.query(PlanQuery.class).isActive(IsActive.Active).statuses(PlanStatus.Finalized).accessTypes(PlanAccessType.Public)).first(); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + data = this.queryFactory.query(DescriptionQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).planSubQuery(this.queryFactory.query(PlanQuery.class).isActive(IsActive.Active).planStatusSubQuery(statusQuery).accessTypes(PlanAccessType.Public)).first(); this.entityManager.reloadTenantFilters(); } finally { this.entityManager.reloadTenantFilters(); @@ -1207,7 +1247,8 @@ public class DescriptionServiceImpl implements DescriptionService { DescriptionEntity data = null; try { this.entityManager.disableTenantFilters(); - data = this.queryFactory.query(DescriptionQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).planSubQuery(this.queryFactory.query(PlanQuery.class).isActive(IsActive.Active).statuses(PlanStatus.Finalized).accessTypes(PlanAccessType.Public)).first(); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + data = this.queryFactory.query(DescriptionQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).planSubQuery(this.queryFactory.query(PlanQuery.class).isActive(IsActive.Active).planStatusSubQuery(statusQuery).accessTypes(PlanAccessType.Public)).first(); this.entityManager.reloadTenantFilters(); } finally { this.entityManager.reloadTenantFilters(); @@ -1225,6 +1266,7 @@ public class DescriptionServiceImpl implements DescriptionService { xml.setId(data.getId()); xml.setDescription(data.getDescription()); xml.setLabel(data.getLabel()); + xml.setStatus(this.descriptionStatusImportExportToExport(data.getStatusId())); xml.setFinalizedAt(data.getFinalizedAt()); PlanDescriptionTemplateEntity planDescriptionTemplateEntity = this.queryFactory.query(PlanDescriptionTemplateQuery.class).disableTracking().ids(data.getPlanDescriptionTemplateId()).authorize(AuthorizationFlags.All).isActive(IsActive.Active).first(); @@ -1261,6 +1303,17 @@ public class DescriptionServiceImpl implements DescriptionService { return xml; } + private DescriptionStatusImportExport descriptionStatusImportExportToExport(UUID statusId) throws InvalidApplicationException { + DescriptionStatusImportExport xml = new DescriptionStatusImportExport(); + if (statusId == null) return xml; + DescriptionStatusEntity statusEntity = this.entityManager.find(DescriptionStatusEntity.class, statusId, true); + if (statusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{statusId, DescriptionStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + xml.setId(statusEntity.getId()); + xml.setName(statusEntity.getName()); + return xml; + } + private DescriptionReferenceImportExport descriptionReferenceToExport(DescriptionReferenceEntity entity, Map referenceEntityMap, Map referenceTypeEntityMap) { DescriptionReferenceImportExport xml = new DescriptionReferenceImportExport(); if (entity == null) return xml; @@ -1382,7 +1435,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 +1652,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..b7652a5bc 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,8 @@ import org.opencdmp.model.descriptionstatus.DescriptionStatus; import org.opencdmp.model.persist.descriptionstatus.DescriptionStatusPersist; import javax.management.InvalidApplicationException; +import java.util.List; +import java.util.Map; import java.util.UUID; public interface DescriptionStatusService { @@ -17,4 +19,6 @@ public interface DescriptionStatusService { DescriptionStatus persist(DescriptionStatusPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JAXBException; void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException; + + Map> getAuthorizedAvailableStatusIds(List descriptionsIds); } 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..b1a9b4fb9 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,33 @@ 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.AffiliatedResource; import org.opencdmp.authorization.Permission; +import org.opencdmp.authorization.authorizationcontentresolver.AuthorizationContentResolver; import org.opencdmp.commons.XmlHandlingService; import org.opencdmp.commons.enums.IsActive; +import org.opencdmp.commons.scope.tenant.TenantScope; 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.DescriptionStatusEntity; -import org.opencdmp.data.TenantEntityManager; +import org.opencdmp.data.*; +import org.opencdmp.event.DescriptionStatusTouchedEvent; +import org.opencdmp.event.EventBroker; 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.DescriptionQuery; +import org.opencdmp.query.DescriptionStatusQuery; +import org.opencdmp.service.custompolicy.CustomPolicyService; +import org.opencdmp.service.descriptionworkflow.DescriptionWorkflowService; import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; @@ -35,8 +47,8 @@ import org.springframework.stereotype.Service; import javax.management.InvalidApplicationException; import java.time.Instant; -import java.util.List; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; @Service public class DescriptionStatusServiceImpl implements DescriptionStatusService { @@ -51,8 +63,15 @@ public class DescriptionStatusServiceImpl implements DescriptionStatusService { private final ConventionService conventionService; private final MessageSource messageSource; private final XmlHandlingService xmlHandlingService; + private final EventBroker eventBroker; + private final TenantScope tenantScope; + private final DescriptionWorkflowService descriptionWorkflowService; + private final CustomPolicyService customPolicyService; + private final AuthorizationService authorizationService; + private final AuthorizationContentResolver authorizationContentResolver; + private final QueryFactory queryFactory; - 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, EventBroker eventBroker, TenantScope tenantScope, DescriptionWorkflowService descriptionWorkflowService, CustomPolicyService customPolicyService, AuthorizationService authorizationService, AuthorizationContentResolver authorizationContentResolver, QueryFactory queryFactory) { this.builderFactory = builderFactory; this.deleterFactory = deleterFactory; @@ -61,6 +80,13 @@ public class DescriptionStatusServiceImpl implements DescriptionStatusService { this.conventionService = conventionService; this.messageSource = messageSource; this.xmlHandlingService = xmlHandlingService; + this.eventBroker = eventBroker; + this.tenantScope = tenantScope; + this.descriptionWorkflowService = descriptionWorkflowService; + this.customPolicyService = customPolicyService; + this.authorizationService = authorizationService; + this.authorizationContentResolver = authorizationContentResolver; + this.queryFactory = queryFactory; } @@ -86,6 +112,7 @@ public class DescriptionStatusServiceImpl implements DescriptionStatusService { data.setName(model.getName()); data.setDescription(model.getDescription()); + data.setAction(model.getAction()); data.setInternalStatus(model.getInternalStatus()); data.setDefinition(this.xmlHandlingService.toXml(this.buildDescriptionStatusDefinitionEntity(model.getDefinition()))); data.setUpdatedAt(Instant.now()); @@ -97,6 +124,8 @@ public class DescriptionStatusServiceImpl implements DescriptionStatusService { this.entityManager.flush(); + this.eventBroker.emit(new DescriptionStatusTouchedEvent(data.getId(), this.tenantScope.getTenantCode())); + return this.builderFactory.builder(DescriptionStatusBuilder.class).build(BaseFieldSet.build(fields, DescriptionStatus._id), data); } @@ -115,7 +144,7 @@ public class DescriptionStatusServiceImpl implements DescriptionStatusService { return data; data.setAuthorization(this.buildDescriptionStatusDefinitionAuthorizationEntity(persist.getAuthorization())); - + if (!this.conventionService.isListNullOrEmpty(persist.getAvailableActions())) data.setAvailableActions(persist.getAvailableActions()); return data; } @@ -141,4 +170,34 @@ public class DescriptionStatusServiceImpl implements DescriptionStatusService { return data; } + + @Override + public Map> getAuthorizedAvailableStatusIds(List descriptionsIds) { + + Map> authorizedStatusMap = new HashMap<>(); + DescriptionWorkflowDefinitionEntity definition; + try { + definition = this.descriptionWorkflowService.getWorkFlowDefinition(); + } catch (InvalidApplicationException e) { + throw new RuntimeException(e); + } + + List descriptionEntities = this.queryFactory.query(DescriptionQuery.class).ids(descriptionsIds).isActive(IsActive.Active).collectAs(new BaseFieldSet().ensure(Description._id).ensure(Description._status).ensure(Description._plan)); + List statusEntities = this.queryFactory.query(DescriptionStatusQuery.class).isActive(IsActive.Active).collectAs(new BaseFieldSet().ensure(DescriptionStatus._id)); + for (DescriptionEntity description: descriptionEntities) { + authorizedStatusMap.put(description.getId(), new ArrayList<>()); + AffiliatedResource affiliatedResource = this.authorizationContentResolver.planAffiliation(description.getPlanId()); + for (DescriptionStatusEntity status: statusEntities) { + + List availableTransitions = definition.getStatusTransitions().stream().filter(x -> x.getFromStatusId().equals(description.getStatusId())).collect(Collectors.toList()); + if (availableTransitions.stream().filter(x -> x.getToStatusId().equals(status.getId())).findFirst().orElse(null) != null) { + String editPermission = this.customPolicyService.getDescriptionStatusCanEditStatusPermission(status.getId()); + Boolean isAllowed = affiliatedResource == null ? this.authorizationService.authorize(editPermission) : this.authorizationService.authorizeAtLeastOne(List.of(affiliatedResource), editPermission); + if (isAllowed) authorizedStatusMap.get(description.getId()).add(status.getId()); + } + } + } + + return authorizedStatusMap; + } } 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..523a6530b 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,37 @@ 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); + + List descriptionWorkflowEntities = query.collect(); + if (this.conventionService.isListNullOrEmpty(descriptionWorkflowEntities)) throw new MyApplicationException("Description workflows not found!"); + + DescriptionWorkflowEntity entity = null; + if (!this.tenantScope.isDefaultTenant()) { + entity = descriptionWorkflowEntities.stream().filter(x -> { + try { + return (this.tenantScope.getTenant().equals(x.getTenantId())); + } catch (InvalidApplicationException e) { + throw new RuntimeException(e); + } + }).findFirst().orElse(null); + } + + // fallback to default tenant + if (entity == null) { + entity = descriptionWorkflowEntities.stream().filter(x -> x.getTenantId() == null).findFirst().orElse(null); + } + + 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/elastic/ElasticServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/elastic/ElasticServiceImpl.java index 3101f10e8..d110e7dca 100644 --- a/backend/core/src/main/java/org/opencdmp/service/elastic/ElasticServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/elastic/ElasticServiceImpl.java @@ -136,7 +136,7 @@ public class ElasticServiceImpl implements ElasticService { propertyMap.put(DescriptionElasticEntity._tenantId, this.createElastic(FieldType.Keyword, false)); propertyMap.put(DescriptionElasticEntity._label, this.createElastic(FieldType.Text, true)); propertyMap.put(DescriptionElasticEntity._description, this.createElastic(FieldType.Text, true)); - propertyMap.put(DescriptionElasticEntity._status, this.createElastic(FieldType.Short, false)); + propertyMap.put(DescriptionElasticEntity._statusId, this.createElastic(FieldType.Keyword, false)); propertyMap.put(DescriptionElasticEntity._finalizedAt, this.createElastic(FieldType.Date, false)); propertyMap.put(DescriptionElasticEntity._createdAt, this.createElastic(FieldType.Date, false)); propertyMap.put(DescriptionElasticEntity._updatedAt, this.createElastic(FieldType.Date, false)); @@ -154,7 +154,7 @@ public class ElasticServiceImpl implements ElasticService { propertyMap.put(PlanElasticEntity._tenantId, this.createElastic(FieldType.Keyword, false)); propertyMap.put(PlanElasticEntity._label, this.createElastic(FieldType.Text, true)); propertyMap.put(PlanElasticEntity._description, this.createElastic(FieldType.Text, false)); - propertyMap.put(PlanElasticEntity._status, this.createElastic(FieldType.Short, false)); + propertyMap.put(PlanElasticEntity._statusId, this.createElastic(FieldType.Keyword, false)); propertyMap.put(PlanElasticEntity._version, this.createElastic(FieldType.Short, false)); propertyMap.put(PlanElasticEntity._language, this.createElastic(FieldType.Keyword, false)); propertyMap.put(PlanElasticEntity._blueprintId, this.createElastic(FieldType.Keyword, false)); @@ -230,7 +230,7 @@ public class ElasticServiceImpl implements ElasticService { propertyMap.put(NestedPlanElasticEntity._id, this.createElastic(FieldType.Keyword, false)); propertyMap.put(NestedPlanElasticEntity._label, this.createElastic(FieldType.Text, true)); propertyMap.put(NestedPlanElasticEntity._description, this.createElastic(FieldType.Text, false)); - propertyMap.put(NestedPlanElasticEntity._status, this.createElastic(FieldType.Short, false)); + propertyMap.put(NestedPlanElasticEntity._statusId, this.createElastic(FieldType.Keyword, false)); propertyMap.put(NestedPlanElasticEntity._version, this.createElastic(FieldType.Short, false)); propertyMap.put(NestedPlanElasticEntity._versionStatus, this.createElastic(FieldType.Short, false)); propertyMap.put(NestedPlanElasticEntity._language, this.createElastic(FieldType.Keyword, false)); diff --git a/backend/core/src/main/java/org/opencdmp/service/filetransformer/FileTransformerServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/filetransformer/FileTransformerServiceImpl.java index 96e8c4860..5c81519f6 100644 --- a/backend/core/src/main/java/org/opencdmp/service/filetransformer/FileTransformerServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/filetransformer/FileTransformerServiceImpl.java @@ -268,7 +268,8 @@ public class FileTransformerServiceImpl implements FileTransformerService { } else { try { this.entityManager.disableTenantFilters(); - entity = this.queryFactory.query(PlanQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(planId).isActive(IsActive.Active).statuses(PlanStatus.Finalized).accessTypes(PlanAccessType.Public).first(); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + entity = this.queryFactory.query(PlanQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(planId).isActive(IsActive.Active).planStatusSubQuery(statusQuery).accessTypes(PlanAccessType.Public).first(); this.entityManager.reloadTenantFilters(); } finally { this.entityManager.reloadTenantFilters(); @@ -307,7 +308,8 @@ public class FileTransformerServiceImpl implements FileTransformerService { } else { try { this.entityManager.disableTenantFilters(); - entity = this.queryFactory.query(DescriptionQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(descriptionId).planSubQuery(this.queryFactory.query(PlanQuery.class).isActive(IsActive.Active).statuses(PlanStatus.Finalized).accessTypes(PlanAccessType.Public)).first(); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + entity = this.queryFactory.query(DescriptionQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(descriptionId).planSubQuery(this.queryFactory.query(PlanQuery.class).isActive(IsActive.Active).planStatusSubQuery(statusQuery).accessTypes(PlanAccessType.Public)).first(); this.entityManager.reloadTenantFilters(); } finally { this.entityManager.reloadTenantFilters(); diff --git a/backend/core/src/main/java/org/opencdmp/service/metrics/MetricsServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/metrics/MetricsServiceImpl.java index 617e8b6a5..d8e7b0cb4 100644 --- a/backend/core/src/main/java/org/opencdmp/service/metrics/MetricsServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/metrics/MetricsServiceImpl.java @@ -325,21 +325,24 @@ public class MetricsServiceImpl implements MetricsService { } private double calculateDraftDmps(boolean forNexus) { - PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).disableTracking().statuses(PlanStatus.Draft).isActive(IsActive.Active); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Draft).isActives(IsActive.Active); + PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).disableTracking().planStatusSubQuery(statusQuery).isActive(IsActive.Active); if (forNexus) planQuery.after(this._config.getNexusDate()); return planQuery.count(); } private double calculateFinalizedDmps(boolean forNexus) { - PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).disableTracking().statuses(PlanStatus.Finalized).isActive(IsActive.Active); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).disableTracking().planStatusSubQuery(statusQuery).isActive(IsActive.Active); if (forNexus) planQuery.after(this._config.getNexusDate()); return planQuery.count(); } private double calculatePublishedDmps(boolean forNexus) { - PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).disableTracking().statuses(PlanStatus.Finalized).accessTypes(PlanAccessType.Public).isActive(IsActive.Active); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).disableTracking().planStatusSubQuery(statusQuery).accessTypes(PlanAccessType.Public).isActive(IsActive.Active); if (forNexus) planQuery.after(this._config.getNexusDate()); return planQuery.count(); @@ -355,8 +358,10 @@ public class MetricsServiceImpl implements MetricsService { return planQuery.count(); } - private double calculateDraftDmpsWithGrant(boolean forNexus) { - PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).statuses(PlanStatus.Draft).disableTracking().isActive(IsActive.Active); + private double calculateDraftDmpsWithGrant(boolean forNexus) + { + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Draft).isActives(IsActive.Active); + PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).planStatusSubQuery(statusQuery).disableTracking().isActive(IsActive.Active); if (forNexus) planQuery.after(this._config.getNexusDate()); ReferenceQuery referenceQuery = this.queryFactory.query(ReferenceQuery.class).disableTracking().typeIds(this._config.getReferenceTypes().getGrantIds()).isActive(IsActive.Active); @@ -366,7 +371,8 @@ public class MetricsServiceImpl implements MetricsService { } private double calculateFinalizedDmpsWithGrant(boolean forNexus) { - PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).disableTracking().statuses(PlanStatus.Finalized).isActive(IsActive.Active); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).disableTracking().planStatusSubQuery(statusQuery).isActive(IsActive.Active); if (forNexus) planQuery.after(this._config.getNexusDate()); ReferenceQuery referenceQuery = this.queryFactory.query(ReferenceQuery.class).disableTracking().typeIds(this._config.getReferenceTypes().getGrantIds()).isActive(IsActive.Active); @@ -376,7 +382,8 @@ public class MetricsServiceImpl implements MetricsService { } private double calculatePublishedDmpsWithGrant(boolean forNexus) { - PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).statuses(PlanStatus.Finalized).disableTracking().accessTypes(PlanAccessType.Public).isActive(IsActive.Active); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).planStatusSubQuery(statusQuery).disableTracking().accessTypes(PlanAccessType.Public).isActive(IsActive.Active); if (forNexus) planQuery.after(this._config.getNexusDate()); ReferenceQuery referenceQuery = this.queryFactory.query(ReferenceQuery.class).disableTracking().typeIds(this._config.getReferenceTypes().getGrantIds()).isActive(IsActive.Active); @@ -426,30 +433,35 @@ public class MetricsServiceImpl implements MetricsService { } private double calculateDraftDatasets(boolean forNexus) { - DescriptionQuery descriptionQuery = this.queryFactory.query(DescriptionQuery.class).disableTracking().statuses(DescriptionStatus.Draft).isActive(IsActive.Active); + DescriptionStatusQuery descriptionStatusQuery = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().internalStatuses(DescriptionStatus.Draft).isActive(IsActive.Active); + DescriptionQuery descriptionQuery = this.queryFactory.query(DescriptionQuery.class).disableTracking().descriptionStatusSubQuery(descriptionStatusQuery).isActive(IsActive.Active); if (forNexus) descriptionQuery.createdAfter(this._config.getNexusDate()); return descriptionQuery.count(); } private double calculateFinalizedDatasets(boolean forNexus) { - DescriptionQuery descriptionQuery = this.queryFactory.query(DescriptionQuery.class).disableTracking().statuses(DescriptionStatus.Finalized).isActive(IsActive.Active); + DescriptionStatusQuery descriptionStatusQuery = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().internalStatuses(DescriptionStatus.Finalized).isActive(IsActive.Active); + DescriptionQuery descriptionQuery = this.queryFactory.query(DescriptionQuery.class).disableTracking().descriptionStatusSubQuery(descriptionStatusQuery).isActive(IsActive.Active); if (forNexus) descriptionQuery.createdAfter(this._config.getNexusDate()); return descriptionQuery.count(); } private double calculatePublishedDatasets(boolean forNexus) { - DescriptionQuery descriptionQuery = this.queryFactory.query(DescriptionQuery.class).disableTracking().statuses(DescriptionStatus.Finalized).isActive(IsActive.Active); + DescriptionStatusQuery descriptionStatusQuery = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().internalStatuses(DescriptionStatus.Finalized).isActive(IsActive.Active); + DescriptionQuery descriptionQuery = this.queryFactory.query(DescriptionQuery.class).disableTracking().descriptionStatusSubQuery(descriptionStatusQuery).isActive(IsActive.Active); if (forNexus) descriptionQuery.createdAfter(this._config.getNexusDate()); - PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).isActive(IsActive.Active).disableTracking().statuses(PlanStatus.Finalized).accessTypes(PlanAccessType.Public); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).isActive(IsActive.Active).disableTracking().planStatusSubQuery(statusQuery).accessTypes(PlanAccessType.Public); descriptionQuery.planSubQuery(planQuery); return descriptionQuery.count(); } private double calculateDoiedDatasets(boolean forNexus) { - DescriptionQuery descriptionQuery = this.queryFactory.query(DescriptionQuery.class).disableTracking().statuses(DescriptionStatus.Finalized).isActive(IsActive.Active); + DescriptionStatusQuery descriptionStatusQuery = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().internalStatuses(DescriptionStatus.Finalized).isActive(IsActive.Active); + DescriptionQuery descriptionQuery = this.queryFactory.query(DescriptionQuery.class).disableTracking().descriptionStatusSubQuery(descriptionStatusQuery).isActive(IsActive.Active); if (forNexus) descriptionQuery.createdAfter(this._config.getNexusDate()); PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).isActive(IsActive.Active); diff --git a/backend/core/src/main/java/org/opencdmp/service/plan/PlanService.java b/backend/core/src/main/java/org/opencdmp/service/plan/PlanService.java index bf4cfbb60..f76694707 100644 --- a/backend/core/src/main/java/org/opencdmp/service/plan/PlanService.java +++ b/backend/core/src/main/java/org/opencdmp/service/plan/PlanService.java @@ -34,9 +34,7 @@ public interface PlanService { void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException, IOException; - void finalize(UUID id, List descriptionIds) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, IOException; - - void undoFinalize(UUID id, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, IOException; + void setStatus(UUID id, UUID newStatusId, List descriptionIds) throws InvalidApplicationException, IOException; PlanValidationResult validate(UUID id) throws InvalidApplicationException; 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 0b544757c..66c3b9311 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 @@ -96,11 +96,14 @@ import org.opencdmp.model.referencetype.ReferenceType; import org.opencdmp.query.*; import org.opencdmp.service.accounting.AccountingService; import org.opencdmp.service.actionconfirmation.ActionConfirmationService; +import org.opencdmp.service.custompolicy.CustomPolicyService; 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; +import org.opencdmp.service.planworkflow.PlanWorkflowServiceImpl; import org.opencdmp.service.responseutils.ResponseUtilsService; import org.opencdmp.service.usagelimit.UsageLimitService; import org.slf4j.LoggerFactory; @@ -183,6 +186,9 @@ public class PlanServiceImpl implements PlanService { private final PlanBlueprintService planBlueprintService; private final UsageLimitService usageLimitService; private final AccountingService accountingService; + private final DescriptionWorkflowService descriptionWorkflowService; + private final PlanWorkflowServiceImpl planWorkflowService; + private final CustomPolicyService customPolicyService; @Autowired public PlanServiceImpl( @@ -205,7 +211,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, PlanWorkflowServiceImpl planWorkflowService, CustomPolicyService customPolicyService) { this.entityManager = entityManager; this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; @@ -234,6 +240,9 @@ public class PlanServiceImpl implements PlanService { this.planBlueprintService = planBlueprintService; this.usageLimitService = usageLimitService; this.accountingService = accountingService; + this.descriptionWorkflowService = descriptionWorkflowService; + this.planWorkflowService = planWorkflowService; + this.customPolicyService = customPolicyService; } public Plan persist(PlanPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JAXBException, IOException { @@ -308,6 +317,9 @@ public class PlanServiceImpl implements PlanService { } private void sendNotification(PlanEntity plan) throws InvalidApplicationException { + PlanStatusEntity planStatusEntity = this.entityManager.find(PlanStatusEntity.class, plan.getStatusId(), true); + if (planStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{plan.getStatusId(), PlanStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + List existingUsers = this.queryFactory.query(PlanUserQuery.class).disableTracking() .planIds(plan.getId()) .isActives(IsActive.Active) @@ -321,22 +333,25 @@ public class PlanServiceImpl implements PlanService { 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.createPlanNotificationEvent(plan, user); + this.createPlanNotificationEvent(plan, planStatusEntity, user); } } } - private void createPlanNotificationEvent(PlanEntity plan, UserEntity user) throws InvalidApplicationException { + private void createPlanNotificationEvent(PlanEntity plan, PlanStatusEntity planStatus, UserEntity user) throws InvalidApplicationException { NotifyIntegrationEvent event = new NotifyIntegrationEvent(); event.setUserId(user.getId()); - this.applyNotificationType(plan.getStatus(), event); + if (planStatus.getInternalStatus() == null) event.setNotificationType(this.notificationProperties.getPlanStatusChangedType()); + else this.applyNotificationType(planStatus.getInternalStatus(), event); + NotificationFieldData data = new NotificationFieldData(); List fieldInfoList = new ArrayList<>(); fieldInfoList.add(new FieldInfo("{recipient}", DataType.String, user.getName())); fieldInfoList.add(new FieldInfo("{reasonName}", DataType.String, this.queryFactory.query(UserQuery.class).disableTracking().ids(this.userScope.getUserId()).first().getName())); fieldInfoList.add(new FieldInfo("{name}", DataType.String, plan.getLabel())); fieldInfoList.add(new FieldInfo("{id}", DataType.String, plan.getId().toString())); + if (planStatus.getInternalStatus() == null) fieldInfoList.add(new FieldInfo("{statusName}", DataType.String, planStatus.getName())); if(this.tenantScope.getTenantCode() != null && !this.tenantScope.getTenantCode().equals(this.tenantScope.getDefaultTenantCode())){ fieldInfoList.add(new FieldInfo("{tenant-url-path}", DataType.String, String.format("/t/%s", this.tenantScope.getTenantCode()))); } @@ -380,7 +395,10 @@ public class PlanServiceImpl implements PlanService { planQuery.setOrder(new Ordering().addDescending(Plan._version)); previousPlan = planQuery.count() > 0 ? planQuery.collect().getFirst() : null; if (previousPlan != null){ - if (previousPlan.getStatus().equals(PlanStatus.Finalized)) previousPlan.setVersionStatus(PlanVersionStatus.Current); + PlanStatusEntity previousPlanStatusEntity = this.entityManager.find(PlanStatusEntity.class, previousPlan.getStatusId(), true); + if (previousPlanStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{previousPlan.getStatusId(), PlanStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + if (previousPlanStatusEntity.getInternalStatus() != null && previousPlanStatusEntity.getInternalStatus().equals(PlanStatus.Finalized)) previousPlan.setVersionStatus(PlanVersionStatus.Current); else previousPlan.setVersionStatus(PlanVersionStatus.NotFinalized); this.entityManager.merge(previousPlan); } @@ -422,6 +440,9 @@ public class PlanServiceImpl implements PlanService { .count(); if (notFinalizedCount > 0) throw new MyValidationException(this.errors.getPlanNewVersionAlreadyCreatedDraft().getCode(), this.errors.getPlanNewVersionAlreadyCreatedDraft().getMessage()); + PlanStatusEntity startingPlanStatusEntity = this.entityManager.find(PlanStatusEntity.class, this.planWorkflowService.getWorkFlowDefinition().getStartingStatusId(), true); + if (startingPlanStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{this.planWorkflowService.getWorkFlowDefinition().getStartingStatusId(), PlanStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + PlanEntity newPlan = new PlanEntity(); newPlan.setId(UUID.randomUUID()); newPlan.setIsActive(IsActive.Active); @@ -434,6 +455,7 @@ public class PlanServiceImpl implements PlanService { newPlan.setLabel(model.getLabel()); newPlan.setLanguage(oldPlanEntity.getLanguage()); newPlan.setStatus(PlanStatus.Draft); + newPlan.setStatusId(startingPlanStatusEntity.getId()); newPlan.setProperties(oldPlanEntity.getProperties()); newPlan.setBlueprintId(model.getBlueprintId()); newPlan.setAccessType(oldPlanEntity.getAccessType()); @@ -585,7 +607,7 @@ public class PlanServiceImpl implements PlanService { this.entityManager.flush(); - this.updateVersionStatusAndSave(newPlan, PlanStatus.Draft, newPlan.getStatus()); + this.updateVersionStatusAndSave(newPlan, PlanStatus.Draft, startingPlanStatusEntity.getInternalStatus()); this.entityManager.flush(); @@ -620,6 +642,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())); @@ -692,7 +715,8 @@ public class PlanServiceImpl implements PlanService { List tags = new ArrayList<>(); try { this.entityManager.disableTenantFilters(); - existing = this.queryFactory.query(DescriptionQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(descriptionId).planSubQuery(this.queryFactory.query(PlanQuery.class).isActive(IsActive.Active).statuses(PlanStatus.Finalized).accessTypes(PlanAccessType.Public)).first(); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + existing = this.queryFactory.query(DescriptionQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(descriptionId).planSubQuery(this.queryFactory.query(PlanQuery.class).isActive(IsActive.Active).planStatusSubQuery(statusQuery).accessTypes(PlanAccessType.Public)).first(); if (existing == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{descriptionId, Description.class.getSimpleName()}, LocaleContextHolder.getLocale())); @@ -740,6 +764,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())); @@ -847,9 +872,11 @@ public class PlanServiceImpl implements PlanService { private void updateVersionStatusAndSave(PlanEntity data, PlanStatus previousStatus, PlanStatus newStatus) throws InvalidApplicationException { - if (previousStatus.equals(newStatus)) + if (previousStatus == null && newStatus == null) return; - if (previousStatus.equals(PlanStatus.Finalized) && newStatus.equals(PlanStatus.Draft)){ + if (previousStatus != null && previousStatus.equals(newStatus)) + return; + if (previousStatus != null && previousStatus.equals(PlanStatus.Finalized) && (newStatus == null || newStatus.equals(PlanStatus.Draft))){ boolean alreadyCreatedNewVersion = this.queryFactory.query(PlanQuery.class).disableTracking() .versionStatuses(PlanVersionStatus.NotFinalized, PlanVersionStatus.Current) .excludedIds(data.getId()) @@ -862,7 +889,7 @@ public class PlanServiceImpl implements PlanService { this.entityManager.merge(data); } - if (newStatus.equals(PlanStatus.Finalized)) { + if (newStatus != null && newStatus.equals(PlanStatus.Finalized)) { List latestVersionPlans = this.queryFactory.query(PlanQuery.class) .versionStatuses(PlanVersionStatus.Current).excludedIds(data.getId()) .isActive(IsActive.Active).groupIds(data.getGroupId()).collect(); @@ -907,6 +934,7 @@ public class PlanServiceImpl implements PlanService { newPlan.setLabel(model.getLabel()); newPlan.setLanguage(existingPlanEntity.getLanguage()); newPlan.setStatus(PlanStatus.Draft); + newPlan.setStatusId(this.planWorkflowService.getWorkFlowDefinition().getStartingStatusId()); newPlan.setProperties(existingPlanEntity.getProperties()); newPlan.setBlueprintId(existingPlanEntity.getBlueprintId()); newPlan.setAccessType(existingPlanEntity.getAccessType()); @@ -1018,7 +1046,8 @@ public class PlanServiceImpl implements PlanService { try { this.entityManager.disableTenantFilters(); // query for public plan - existingPlanEntity = this.queryFactory.query(PlanQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(model.getId()).isActive(IsActive.Active).statuses(PlanStatus.Finalized).accessTypes(PlanAccessType.Public).firstAs(fields); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + existingPlanEntity = this.queryFactory.query(PlanQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(model.getId()).isActive(IsActive.Active).planStatusSubQuery(statusQuery).accessTypes(PlanAccessType.Public).firstAs(fields); if (existingPlanEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), PublicPlan.class.getSimpleName()}, LocaleContextHolder.getLocale())); @@ -1095,6 +1124,7 @@ public class PlanServiceImpl implements PlanService { newPlan.setLabel(model.getLabel()); newPlan.setLanguage(existingPlanEntity.getLanguage()); newPlan.setStatus(PlanStatus.Draft); + newPlan.setStatusId(this.planWorkflowService.getWorkFlowDefinition().getStartingStatusId()); newPlan.setProperties(existingPlanEntity.getProperties()); newPlan.setBlueprintId(blueprintEntityByTenant.getId()); newPlan.setAccessType(existingPlanEntity.getAccessType()); @@ -1273,6 +1303,7 @@ public class PlanServiceImpl implements PlanService { PlanEntity data = this.entityManager.find(PlanEntity.class, model.getPlanId(), true); if (data == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Plan.class.getSimpleName()}, LocaleContextHolder.getLocale())); + if (data.getIsActive().equals(IsActive.Inactive)) throw new MyApplicationException("Plan is not Active"); List existingUsers = this.queryFactory.query(PlanUserQuery.class) .planIds(model.getPlanId()).ids(model.getId()).userRoles(model.getRole()) .collect(); @@ -1309,9 +1340,13 @@ public class PlanServiceImpl implements PlanService { data = this.entityManager.find(PlanEntity.class, model.getId()); if (data == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Plan.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 (model.getStatus() != null && model.getStatus() == PlanStatus.Finalized && data.getStatus() != PlanStatus.Finalized) { + PlanStatusEntity oldPlanStatusEntity = this.entityManager.find(PlanStatusEntity.class, data.getStatusId(), true); + if (oldPlanStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{data.getStatusId(), PlanStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + PlanStatusEntity newPlanStatusEntity = this.entityManager.find(PlanStatusEntity.class, model.getStatusId(), true); + if (newPlanStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{data.getStatusId(), PlanStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + if (newPlanStatusEntity.getInternalStatus() != null && newPlanStatusEntity.getInternalStatus().equals(PlanStatus.Finalized) && oldPlanStatusEntity.getInternalStatus() != null && oldPlanStatusEntity.getInternalStatus().equals(PlanStatus.Finalized)) { this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.planAffiliation(model.getId())), Permission.FinalizePlan); - data.setStatus(model.getStatus()); + data.setStatusId(model.getStatusId()); data.setFinalizedAt(Instant.now()); } } else { @@ -1320,13 +1355,14 @@ public class PlanServiceImpl implements PlanService { data.setGroupId(UUID.randomUUID()); data.setVersion((short) 1); data.setStatus(PlanStatus.Draft); + data.setStatusId(this.planWorkflowService.getWorkFlowDefinition().getStartingStatusId()); data.setVersionStatus(PlanVersionStatus.NotFinalized); data.setCreatorId(this.userScope.getUserId()); data.setBlueprintId(model.getBlueprint()); data.setIsActive(IsActive.Active); data.setCreatedAt(Instant.now()); } - PlanStatus previousStatus = data.getStatus(); +// PlanStatus previousStatus = data.getStatus(); PlanBlueprintEntity planBlueprintEntity = this.entityManager.find(PlanBlueprintEntity.class, model.getBlueprint(), true); if (planBlueprintEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getBlueprint(), PlanBlueprint.class.getSimpleName()}, LocaleContextHolder.getLocale())); @@ -1348,7 +1384,7 @@ public class PlanServiceImpl implements PlanService { this.entityManager.flush(); - this.updateVersionStatusAndSave(data, previousStatus, data.getStatus()); +// this.updateVersionStatusAndSave(data, previousStatus, data.getStatus()); this.entityManager.flush(); @@ -1574,51 +1610,88 @@ public class PlanServiceImpl implements PlanService { return data; } - public void finalize(UUID id, List descriptionIds) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, IOException { - this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.planAffiliation(id)), Permission.FinalizePlan); + public void setStatus(UUID id, UUID newStatusId, List descriptionIds) throws InvalidApplicationException, IOException { PlanEntity plan = this.queryFactory.query(PlanQuery.class).authorize(AuthorizationFlags.AllExceptPublic).ids(id).isActive(IsActive.Active).first(); + if (plan == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{id, Plan.class.getSimpleName()}, LocaleContextHolder.getLocale())); - if (plan == null){ - throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{id, Plan.class.getSimpleName()}, LocaleContextHolder.getLocale())); + try { + this.authorizationService.authorizeForce(this.customPolicyService.getPlanStatusCanEditStatusPermission(newStatusId)); + } catch (Exception e) { + this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.planAffiliation(id)), this.customPolicyService.getPlanStatusCanEditStatusPermission(newStatusId)); } - if (plan.getStatus().equals(PlanStatus.Finalized)){ + if (plan.getStatusId().equals(newStatusId)) throw new MyApplicationException("Old status equals with new"); + + PlanStatusEntity oldPlanStatusEntity = this.entityManager.find(PlanStatusEntity.class, plan.getStatusId(), true); + if (oldPlanStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{plan.getStatusId(), PlanStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + PlanStatusEntity newPlanStatusEntity = this.entityManager.find(PlanStatusEntity.class, newStatusId, true); + if (newPlanStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{newStatusId, PlanStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + if (oldPlanStatusEntity.getInternalStatus() != null && oldPlanStatusEntity.getInternalStatus().equals(PlanStatus.Finalized)) { + this.undoFinalize(plan, oldPlanStatusEntity, newPlanStatusEntity); + } else if (newPlanStatusEntity.getInternalStatus() != null && newPlanStatusEntity.getInternalStatus().equals(PlanStatus.Finalized)) { + this.finalize(plan, descriptionIds ,oldPlanStatusEntity, newPlanStatusEntity); + } else { + plan.setStatusId(newPlanStatusEntity.getId()); + plan.setUpdatedAt(Instant.now()); + + this.entityManager.merge(plan); + this.entityManager.flush(); + this.sendNotification(plan); + } + } + + private void finalize(PlanEntity plan, List descriptionIds, PlanStatusEntity oldPlanStatusEntity, PlanStatusEntity newPlanStatusEntity) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, IOException { + this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.planAffiliation(plan.getId())), Permission.FinalizePlan); + + if (oldPlanStatusEntity.getInternalStatus() != null && oldPlanStatusEntity.getInternalStatus().equals(PlanStatus.Finalized)){ throw new MyApplicationException("Plan is already finalized"); } - if (this.validate(id).getResult().equals(PlanValidationOutput.Invalid)){ + if (this.validate(plan.getId()).getResult().equals(PlanValidationOutput.Invalid)){ throw new MyApplicationException("Plan is invalid"); } List descriptions = this.queryFactory.query(DescriptionQuery.class) - .authorize(AuthorizationFlags.AllExceptPublic).planIds(id).isActive(IsActive.Active).collect(); + .authorize(AuthorizationFlags.AllExceptPublic).planIds(plan.getId()).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 descriptionFinalizedStatusEntity = this.queryFactory.query(DescriptionStatusQuery.class).disableTracking().internalStatuses(DescriptionStatus.Finalized).isActive(IsActive.Active).firstAs(new BaseFieldSet().ensure(org.opencdmp.model.descriptionstatus.DescriptionStatus._id)); + if (descriptionFinalizedStatusEntity == 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"); + } + description.setStatusId(descriptionFinalizedStatusEntity.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), false); } - 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); } } - - PlanStatus previousStatus = plan.getStatus(); - plan.setStatus(PlanStatus.Finalized); + + plan.setStatusId(newPlanStatusEntity.getId()); plan.setUpdatedAt(Instant.now()); plan.setFinalizedAt(Instant.now()); - this.updateVersionStatusAndSave(plan, previousStatus, plan.getStatus()); + this.updateVersionStatusAndSave(plan, oldPlanStatusEntity.getInternalStatus(), newPlanStatusEntity.getInternalStatus()); plan.setVersionStatus(PlanVersionStatus.Current); this.entityManager.merge(plan); @@ -1630,24 +1703,21 @@ public class PlanServiceImpl implements PlanService { this.sendNotification(plan); } - public void undoFinalize(UUID id, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException { - this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.planAffiliation(id)), Permission.UndoFinalizePlan); - PlanEntity plan = this.queryFactory.query(PlanQuery.class).authorize(AuthorizationFlags.AllExceptPublic).ids(id).isActive(IsActive.Active).firstAs(fields); + private void undoFinalize(PlanEntity plan, PlanStatusEntity oldPlanStatusEntity, PlanStatusEntity newPlanStatusEntity) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException { + this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.planAffiliation(plan.getId())), Permission.UndoFinalizePlan); - if (plan == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{id, Plan.class.getSimpleName()}, LocaleContextHolder.getLocale())); - - if (!plan.getStatus().equals(PlanStatus.Finalized)) throw new MyApplicationException("Plan is already drafted"); + if (oldPlanStatusEntity.getInternalStatus() == null && !oldPlanStatusEntity.getInternalStatus().equals(PlanStatus.Finalized)) throw new MyApplicationException("Plan is already non finalized"); EntityDoiQuery entityDoiQuery = this.queryFactory.query(EntityDoiQuery.class).types(EntityType.Plan).entityIds(plan.getId()).isActive(IsActive.Active); if (entityDoiQuery.count() > 0) throw new MyApplicationException("Plan is deposited"); - plan.setStatus(PlanStatus.Draft); + plan.setStatusId(newPlanStatusEntity.getId()); plan.setUpdatedAt(Instant.now()); this.entityManager.merge(plan); this.entityManager.flush(); - this.updateVersionStatusAndSave(plan, PlanStatus.Finalized, plan.getStatus()); + this.updateVersionStatusAndSave(plan, PlanStatus.Finalized, newPlanStatusEntity.getInternalStatus()); this.entityManager.flush(); PlanQuery planQuery = this.queryFactory.query(PlanQuery.class).disableTracking() @@ -1659,7 +1729,10 @@ public class PlanServiceImpl implements PlanService { planQuery.setOrder(new Ordering().addDescending(Plan._version)); PlanEntity previousPlan = planQuery.count() > 0 ? planQuery.collect().getFirst() : null; if (previousPlan != null){ - if (previousPlan.getStatus().equals(PlanStatus.Finalized)) previousPlan.setVersionStatus(PlanVersionStatus.Current); + PlanStatusEntity previousPlanStatusEntity = this.entityManager.find(PlanStatusEntity.class, previousPlan.getStatusId(), true); + if (previousPlanStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{previousPlan.getStatusId(), PlanStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + if (previousPlanStatusEntity.getInternalStatus() != null && previousPlanStatusEntity.getInternalStatus().equals(PlanStatus.Finalized)) previousPlan.setVersionStatus(PlanVersionStatus.Current); else previousPlan.setVersionStatus(PlanVersionStatus.NotFinalized); this.entityManager.merge(previousPlan); } @@ -1697,7 +1770,8 @@ public class PlanServiceImpl implements PlanService { persist.setId(data.getId()); persist.setHash(data.getId().toString()); persist.setLabel(data.getLabel()); - persist.setStatus(PlanStatus.Finalized); + PlanStatusEntity statusEntity = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active).firstAs(new BaseFieldSet().ensure(org.opencdmp.model.planstatus.PlanStatus._id)); + if (statusEntity != null) persist.setStatusId(statusEntity.getId()); persist.setDescription(data.getDescription()); persist.setBlueprint(data.getBlueprintId()); persist.setAccessType(data.getAccessType()); @@ -2031,7 +2105,8 @@ public class PlanServiceImpl implements PlanService { } else { try { this.entityManager.disableTenantFilters(); - data = this.queryFactory.query(PlanQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).isActive(IsActive.Active).statuses(PlanStatus.Finalized).accessTypes(PlanAccessType.Public).first(); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + data = this.queryFactory.query(PlanQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).isActive(IsActive.Active).planStatusSubQuery(statusQuery).accessTypes(PlanAccessType.Public).first(); this.entityManager.reloadTenantFilters(); } finally { this.entityManager.reloadTenantFilters(); @@ -2065,7 +2140,8 @@ public class PlanServiceImpl implements PlanService { PlanEntity data = null; try { this.entityManager.disableTenantFilters(); - data = this.queryFactory.query(PlanQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).isActive(IsActive.Active).statuses(PlanStatus.Finalized).accessTypes(PlanAccessType.Public).first(); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + data = this.queryFactory.query(PlanQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).isActive(IsActive.Active).planStatusSubQuery(statusQuery).accessTypes(PlanAccessType.Public).first(); this.entityManager.reloadTenantFilters(); } finally { this.entityManager.reloadTenantFilters(); @@ -2088,6 +2164,7 @@ public class PlanServiceImpl implements PlanService { xml.setTitle(data.getLabel()); xml.setLanguage(data.getLanguage()); xml.setAccess(data.getAccessType()); + xml.setStatus(this.planStatusImportExportToExport(data.getStatusId())); xml.setFinalizedAt(data.getFinalizedAt()); xml.setPublicAfter(data.getPublicAfter()); xml.setVersion(data.getVersion()); @@ -2101,6 +2178,17 @@ public class PlanServiceImpl implements PlanService { return xml; } + + private PlanStatusImportExport planStatusImportExportToExport(UUID statusId) throws InvalidApplicationException { + PlanStatusImportExport xml = new PlanStatusImportExport(); + if (statusId == null) return xml; + PlanStatusEntity planStatusEntity = this.entityManager.find(PlanStatusEntity.class, statusId, true); + if (planStatusEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{statusId, PlanStatusEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + xml.setId(planStatusEntity.getId()); + xml.setName(planStatusEntity.getName()); + return xml; + } private List descriptionsToExport(PlanEntity data, Boolean isPublic) throws JAXBException, InvalidApplicationException, ParserConfigurationException, IOException, InstantiationException, IllegalAccessException, SAXException { List descriptions; @@ -2299,7 +2387,6 @@ public class PlanServiceImpl implements PlanService { PlanPersist persist = new PlanPersist(); persist.setLabel(label); - persist.setStatus(PlanStatus.Draft); persist.setDescription(planXml.getDescription()); persist.setAccessType(planXml.getAccess()); persist.setLanguage(planXml.getLanguage()); @@ -2601,7 +2688,6 @@ public class PlanServiceImpl implements PlanService { PlanPersist persist = new PlanPersist(); persist.setLabel(planCommonModelConfig.getLabel()); - persist.setStatus(PlanStatus.Draft); persist.setDescription(model.getDescription()); switch (model.getAccessType()) { case Public -> persist.setAccessType(PlanAccessType.Public); diff --git a/backend/core/src/main/java/org/opencdmp/service/planstatus/PlanStatusService.java b/backend/core/src/main/java/org/opencdmp/service/planstatus/PlanStatusService.java index f98cb87dd..59f200c3f 100644 --- a/backend/core/src/main/java/org/opencdmp/service/planstatus/PlanStatusService.java +++ b/backend/core/src/main/java/org/opencdmp/service/planstatus/PlanStatusService.java @@ -10,10 +10,14 @@ import org.opencdmp.model.persist.planstatus.PlanStatusPersist; import org.opencdmp.model.planstatus.PlanStatus; import javax.management.InvalidApplicationException; +import java.util.List; +import java.util.Map; import java.util.UUID; public interface PlanStatusService { PlanStatus persist(PlanStatusPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JAXBException; void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException; + + Map> getAuthorizedAvailableStatusIds(List planIds); } diff --git a/backend/core/src/main/java/org/opencdmp/service/planstatus/PlanStatusServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/planstatus/PlanStatusServiceImpl.java index 3ac7b896d..5f01a563c 100644 --- a/backend/core/src/main/java/org/opencdmp/service/planstatus/PlanStatusServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/planstatus/PlanStatusServiceImpl.java @@ -3,6 +3,7 @@ package org.opencdmp.service.planstatus; 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; @@ -13,24 +14,36 @@ import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.MapLogEntry; import jakarta.xml.bind.JAXBException; import org.jetbrains.annotations.NotNull; +import org.opencdmp.authorization.AffiliatedResource; import org.opencdmp.authorization.Permission; +import org.opencdmp.authorization.authorizationcontentresolver.AuthorizationContentResolver; import org.opencdmp.commons.XmlHandlingService; import org.opencdmp.commons.enums.IsActive; +import org.opencdmp.commons.scope.tenant.TenantScope; import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionAuthorizationEntity; import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionAuthorizationItemEntity; import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionEntity; +import org.opencdmp.commons.types.planworkflow.PlanWorkflowDefinitionEntity; +import org.opencdmp.commons.types.planworkflow.PlanWorkflowDefinitionTransitionEntity; import org.opencdmp.convention.ConventionService; +import org.opencdmp.data.PlanEntity; import org.opencdmp.data.PlanStatusEntity; import org.opencdmp.data.TenantEntityManager; import org.opencdmp.errorcode.ErrorThesaurusProperties; import org.opencdmp.event.EventBroker; +import org.opencdmp.event.PlanStatusTouchedEvent; import org.opencdmp.model.builder.planstatus.PlanStatusBuilder; import org.opencdmp.model.deleter.PlanStatusDeleter; import org.opencdmp.model.persist.planstatus.PlanStatusDefinitionAuthorizationItemPersist; import org.opencdmp.model.persist.planstatus.PlanStatusDefinitionAuthorizationPersist; import org.opencdmp.model.persist.planstatus.PlanStatusDefinitionPersist; import org.opencdmp.model.persist.planstatus.PlanStatusPersist; +import org.opencdmp.model.plan.Plan; import org.opencdmp.model.planstatus.PlanStatus; +import org.opencdmp.query.PlanQuery; +import org.opencdmp.query.PlanStatusQuery; +import org.opencdmp.service.custompolicy.CustomPolicyService; +import org.opencdmp.service.planworkflow.PlanWorkflowService; import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; @@ -38,8 +51,8 @@ import org.springframework.stereotype.Service; import javax.management.InvalidApplicationException; import java.time.Instant; -import java.util.List; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; @Service public class PlanStatusServiceImpl implements PlanStatusService { @@ -55,8 +68,14 @@ public class PlanStatusServiceImpl implements PlanStatusService { private final TenantEntityManager entityManager; private final MessageSource messageSource; private final ErrorThesaurusProperties errors; + private final EventBroker eventBroker; + private final TenantScope tenantScope; + private final PlanWorkflowService planWorkflowService; + private final CustomPolicyService customPolicyService; + private final AuthorizationContentResolver authorizationContentResolver; + private final QueryFactory queryFactory; - public PlanStatusServiceImpl(BuilderFactory builderFactory, DeleterFactory deleterFactory, AuthorizationService authorizationService, ConventionService conventionService, XmlHandlingService xmlHandlingService, TenantEntityManager entityManager, MessageSource messageSource, ErrorThesaurusProperties errors, EventBroker eventBroker) { + public PlanStatusServiceImpl(BuilderFactory builderFactory, DeleterFactory deleterFactory, AuthorizationService authorizationService, ConventionService conventionService, XmlHandlingService xmlHandlingService, TenantEntityManager entityManager, MessageSource messageSource, ErrorThesaurusProperties errors, EventBroker eventBroker, TenantScope tenantScope, PlanWorkflowService planWorkflowService, CustomPolicyService customPolicyService, AuthorizationContentResolver authorizationContentResolver, QueryFactory queryFactory) { this.builderFactory = builderFactory; this.deleterFactory = deleterFactory; @@ -66,6 +85,12 @@ public class PlanStatusServiceImpl implements PlanStatusService { this.entityManager = entityManager; this.messageSource = messageSource; this.errors = errors; + this.eventBroker = eventBroker; + this.tenantScope = tenantScope; + this.planWorkflowService = planWorkflowService; + this.customPolicyService = customPolicyService; + this.authorizationContentResolver = authorizationContentResolver; + this.queryFactory = queryFactory; } @Override @@ -90,6 +115,7 @@ public class PlanStatusServiceImpl implements PlanStatusService { data.setName(model.getName()); data.setDescription(model.getDescription()); + data.setAction(model.getAction()); data.setInternalStatus(model.getInternalStatus()); data.setDefinition(this.xmlHandlingService.toXml(this.buildPlanStatusDefinitionEntity(model.getDefinition()))); data.setUpdatedAt(Instant.now()); @@ -101,6 +127,8 @@ public class PlanStatusServiceImpl implements PlanStatusService { this.entityManager.flush(); + this.eventBroker.emit(new PlanStatusTouchedEvent(data.getId(), this.tenantScope.getTenantCode())); + return this.builderFactory.builder(PlanStatusBuilder.class).build(BaseFieldSet.build(fields, PlanStatus._id), data); } @@ -122,6 +150,8 @@ public class PlanStatusServiceImpl implements PlanStatusService { PlanStatusDefinitionAuthorizationEntity definitionAuthorizationData = this.buildPlanStatusDefinitionAuthorizationEntity(persist.getAuthorization()); data.setAuthorization(definitionAuthorizationData); } + + if (!this.conventionService.isListNullOrEmpty(persist.getAvailableActions())) data.setAvailableActions(persist.getAvailableActions()); return data; } @@ -149,4 +179,34 @@ public class PlanStatusServiceImpl implements PlanStatusService { return data; } + + @Override + public Map> getAuthorizedAvailableStatusIds(List planIds) { + + Map> authorizedStatusMap = new HashMap<>(); + PlanWorkflowDefinitionEntity definition; + try { + definition = this.planWorkflowService.getWorkFlowDefinition(); + } catch (InvalidApplicationException e) { + throw new RuntimeException(e); + } + + List planEntities = this.queryFactory.query(PlanQuery.class).ids(planIds).isActive(IsActive.Active).collectAs(new BaseFieldSet().ensure(Plan._id).ensure(Plan._status)); + List statusEntities = this.queryFactory.query(PlanStatusQuery.class).isActives(IsActive.Active).collectAs(new BaseFieldSet().ensure(PlanStatus._id)); + for (PlanEntity plan: planEntities) { + authorizedStatusMap.put(plan.getId(), new ArrayList<>()); + AffiliatedResource affiliatedResource = this.authorizationContentResolver.planAffiliation(plan.getId()); + for (PlanStatusEntity status: statusEntities) { + + List availableTransitions = definition.getStatusTransitions().stream().filter(x -> x.getFromStatusId().equals(plan.getStatusId())).collect(Collectors.toList()); + if (availableTransitions.stream().filter(x -> x.getToStatusId().equals(status.getId())).findFirst().orElse(null) != null) { + String editPermission = this.customPolicyService.getPlanStatusCanEditStatusPermission(status.getId()); + Boolean isAllowed = affiliatedResource == null ? this.authorizationService.authorize(editPermission) : this.authorizationService.authorizeAtLeastOne(List.of(affiliatedResource), editPermission); + if (isAllowed) authorizedStatusMap.get(plan.getId()).add(status.getId()); + } + } + } + + return authorizedStatusMap; + } } diff --git a/backend/core/src/main/java/org/opencdmp/service/planworkflow/PlanWorkflowService.java b/backend/core/src/main/java/org/opencdmp/service/planworkflow/PlanWorkflowService.java index 649cbdb38..7f770ba7a 100644 --- a/backend/core/src/main/java/org/opencdmp/service/planworkflow/PlanWorkflowService.java +++ b/backend/core/src/main/java/org/opencdmp/service/planworkflow/PlanWorkflowService.java @@ -6,6 +6,7 @@ import gr.cite.tools.exception.MyNotFoundException; import gr.cite.tools.exception.MyValidationException; import gr.cite.tools.fieldset.FieldSet; import jakarta.xml.bind.JAXBException; +import org.opencdmp.commons.types.planworkflow.PlanWorkflowDefinitionEntity; import org.opencdmp.model.persist.planworkflow.PlanWorkflowPersist; import org.opencdmp.model.planworkflow.PlanWorkflow; @@ -16,4 +17,6 @@ public interface PlanWorkflowService { PlanWorkflow persist(PlanWorkflowPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JAXBException; void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException; + + PlanWorkflowDefinitionEntity getWorkFlowDefinition() throws InvalidApplicationException; } diff --git a/backend/core/src/main/java/org/opencdmp/service/planworkflow/PlanWorkflowServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/planworkflow/PlanWorkflowServiceImpl.java index 5c963be91..8d81e2f3e 100644 --- a/backend/core/src/main/java/org/opencdmp/service/planworkflow/PlanWorkflowServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/planworkflow/PlanWorkflowServiceImpl.java @@ -3,6 +3,7 @@ package org.opencdmp.service.planworkflow; 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,9 +13,11 @@ 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.scope.tenant.TenantScope; import org.opencdmp.commons.types.planworkflow.PlanWorkflowDefinitionEntity; import org.opencdmp.commons.types.planworkflow.PlanWorkflowDefinitionTransitionEntity; import org.opencdmp.convention.ConventionService; @@ -27,6 +30,7 @@ import org.opencdmp.model.persist.planworkflow.PlanWorkflowDefinitionPersist; import org.opencdmp.model.persist.planworkflow.PlanWorkflowDefinitionTransitionPersist; import org.opencdmp.model.persist.planworkflow.PlanWorkflowPersist; import org.opencdmp.model.planworkflow.PlanWorkflow; +import org.opencdmp.query.PlanWorkflowQuery; import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; @@ -51,7 +55,10 @@ public class PlanWorkflowServiceImpl implements PlanWorkflowService { private final DeleterFactory deleterFactory; private final MessageSource messageSource; private final ErrorThesaurusProperties errors; - public PlanWorkflowServiceImpl(AuthorizationService authorizationService, ConventionService conventionService, XmlHandlingService xmlHandlingService, TenantEntityManager entityManager, BuilderFactory builderFactory, DeleterFactory deleterFactory, MessageSource messageSource, ErrorThesaurusProperties errors) { + private final TenantScope tenantScope; + private final QueryFactory queryFactory; + + public PlanWorkflowServiceImpl(AuthorizationService authorizationService, ConventionService conventionService, XmlHandlingService xmlHandlingService, TenantEntityManager entityManager, BuilderFactory builderFactory, DeleterFactory deleterFactory, MessageSource messageSource, ErrorThesaurusProperties errors, TenantScope tenantScope, QueryFactory queryFactory) { this.authorizationService = authorizationService; this.conventionService = conventionService; this.xmlHandlingService = xmlHandlingService; @@ -60,6 +67,8 @@ public class PlanWorkflowServiceImpl implements PlanWorkflowService { this.deleterFactory = deleterFactory; this.messageSource = messageSource; this.errors = errors; + this.tenantScope = tenantScope; + this.queryFactory = queryFactory; } @Override @@ -136,4 +145,36 @@ public class PlanWorkflowServiceImpl implements PlanWorkflowService { return data; } + + @Override + public PlanWorkflowDefinitionEntity getWorkFlowDefinition() throws InvalidApplicationException { + PlanWorkflowQuery query = this.queryFactory.query(PlanWorkflowQuery.class).authorize(AuthorizationFlags.AllExceptPublic).isActives(IsActive.Active); + + if (this.tenantScope.isDefaultTenant()) query = query.defaultTenant(true); + + List planWorkflowEntities = query.collect(); + if (this.conventionService.isListNullOrEmpty(planWorkflowEntities)) throw new MyApplicationException("Plan workflows not found!"); + PlanWorkflowEntity entity = null; + if (!this.tenantScope.isDefaultTenant()) { + entity = planWorkflowEntities.stream().filter(x -> { + try { + return (this.tenantScope.getTenant().equals(x.getTenantId())); + } catch (InvalidApplicationException e) { + throw new RuntimeException(e); + } + }).findFirst().orElse(null); + } + + // fallback to default tenant + if (entity == null) { + entity = planWorkflowEntities.stream().filter(x -> x.getTenantId() == null).findFirst().orElse(null); + } + + if (entity == null) throw new MyApplicationException("Plan workflow not found!"); + + PlanWorkflowDefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(PlanWorkflowDefinitionEntity.class, entity.getDefinition()); + if (definition == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{entity.getId(), PlanWorkflowDefinitionEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + return definition; + } } diff --git a/backend/web/pom.xml b/backend/web/pom.xml index c5ec3c171..29727e9ef 100644 --- a/backend/web/pom.xml +++ b/backend/web/pom.xml @@ -128,6 +128,11 @@ exceptions-web 2.2.0 + + gr.cite + oidc-authz + 2.2.0 + diff --git a/backend/web/src/main/java/org/opencdmp/authorization/AffiliatedAuthorizationHandler.java b/backend/web/src/main/java/org/opencdmp/authorization/AffiliatedAuthorizationHandler.java index 920ad5510..6f593e97d 100644 --- a/backend/web/src/main/java/org/opencdmp/authorization/AffiliatedAuthorizationHandler.java +++ b/backend/web/src/main/java/org/opencdmp/authorization/AffiliatedAuthorizationHandler.java @@ -19,7 +19,7 @@ public class AffiliatedAuthorizationHandler extends AuthorizationHandler 0)) diff --git a/backend/web/src/main/java/org/opencdmp/authorization/CustomPermissionAttributesConfiguration.java b/backend/web/src/main/java/org/opencdmp/authorization/CustomPermissionAttributesConfiguration.java index f2a078b08..5846a59f2 100644 --- a/backend/web/src/main/java/org/opencdmp/authorization/CustomPermissionAttributesConfiguration.java +++ b/backend/web/src/main/java/org/opencdmp/authorization/CustomPermissionAttributesConfiguration.java @@ -1,24 +1,94 @@ package org.opencdmp.authorization; +import org.opencdmp.commons.types.descriptionstatus.DescriptionStatusDefinitionEntity; +import org.opencdmp.commons.types.planstatus.PlanStatusDefinitionEntity; +import org.opencdmp.event.DescriptionStatusTouchedEvent; +import org.opencdmp.event.PlanStatusTouchedEvent; +import org.opencdmp.service.custompolicy.CustomPolicyCacheService; +import org.opencdmp.service.custompolicy.CustomPolicyService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.UUID; @Configuration @EnableConfigurationProperties(CustomPermissionAttributesProperties.class) public class CustomPermissionAttributesConfiguration { private final CustomPermissionAttributesProperties properties; + private HashMap myPolicies; + private final CustomPolicyService customPolicyService; + private final CustomPolicyCacheService customPolicyCacheService; @Autowired - public CustomPermissionAttributesConfiguration(CustomPermissionAttributesProperties properties) { + public CustomPermissionAttributesConfiguration(CustomPermissionAttributesProperties properties, CustomPolicyService customPolicyService, CustomPolicyCacheService customPolicyCacheService) { this.properties = properties; + this.customPolicyService = customPolicyService; + this.customPolicyCacheService = customPolicyCacheService; + } + + @EventListener + public void handlePlanTouchedEvent(PlanStatusTouchedEvent event) { + this.customPolicyCacheService.clearCache(event); + this.refresh(true); + } + + @EventListener + public void handleDescriptionStatusTouchedEvent(DescriptionStatusTouchedEvent event) { + this.customPolicyCacheService.clearCache(event); + this.refresh(true); } public HashMap getMyPolicies() { + if (this.myPolicies == null) this.refresh(false); + return properties.getPolicies(); } + public void refresh(boolean force) { + if (!force && this.myPolicies != null) return; + this.myPolicies = this.properties.getPolicies(); + this.myPolicies.putAll(this.buildAffiliatedCustomPermissions()); + } + + private HashMap buildAffiliatedCustomPermissions() { + HashMap affiliatedCustomPermissions = new HashMap<>(); + this.buildAffiliatedPlanCustomPermissions(affiliatedCustomPermissions); + this.buildAffiliatedDescriptionCustomPermissions(affiliatedCustomPermissions); + + return affiliatedCustomPermissions; + } + + private void buildAffiliatedPlanCustomPermissions(HashMap affiliatedCustomPermissions) { + Map map = this.customPolicyService.buildPlanStatusDefinitionMap(); + if (map == null) return; + + for (UUID statusId: map.keySet()) { + PlanStatusDefinitionEntity definition = map.get(statusId); + if (definition != null && definition.getAuthorization() != null && definition.getAuthorization().getEdit() != null) { + CustomPermissionAttributesProperties.MyPermission myPermission = new CustomPermissionAttributesProperties.MyPermission(new PlanRole(new HashSet<>(definition.getAuthorization().getEdit().getPlanRoles())), null); + affiliatedCustomPermissions.put(this.customPolicyService.getPlanStatusCanEditStatusPermission(statusId), myPermission); + } + } + + } + + private void buildAffiliatedDescriptionCustomPermissions(HashMap affiliatedCustomPermissions) { + Map map = this.customPolicyService.buildDescriptionStatusDefinitionMap(); + if (map == null) return; + + for (UUID statusId: map.keySet()) { + DescriptionStatusDefinitionEntity definition = map.get(statusId); + if (definition != null && definition.getAuthorization() != null && definition.getAuthorization().getEdit() != null) { + CustomPermissionAttributesProperties.MyPermission myPermission = new CustomPermissionAttributesProperties.MyPermission(new PlanRole(new HashSet<>(definition.getAuthorization().getEdit().getPlanRoles())), null); + affiliatedCustomPermissions.put(this.customPolicyService.getDescriptionStatusCanEditStatusPermission(statusId), myPermission); + } + } + } + } diff --git a/backend/web/src/main/java/org/opencdmp/configurations/OpencdmpPermissionPolicyContextImpl.java b/backend/web/src/main/java/org/opencdmp/configurations/OpencdmpPermissionPolicyContextImpl.java new file mode 100644 index 000000000..819bbe8c1 --- /dev/null +++ b/backend/web/src/main/java/org/opencdmp/configurations/OpencdmpPermissionPolicyContextImpl.java @@ -0,0 +1,50 @@ +package org.opencdmp.configurations; + +import gr.cite.commons.web.authz.configuration.AuthorizationConfiguration; +import gr.cite.commons.web.authz.configuration.PermissionPolicyContextImpl; +import gr.cite.tools.logging.LoggerService; +import org.opencdmp.event.DescriptionStatusTouchedEvent; +import org.opencdmp.event.PlanStatusTouchedEvent; +import org.opencdmp.service.custompolicy.CustomPolicyCacheService; +import org.opencdmp.service.custompolicy.CustomPolicyService; +import org.slf4j.LoggerFactory; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + + +@Service +public class OpencdmpPermissionPolicyContextImpl extends PermissionPolicyContextImpl { + private final CustomPolicyService customPolicyService; + private final CustomPolicyCacheService customPolicyCacheService; + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(OpencdmpPermissionPolicyContextImpl.class)); + + public OpencdmpPermissionPolicyContextImpl(AuthorizationConfiguration authorizationConfiguration, CustomPolicyService customPolicyService, CustomPolicyCacheService customPolicyCacheService) { + super(authorizationConfiguration); + this.customPolicyService = customPolicyService; + this.customPolicyCacheService = customPolicyCacheService; + } + + @EventListener + public void handlePlanTouchedEvent(PlanStatusTouchedEvent event) { + this.customPolicyCacheService.clearCache(event); + this.refresh(true); + } + + @EventListener + public void handleDescriptionStatusTouchedEvent(DescriptionStatusTouchedEvent event) { + this.customPolicyCacheService.clearCache(event); + this.refresh(true); + } + + @Override + public void refresh(boolean force) { + if (!force && this.policies != null) return; + this.policies = this.authorizationConfiguration.getRawPolicies(); + this.extendedClaims = this.authorizationConfiguration.getRawExtendedClaims(); + this.policies.putAll(this.customPolicyService.buildPlanStatusPolicies()); + this.policies.putAll(this.customPolicyService.buildDescriptionStatusPolicies()); + logger.info("Authorization policies found: {}", this.policies.size()); + this.reload(); + } + +} diff --git a/backend/web/src/main/java/org/opencdmp/configurations/SecurityConfiguration.java b/backend/web/src/main/java/org/opencdmp/configurations/SecurityConfiguration.java index 065766c5f..460a1cab3 100644 --- a/backend/web/src/main/java/org/opencdmp/configurations/SecurityConfiguration.java +++ b/backend/web/src/main/java/org/opencdmp/configurations/SecurityConfiguration.java @@ -3,7 +3,6 @@ package org.opencdmp.configurations; import org.opencdmp.authorization.*; import gr.cite.commons.web.authz.handler.AuthorizationHandler; -import gr.cite.commons.web.authz.handler.PermissionClientAuthorizationHandler; import gr.cite.commons.web.authz.policy.AuthorizationRequirement; import gr.cite.commons.web.authz.policy.AuthorizationRequirementMapper; import gr.cite.commons.web.authz.policy.AuthorizationResource; @@ -26,9 +25,7 @@ import org.springframework.security.web.authentication.preauth.AbstractPreAuthen import jakarta.servlet.Filter; import jakarta.servlet.http.HttpServletRequest; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; +import java.util.*; @Configuration @EnableWebSecurity @@ -42,10 +39,10 @@ public class SecurityConfiguration { @Autowired public SecurityConfiguration(WebSecurityProperties webSecurityProperties, - @Qualifier("tokenAuthenticationResolver") AuthenticationManagerResolver authenticationManagerResolver, - @Qualifier("apiKeyFilter") Filter apiKeyFilter, - @Qualifier("ownedAuthorizationHandler") OwnedAuthorizationHandler ownedAuthorizationHandler, - @Qualifier("affiliatedAuthorizationHandler") AffiliatedAuthorizationHandler affiliatedAuthorizationHandler) { + @Qualifier("tokenAuthenticationResolver") AuthenticationManagerResolver authenticationManagerResolver, + @Qualifier("apiKeyFilter") Filter apiKeyFilter, + @Qualifier("ownedAuthorizationHandler") OwnedAuthorizationHandler ownedAuthorizationHandler, + @Qualifier("affiliatedAuthorizationHandler") AffiliatedAuthorizationHandler affiliatedAuthorizationHandler) { this.webSecurityProperties = webSecurityProperties; this.authenticationManagerResolver = authenticationManagerResolver; this.apiKeyFilter = apiKeyFilter; @@ -107,6 +104,11 @@ public class SecurityConfiguration { }; } +// @Bean() +// public PermissionPolicyContext permissionPolicyContext(){ +// return new PermissionPolicyContextImpl(configuration); +// } + @Bean AuthorizationRequirementMapper authorizationRequirementMapper() { return new AuthorizationRequirementMapper() { @@ -147,4 +149,5 @@ public class SecurityConfiguration { } return endpoint; } -} \ No newline at end of file +} + diff --git a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java index c657fcb79..72392840b 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java @@ -45,6 +45,7 @@ import org.opencdmp.model.persist.*; import org.opencdmp.model.result.QueryResult; import org.opencdmp.query.DescriptionQuery; import org.opencdmp.query.PlanQuery; +import org.opencdmp.query.PlanStatusQuery; import org.opencdmp.query.lookup.DescriptionLookup; import org.opencdmp.service.description.DescriptionService; import org.opencdmp.service.elastic.ElasticQueryHelperService; @@ -147,7 +148,8 @@ public class DescriptionController { this.censorFactory.censor(PublicDescriptionCensor.class).censor(fieldSet); - DescriptionQuery query = this.queryFactory.query(DescriptionQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).planSubQuery(this.queryFactory.query(PlanQuery.class).isActive(IsActive.Active).statuses(PlanStatus.Finalized).accessTypes(PlanAccessType.Public)); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + DescriptionQuery query = this.queryFactory.query(DescriptionQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).planSubQuery(this.queryFactory.query(PlanQuery.class).isActive(IsActive.Active).planStatusSubQuery(statusQuery).accessTypes(PlanAccessType.Public)); PublicDescription model = this.builderFactory.builder(PublicDescriptionBuilder.class).authorize(EnumSet.of(Public)).build(fieldSet, query.firstAs(fieldSet)); if (model == null) 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..c4abba187 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionStatusController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionStatusController.java @@ -183,4 +183,5 @@ public class DescriptionStatusController { this.auditService.track(AuditableAction.DescriptionStatus_Delete, "id", id); } + } diff --git a/backend/web/src/main/java/org/opencdmp/controllers/PlanController.java b/backend/web/src/main/java/org/opencdmp/controllers/PlanController.java index fb8dcde83..f95a2c515 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/PlanController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/PlanController.java @@ -43,6 +43,7 @@ import org.opencdmp.model.persist.*; import org.opencdmp.model.plan.Plan; import org.opencdmp.model.result.QueryResult; import org.opencdmp.query.PlanQuery; +import org.opencdmp.query.PlanStatusQuery; import org.opencdmp.query.lookup.PlanLookup; import org.opencdmp.service.elastic.ElasticQueryHelperService; import org.opencdmp.service.plan.PlanService; @@ -131,7 +132,8 @@ public class PlanController { this.censorFactory.censor(PublicPlanCensor.class).censor(fieldSet); - PlanQuery query = this.queryFactory.query(PlanQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).isActive(IsActive.Active).statuses(PlanStatus.Finalized).accessTypes(PlanAccessType.Public); + PlanStatusQuery statusQuery = this.queryFactory.query(PlanStatusQuery.class).disableTracking().internalStatuses(PlanStatus.Finalized).isActives(IsActive.Active); + PlanQuery query = this.queryFactory.query(PlanQuery.class).disableTracking().authorize(EnumSet.of(Public)).ids(id).isActive(IsActive.Active).planStatusSubQuery(statusQuery).accessTypes(PlanAccessType.Public); PublicPlan model = this.builderFactory.builder(PublicPlanBuilder.class).authorize(EnumSet.of(Public)).build(fieldSet, query.firstAs(fieldSet)); if (model == null) @@ -245,49 +247,29 @@ public class PlanController { this.auditService.track(AuditableAction.Plan_Delete, "id", id); } - @PostMapping("finalize/{id}") - @OperationWithTenantHeader(summary = "Finalize a plan by id", description = "", + @PostMapping("set-status/{id}/{newStatusId}") + @OperationWithTenantHeader(summary = "set status for a plan", description = "", responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 @Transactional - public boolean finalize( - @Parameter(name = "id", description = "The id of a plan to finalize", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id, + public boolean SetStatus( + @Parameter(name = "id", description = "The id of a plan", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id, + @Parameter(name = "newStatusId", description = "The new status of a plan", example = "f1a3da63-0bff-438f-8b46-1a81ca176115", required = true) @PathVariable("newStatusId") UUID newStatusId, @RequestBody DescriptionsToBeFinalized descriptions ) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, IOException { - logger.debug(new MapLogEntry("finalizing" + Plan.class.getSimpleName()).And("id", id).And("descriptionIds", descriptions.getDescriptionIds())); + logger.debug(new MapLogEntry("set status" + Plan.class.getSimpleName()).And("id", id).And("newStatusId", newStatusId).And("descriptionIds", descriptions.getDescriptionIds())); - this.planService.finalize(id, descriptions.getDescriptionIds()); + this.planService.setStatus(id, newStatusId, descriptions.getDescriptionIds()); - this.auditService.track(AuditableAction.Plan_Finalize, Map.ofEntries( + this.auditService.track(AuditableAction.Plan_SetStatus, Map.ofEntries( new AbstractMap.SimpleEntry("id", id), + new AbstractMap.SimpleEntry("newStatusId", newStatusId), new AbstractMap.SimpleEntry("descriptionIds", descriptions.getDescriptionIds()) )); return true; } - @GetMapping("undo-finalize/{id}") - @OperationWithTenantHeader(summary = "Undo the finalization of a plan by id (only possible if it is not already deposited)", description = "", - responses = @ApiResponse(description = "OK", responseCode = "200")) - @Swagger404 - @Transactional - public boolean undoFinalize( - @Parameter(name = "id", description = "The id of a plan to revert the finalization", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id, - @Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet - ) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, IOException, JAXBException { - logger.debug(new MapLogEntry("undo-finalizing" + Plan.class.getSimpleName()).And("id", id)); - - this.censorFactory.censor(PlanCensor.class).censor(fieldSet, null); - - this.planService.undoFinalize(id, fieldSet); - - this.auditService.track(AuditableAction.Plan_Undo_Finalize, Map.ofEntries( - new AbstractMap.SimpleEntry("id", id) - )); - - return true; - } - @GetMapping("validate/{id}") @OperationWithTenantHeader(summary = "Validate if a plan is ready for finalization by id") @Hidden diff --git a/backend/web/src/main/java/org/opencdmp/models/AccountBuilder.java b/backend/web/src/main/java/org/opencdmp/models/AccountBuilder.java index 6a58e7e5b..bfc1b2562 100644 --- a/backend/web/src/main/java/org/opencdmp/models/AccountBuilder.java +++ b/backend/web/src/main/java/org/opencdmp/models/AccountBuilder.java @@ -1,7 +1,7 @@ package org.opencdmp.models; -import gr.cite.commons.web.authz.configuration.AuthorizationConfiguration; import gr.cite.commons.web.authz.configuration.Permission; +import gr.cite.commons.web.authz.configuration.PermissionPolicyContext; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.MyPrincipal; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; @@ -33,7 +33,7 @@ public class AccountBuilder { private final ClaimExtractor claimExtractor; private final Set excludeMoreClaim; private final CurrentPrincipalResolver currentPrincipalResolver; - private final AuthorizationConfiguration authorizationConfiguration; + private final PermissionPolicyContext permissionPolicyContext; private final AuthorizationContentResolver authorizationContentResolver; private final JsonHandlingService jsonHandlingService; private final UserScope userScope; @@ -42,10 +42,10 @@ public class AccountBuilder { private final QueryFactory queryFactory; private final BuilderFactory builderFactory; - public AccountBuilder(ClaimExtractor claimExtractor, CurrentPrincipalResolver currentPrincipalResolver, AuthorizationConfiguration authorizationConfiguration, AuthorizationContentResolver authorizationContentResolver, JsonHandlingService jsonHandlingService, UserScope userScope, TenantEntityManager entityManager, TenantScope tenantScope, QueryFactory queryFactory, BuilderFactory builderFactory) { + public AccountBuilder(ClaimExtractor claimExtractor, CurrentPrincipalResolver currentPrincipalResolver, PermissionPolicyContext permissionPolicyContext, AuthorizationContentResolver authorizationContentResolver, JsonHandlingService jsonHandlingService, UserScope userScope, TenantEntityManager entityManager, TenantScope tenantScope, QueryFactory queryFactory, BuilderFactory builderFactory) { this.claimExtractor = claimExtractor; this.currentPrincipalResolver = currentPrincipalResolver; - this.authorizationConfiguration = authorizationConfiguration; + this.permissionPolicyContext = permissionPolicyContext; this.authorizationContentResolver = authorizationContentResolver; this.jsonHandlingService = jsonHandlingService; this.userScope = userScope; @@ -107,8 +107,8 @@ public class AccountBuilder { } if (fields.hasField(Account._permissions)) { List roles = this.claimExtractor.roles(this.currentPrincipalResolver.currentPrincipal()); - Set permissions = this.authorizationConfiguration.permissionsOfRoles(roles); - for (Map.Entry permissionEntry : this.authorizationConfiguration.getRawPolicies().entrySet()){ + Set permissions = this.permissionPolicyContext.permissionsOfRoles(roles); + for (Map.Entry permissionEntry : this.permissionPolicyContext.getRawPolicies().entrySet()){ if (permissionEntry.getValue().getAllowAuthenticated()){ permissions.add(permissionEntry.getKey()); } diff --git a/backend/web/src/main/resources/config/cache.yml b/backend/web/src/main/resources/config/cache.yml index b1ca08f0b..1662b2e82 100644 --- a/backend/web/src/main/resources/config/cache.yml +++ b/backend/web/src/main/resources/config/cache.yml @@ -86,6 +86,12 @@ cache: maximumSize: 500 enableRecordStats: false expireAfterWriteSeconds: 20 + - names: [ "customPolicyByTenant" ] + allowNullValues: true + initialCapacity: 100 + maximumSize: 500 + enableRecordStats: false + expireAfterWriteSeconds: 600 mapCaches: userBySubjectId: name: userBySubjectId @@ -128,4 +134,7 @@ cache: keyPattern: resolve_$keyhash$:v0 affiliation: name: affiliation - keyPattern: affiliation_$entity$_$user$_$tenant$_$type$:v0 \ No newline at end of file + keyPattern: affiliation_$entity$_$user$_$tenant$_$type$:v0 + customPolicyByTenant: + name: customPolicyByTenant + keyPattern: custom_policy_by_tenant$tenant_code$:v0 \ No newline at end of file diff --git a/backend/web/src/main/resources/config/notification-devel.yml b/backend/web/src/main/resources/config/notification-devel.yml index de6b3e48e..217cc1a1c 100644 --- a/backend/web/src/main/resources/config/notification-devel.yml +++ b/backend/web/src/main/resources/config/notification-devel.yml @@ -3,11 +3,13 @@ notification: planInvitationExistingUserType: 4904dea2-5079-46d3-83be-3a19c9ab45dc planModifiedType: 4542262A-22F8-4BAA-9DB6-1C8E70AC1DBB planFinalisedType: 90DB0B46-42DE-BD89-AEBF-6F27EFEB256E + planStatusChangedType: 25bbc307-4fa8-4381-b2d7-a3395f57d575 planAnnotationCreatedType: 1cca80f5-2ea9-41ae-a204-9b4332216c24 planAnnotationStatusChangedType: 0c8a5c62-e48f-4eca-99ee-a7f262477061 descriptionCreatedType: 8965b1d5-99a6-4acf-9016-c0d0ce341364 descriptionModifiedType: 4FDBFA80-7A71-4A69-B854-67CBB70648F1 descriptionFinalisedType: 33790bad-94d4-488a-8ee2-7f6295ca18ea + descriptionStatusChangedType: 8389aca8-43bd-4aac-958d-24daf6ed47b4 descriptionAnnotationCreatedType: db1e99d2-a240-4e75-9bb2-ef25b234c1f0 descriptionAnnotationStatusChangedType: 3189e3a6-91e6-40c6-8ff8-275a68445aec mergeAccountConfirmationType: BFE68845-CB05-4C5A-A03D-29161A7C9660 diff --git a/backend/web/src/main/resources/config/permissions.yml b/backend/web/src/main/resources/config/permissions.yml index 51bda04eb..a53416882 100644 --- a/backend/web/src/main/resources/config/permissions.yml +++ b/backend/web/src/main/resources/config/permissions.yml @@ -120,8 +120,11 @@ permissions: BrowseDescriptionStatus: roles: - Admin - - TenantAdmin + - InstallationAdmin + - TenantPlanManager - TenantConfigManager + - TenantAdmin + - TenantUser clients: [ ] allowAnonymous: false allowAuthenticated: false @@ -174,8 +177,11 @@ permissions: BrowsePlanStatus: roles: - Admin - - TenantAdmin + - InstallationAdmin + - TenantPlanManager - TenantConfigManager + - TenantAdmin + - TenantUser clients: [ ] allowAnonymous: false allowAuthenticated: false diff --git a/dmp-db-scema/updates/00.01.072_Add_PlanStatus_table.sql b/dmp-db-scema/updates/00.01.072_Add_PlanStatus_table.sql index 9d58b53d8..927640eb9 100644 --- a/dmp-db-scema/updates/00.01.072_Add_PlanStatus_table.sql +++ b/dmp-db-scema/updates/00.01.072_Add_PlanStatus_table.sql @@ -15,6 +15,7 @@ BEGIN internal_status smallint, tenant uuid, definition xml, + action character varying(250) COLLATE pg_catalog."default", CONSTRAINT "PlanStatus_pkey" PRIMARY KEY (id), CONSTRAINT "PlanStatus_tenant_fkey" FOREIGN KEY (tenant) REFERENCES public."Tenant" (id) MATCH SIMPLE diff --git a/dmp-db-scema/updates/00.01.073_Add_DescriptionStatus_table.sql b/dmp-db-scema/updates/00.01.073_Add_DescriptionStatus_table.sql index cd329d07e..d5568fe99 100644 --- a/dmp-db-scema/updates/00.01.073_Add_DescriptionStatus_table.sql +++ b/dmp-db-scema/updates/00.01.073_Add_DescriptionStatus_table.sql @@ -15,6 +15,7 @@ BEGIN internal_status smallint, definition xml, tenant uuid, + action character varying(250) COLLATE pg_catalog."default", CONSTRAINT "DescriptionStatus_pkey" PRIMARY KEY (id), CONSTRAINT "DescriptionStatus_tenant_fkey" FOREIGN KEY (tenant) REFERENCES public."Tenant" (id) MATCH SIMPLE diff --git a/dmp-db-scema/updates/00.01.076_Insert_values_Workflows_Tenant_config.sql b/dmp-db-scema/updates/00.01.076_Insert_values_Workflows_Tenant_config.sql new file mode 100644 index 000000000..f0759c5eb --- /dev/null +++ b/dmp-db-scema/updates/00.01.076_Insert_values_Workflows_Tenant_config.sql @@ -0,0 +1,23 @@ +DO $$DECLARE + this_version CONSTANT varchar := '00.01.076'; +BEGIN + PERFORM * FROM "DBVersion" WHERE version = this_version; + IF FOUND THEN RETURN; END IF; + + INSERT INTO public."PlanStatus" (id, name, description, created_at, updated_at, is_active, internal_status, tenant, definition, action) VALUES ('cb3ced76-9807-4829-82da-75777de1bc78', 'Draft', NULL, '2024-09-17 07:37:24.288404', '2024-09-20 10:34:51.23021', 1, 0, NULL, 'OwnerViewerDescriptionContributorReviewerfalsefalseExport', NULL); + INSERT INTO public."PlanStatus" (id, name, description, created_at, updated_at, is_active, internal_status, tenant, definition, action) VALUES ('f1a3da63-0bff-438f-8b46-1a81ca176115', 'Finalized', NULL, '2024-09-16 14:16:56.685177', '2024-09-23 12:58:56.093437', 1, 1, NULL, 'OwnerfalsefalseDepositExport', 'Finalize'); + INSERT INTO public."PlanStatus" (id, name, description, created_at, updated_at, is_active, internal_status, tenant, definition, action) VALUES ('61fd91f5-c63a-45bc-aa7a-e1f00fbd8545', 'Validated', NULL, '2024-09-17 07:36:38.386887', '2024-09-23 12:28:31.57448', 1, NULL, NULL, 'ReviewerfalsefalseExport', 'Validate'); + INSERT INTO public."PlanStatus" (id, name, description, created_at, updated_at, is_active, internal_status, tenant, definition, action) VALUES ('313cce74-f44b-4a72-9cd8-a9c75fe03a7e', 'Under Review', NULL, '2024-09-17 07:36:58.677058', '2024-09-23 12:28:48.897145', 1, NULL, NULL, 'OwnerfalsefalseExport', 'Review'); + + INSERT INTO public."DescriptionStatus" (id, name, description, created_at, updated_at, is_active, internal_status, definition, tenant, action) VALUES ('978e6ff6-b5e9-4cee-86cb-bc7401ec4059', 'Draft', NULL, '2024-09-16 12:46:20.459486', '2024-09-20 12:21:00.641011', 1, 0, 'OwnerViewerDescriptionContributorReviewerfalsefalseExport', NULL, NULL); + INSERT INTO public."DescriptionStatus" (id, name, description, created_at, updated_at, is_active, internal_status, definition, tenant, action) VALUES ('60f5e529-7ed3-4be1-8754-ac8c7443f246', 'Canceled', NULL, '2024-09-17 15:37:42.256424', '2024-09-25 07:47:24.839357', 1, 2, 'OwnerfalsefalseExport', NULL, 'Cancel'); + INSERT INTO public."DescriptionStatus" (id, name, description, created_at, updated_at, is_active, internal_status, definition, tenant, action) VALUES ('c266e2ee-9ae9-4a2f-9b4b-bc6fb1dd54aa', 'Finalized', NULL, '2024-09-17 07:38:15.846399', '2024-09-23 12:55:39.702483', 1, 1, 'OwnerfalsefalseExport', NULL, 'Finalize'); + + INSERT INTO public."PlanWorkflow" (id, name, description, created_at, updated_at, is_active, definition, tenant) VALUES ('44df0e24-7879-48cc-bbe0-cd8a2b618855', 'default', NULL, '2024-09-18 11:39:30.5974', '2024-09-18 11:39:30.611945', 1, 'cb3ced76-9807-4829-82da-75777de1bc78cb3ced76-9807-4829-82da-75777de1bc78313cce74-f44b-4a72-9cd8-a9c75fe03a7e313cce74-f44b-4a72-9cd8-a9c75fe03a7e61fd91f5-c63a-45bc-aa7a-e1f00fbd854561fd91f5-c63a-45bc-aa7a-e1f00fbd8545f1a3da63-0bff-438f-8b46-1a81ca176115f1a3da63-0bff-438f-8b46-1a81ca176115cfa5cff0-0cbf-4ae7-87b7-263343991f35cb3ced76-9807-4829-82da-75777de1bc78f1a3da63-0bff-438f-8b46-1a81ca176115f1a3da63-0bff-438f-8b46-1a81ca176115cb3ced76-9807-4829-82da-75777de1bc78', NULL); + INSERT INTO public."DescriptionWorkflow" (id, name, description, created_at, updated_at, is_active, definition, tenant) VALUES ('8651af83-8b24-4776-ae45-329031db9f5e', 'default', NULL, '2024-09-17 07:39:00.221933', '2024-09-18 10:51:14.478419', 1, '978e6ff6-b5e9-4cee-86cb-bc7401ec4059978e6ff6-b5e9-4cee-86cb-bc7401ec4059c266e2ee-9ae9-4a2f-9b4b-bc6fb1dd54aac266e2ee-9ae9-4a2f-9b4b-bc6fb1dd54aa978e6ff6-b5e9-4cee-86cb-bc7401ec4059', NULL); + + + INSERT INTO public."DBVersion" VALUES ('DMPDB', '00.01.076', '2024-09-18 12:00:00.000000+02', now(), 'Insert values for Plan, Description Status and Workflow.'); + + +END$$; \ No newline at end of file diff --git a/dmp-db-scema/updates/00.01.077_Migrate_Plan_Description_status_column.sql b/dmp-db-scema/updates/00.01.077_Migrate_Plan_Description_status_column.sql new file mode 100644 index 000000000..c9a72c9c0 --- /dev/null +++ b/dmp-db-scema/updates/00.01.077_Migrate_Plan_Description_status_column.sql @@ -0,0 +1,58 @@ +DO $$DECLARE + this_version CONSTANT varchar := '00.01.077'; +BEGIN + PERFORM * FROM "DBVersion" WHERE version = this_version; + IF FOUND THEN RETURN; END IF; + + ALTER TABLE public."Plan" + ADD COLUMN status_id uuid; + ALTER TABLE public."Plan" + ADD CONSTRAINT "Plan_status_fkey" FOREIGN KEY (status_id) + REFERENCES public."PlanStatus" (id) + ON UPDATE NO ACTION + ON DELETE NO ACTION + NOT VALID; + + UPDATE public."Plan" SET + status_id = 'cb3ced76-9807-4829-82da-75777de1bc78' + WHERE status = 0; + + UPDATE public."Plan" SET + status_id = 'f1a3da63-0bff-438f-8b46-1a81ca176115' + WHERE status = 1; + + ALTER TABLE IF EXISTS public."Plan" DROP COLUMN IF EXISTS status; + ALTER TABLE public."Plan" ALTER COLUMN status_id SET NOT NULL; + ALTER TABLE IF EXISTS public."Plan" RENAME status_id TO status; + + + + ALTER TABLE public."Description" + ADD COLUMN status_id uuid; + ALTER TABLE public."Description" + ADD CONSTRAINT "Description_status_fkey" FOREIGN KEY (status_id) + REFERENCES public."DescriptionStatus" (id) + ON UPDATE NO ACTION + ON DELETE NO ACTION + NOT VALID; + + UPDATE public."Description" SET + status_id = '978e6ff6-b5e9-4cee-86cb-bc7401ec4059' + WHERE status = 0; + + UPDATE public."Description" SET + status_id = 'c266e2ee-9ae9-4a2f-9b4b-bc6fb1dd54aa' + WHERE status = 1; + + UPDATE public."Description" SET + status_id = '60f5e529-7ed3-4be1-8754-ac8c7443f246' + WHERE status = 2; + + ALTER TABLE IF EXISTS public."Description" DROP COLUMN IF EXISTS status; + ALTER TABLE public."Description" ALTER COLUMN status_id SET NOT NULL; + ALTER TABLE IF EXISTS public."Description" RENAME status_id TO status; + + + INSERT INTO public."DBVersion" VALUES ('DMPDB', '00.01.077', '2024-07-29 12:00:00.000000+02', now(), 'Migrate status collumns.'); + +END$$; \ No newline at end of file diff --git a/frontend/src/app/core/common/enum/description-status-available-action-type.ts b/frontend/src/app/core/common/enum/description-status-available-action-type.ts new file mode 100644 index 000000000..f8d2451c7 --- /dev/null +++ b/frontend/src/app/core/common/enum/description-status-available-action-type.ts @@ -0,0 +1,3 @@ +export enum DescriptionStatusAvailableActionType { + Export = 0, +} \ No newline at end of file diff --git a/frontend/src/app/core/common/enum/description-status-permission.enum.ts b/frontend/src/app/core/common/enum/description-status-permission.enum.ts new file mode 100644 index 000000000..1781feea4 --- /dev/null +++ b/frontend/src/app/core/common/enum/description-status-permission.enum.ts @@ -0,0 +1,5 @@ +export enum DescriptionStatusPermission { + Edit = "Edit" + +} + diff --git a/frontend/src/app/core/common/enum/plan-status-available-action-type.ts b/frontend/src/app/core/common/enum/plan-status-available-action-type.ts new file mode 100644 index 000000000..ef40ff21b --- /dev/null +++ b/frontend/src/app/core/common/enum/plan-status-available-action-type.ts @@ -0,0 +1,4 @@ +export enum PlanStatusAvailableActionType { + Deposit = 0, + Export = 1 +} \ No newline at end of file diff --git a/frontend/src/app/core/common/enum/plan-status-permission.enum.ts b/frontend/src/app/core/common/enum/plan-status-permission.enum.ts new file mode 100644 index 000000000..eeab14d72 --- /dev/null +++ b/frontend/src/app/core/common/enum/plan-status-permission.enum.ts @@ -0,0 +1,5 @@ +export enum PlanStatusPermission { + Edit = "Edit" + +} + diff --git a/frontend/src/app/core/model/description-status/description-status-persist.ts b/frontend/src/app/core/model/description-status/description-status-persist.ts index 10600041a..c04a65154 100644 --- a/frontend/src/app/core/model/description-status/description-status-persist.ts +++ b/frontend/src/app/core/model/description-status/description-status-persist.ts @@ -5,6 +5,7 @@ import { DescriptionStatusDefinition } from "./description-status"; export interface DescriptionStatusPersist extends BaseEntityPersist { name: string; description: string; + action: string; internalStatus: DescriptionStatusEnum; definition: DescriptionStatusDefinition; } \ No newline at end of file diff --git a/frontend/src/app/core/model/description-status/description-status.ts b/frontend/src/app/core/model/description-status/description-status.ts index 856b38958..2c93bddb9 100644 --- a/frontend/src/app/core/model/description-status/description-status.ts +++ b/frontend/src/app/core/model/description-status/description-status.ts @@ -1,17 +1,20 @@ import { AppRole } from "@app/core/common/enum/app-role"; import { DescriptionStatusEnum } from "@app/core/common/enum/description-status"; +import { DescriptionStatusAvailableActionType } from "@app/core/common/enum/description-status-available-action-type"; import { PlanUserRole } from "@app/core/common/enum/plan-user-role"; import { BaseEntity } from "@common/base/base-entity.model"; export interface DescriptionStatus extends BaseEntity{ name: string; description: string; + action: string; internalStatus: DescriptionStatusEnum; definition: DescriptionStatusDefinition; } export interface DescriptionStatusDefinition { authorization: DescriptionStatusDefinitionAuthorization + availableActions: DescriptionStatusAvailableActionType[]; } export interface DescriptionStatusDefinitionAuthorization { diff --git a/frontend/src/app/core/model/description/description.ts b/frontend/src/app/core/model/description/description.ts index dacfd5f27..62b852b62 100644 --- a/frontend/src/app/core/model/description/description.ts +++ b/frontend/src/app/core/model/description/description.ts @@ -7,9 +7,12 @@ 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, DescriptionStatusDefinition } from "../description-status/description-status"; +import { DescriptionStatusPermission } from "@app/core/common/enum/description-status-permission.enum"; export interface Description extends BaseDescription { label?: string; + status?: DescriptionStatus; properties?: DescriptionPropertyDefinition; description?: string; createdBy?: User; @@ -19,7 +22,9 @@ export interface Description extends BaseDescription { descriptionTemplate?: DescriptionTemplate; planDescriptionTemplate?: PlanDescriptionTemplate; plan?: Plan; + availableStatuses?: DescriptionStatus[]; authorizationFlags?: AppPermission[]; + statusAuthorizationFlags?: DescriptionStatusPermission[]; } @@ -76,7 +81,7 @@ export interface DescriptionPersist extends BaseEntityPersist { planId: Guid; planDescriptionTemplateId: Guid; descriptionTemplateId: Guid; - status: DescriptionStatusEnum; + statusId: Guid; description: string; properties: DescriptionPropertyDefinitionPersist; tags: string[]; @@ -120,7 +125,7 @@ export interface DescriptionReferencePersist { export interface DescriptionStatusPersist { id: Guid; - status: DescriptionStatusEnum; + statusId?: Guid; hash: string; } @@ -130,7 +135,7 @@ export interface DescriptionStatusPersist { export interface PublicDescription extends BaseDescription { label?: string; - status?: DescriptionStatusEnum; + status?: PublicDescriptionStatus; description?: string; finalizedAt?: Date; descriptionTemplate?: PublicDescriptionTemplate; @@ -148,6 +153,14 @@ export interface PublicDescriptionTemplate { label: string; description: string; } + +export interface PublicDescriptionStatus { + id: Guid; + name: string; + internalStatus: DescriptionStatusEnum; + definition: DescriptionStatusDefinition; +} + export interface DescriptionSectionPermissionResolver { planId: Guid; sectionIds: Guid[]; @@ -161,5 +174,4 @@ export interface UpdateDescriptionTemplatePersist { export interface BaseDescription extends BaseEntity { tenantId?: Guid; - status?: DescriptionStatusEnum; } diff --git a/frontend/src/app/core/model/plan-status/plan-status-persist.ts b/frontend/src/app/core/model/plan-status/plan-status-persist.ts index 03ca3beb9..166a445a3 100644 --- a/frontend/src/app/core/model/plan-status/plan-status-persist.ts +++ b/frontend/src/app/core/model/plan-status/plan-status-persist.ts @@ -5,6 +5,7 @@ import { PlanStatusDefinition } from "./plan-status"; export interface PlanStatusPersist extends BaseEntityPersist { name: string; description: string; + action: string; internalStatus: PlanStatusEnum; definition: PlanStatusDefinition; } \ No newline at end of file diff --git a/frontend/src/app/core/model/plan-status/plan-status.ts b/frontend/src/app/core/model/plan-status/plan-status.ts index f531d0a38..1bee49e28 100644 --- a/frontend/src/app/core/model/plan-status/plan-status.ts +++ b/frontend/src/app/core/model/plan-status/plan-status.ts @@ -1,17 +1,20 @@ import { AppRole } from "@app/core/common/enum/app-role"; import { PlanStatusEnum } from "@app/core/common/enum/plan-status"; +import { PlanStatusAvailableActionType } from "@app/core/common/enum/plan-status-available-action-type"; import { PlanUserRole } from "@app/core/common/enum/plan-user-role"; import { BaseEntity } from "@common/base/base-entity.model"; export interface PlanStatus extends BaseEntity{ name: string; description: string; + action: string; internalStatus: PlanStatusEnum; definition: PlanStatusDefinition; } export interface PlanStatusDefinition { authorization: PlanStatusDefinitionAuthorization + availableActions: PlanStatusAvailableActionType[] } export interface PlanStatusDefinitionAuthorization { diff --git a/frontend/src/app/core/model/plan/plan.ts b/frontend/src/app/core/model/plan/plan.ts index 245f3f7c0..c2a0ca34f 100644 --- a/frontend/src/app/core/model/plan/plan.ts +++ b/frontend/src/app/core/model/plan/plan.ts @@ -14,6 +14,8 @@ import { PlanReference } from './plan-reference'; import { IsActive } from '@app/core/common/enum/is-active.enum'; import { AppPermission } from '@app/core/common/enum/permission.enum'; import { EntityType } from '@app/core/common/enum/entity-type'; +import { PlanStatus } from '../plan-status/plan-status'; +import { PlanStatusPermission } from '@app/core/common/enum/plan-status-permission.enum'; export interface BasePlan extends BaseEntity { label?: string; @@ -26,7 +28,7 @@ export interface BasePlan extends BaseEntity { planReferences?: PlanReference[]; entityDois?: EntityDoi[]; tenantId?: Guid; - status?: PlanStatusEnum; + status?: PlanStatus; descriptions?: BaseDescription[]; } export interface Plan extends BasePlan { @@ -40,7 +42,9 @@ export interface Plan extends BasePlan { descriptions?: Description[]; planDescriptionTemplates?: PlanDescriptionTemplate[]; otherPlanVersions?: Plan[]; + availableStatuses?: PlanStatus[]; authorizationFlags?: AppPermission[]; + statusAuthorizationFlags?: PlanStatusPermission[]; } export interface PublicPlan extends BasePlan { @@ -96,7 +100,7 @@ export interface PlanDescriptionTemplate extends BaseEntity { // export interface PlanPersist extends BaseEntityPersist { label: string; - status: PlanStatusEnum; + statusId: Guid; properties: PlanPropertiesPersist; description: String; language: String; diff --git a/frontend/src/app/core/query/description.lookup.ts b/frontend/src/app/core/query/description.lookup.ts index 810ff4bd0..68dbde5b3 100644 --- a/frontend/src/app/core/query/description.lookup.ts +++ b/frontend/src/app/core/query/description.lookup.ts @@ -1,7 +1,6 @@ import { Lookup } from '@common/model/lookup'; import { Guid } from '@common/types/guid'; import { IsActive } from '../common/enum/is-active.enum'; -import { DescriptionStatusEnum } from '../common/enum/description-status'; import { PlanLookup } from './plan.lookup'; import { DescriptionReferenceLookup } from './reference.lookup'; import { DescriptionTagLookup } from './tag.lookup'; @@ -17,7 +16,7 @@ export class DescriptionLookup extends Lookup implements DescriptionFilter { finalizedAfter: Date; finalizedBefore: Date; isActive: IsActive[]; - statuses: DescriptionStatusEnum[]; + statusIds: Guid[]; planSubQuery: PlanLookup; tenantSubQuery: TenantLookup; @@ -39,7 +38,7 @@ export interface DescriptionFilter { finalizedAfter: Date; finalizedBefore: Date; isActive: IsActive[]; - statuses: DescriptionStatusEnum[]; + statusIds: Guid[]; planSubQuery: PlanLookup; descriptionTemplateSubQuery: DescriptionTemplateLookup; diff --git a/frontend/src/app/core/query/plan.lookup.ts b/frontend/src/app/core/query/plan.lookup.ts index 25642496e..5d22c2f8c 100644 --- a/frontend/src/app/core/query/plan.lookup.ts +++ b/frontend/src/app/core/query/plan.lookup.ts @@ -1,7 +1,6 @@ import { Lookup } from '@common/model/lookup'; import { Guid } from '@common/types/guid'; import { PlanAccessType } from '../common/enum/plan-access-type'; -import { PlanStatusEnum } from '../common/enum/plan-status'; import { PlanVersionStatus } from '../common/enum/plan-version-status'; import { IsActive } from '../common/enum/is-active.enum'; import { PlanDescriptionTemplateLookup } from './plan-description-template.lookup'; @@ -9,6 +8,7 @@ import { PlanUserLookup } from './plan-user.lookup'; import { PlanBlueprintLookup } from './plan-blueprint.lookup'; import { PlanReferenceLookup } from './reference.lookup'; import { TenantLookup } from './tenant.lookup'; +import { PlanStatusLookup } from './plan-status.lookup'; export class PlanLookup extends Lookup implements PlanFilter { ids: Guid[]; @@ -16,7 +16,7 @@ export class PlanLookup extends Lookup implements PlanFilter { like: string; isActive: IsActive[]; versionStatuses: PlanVersionStatus[]; - statuses: PlanStatusEnum[]; + statusIds: Guid[]; accessTypes: PlanAccessType[]; versions: Number[]; groupIds: Guid[]; @@ -26,6 +26,7 @@ export class PlanLookup extends Lookup implements PlanFilter { planBlueprintSubQuery: PlanBlueprintLookup; planDescriptionTemplateSubQuery: PlanDescriptionTemplateLookup; planReferenceSubQuery: PlanReferenceLookup; + planStatusSubQuery: PlanStatusLookup; constructor() { super(); @@ -38,7 +39,7 @@ export interface PlanFilter { like: string; isActive: IsActive[]; versionStatuses: PlanVersionStatus[]; - statuses: PlanStatusEnum[]; + statusIds: Guid[]; accessTypes: PlanAccessType[]; versions: Number[]; groupIds: Guid[]; @@ -47,4 +48,5 @@ export interface PlanFilter { planBlueprintSubQuery: PlanBlueprintLookup; planDescriptionTemplateSubQuery: PlanDescriptionTemplateLookup; planReferenceSubQuery: PlanReferenceLookup; + planStatusSubQuery: PlanStatusLookup; } 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..ae8c9e8fa 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 @@ -3,7 +3,7 @@ import { Injectable } from "@angular/core"; import { BaseHttpV2Service } from "../http/base-http-v2.service"; import { ConfigurationService } from "../configuration/configuration.service"; import { QueryResult } from "@common/model/query-result"; -import { catchError, Observable, throwError } from "rxjs"; +import { catchError, map, Observable, throwError } from "rxjs"; import { Guid } from "@common/types/guid"; import { DescriptionStatusLookup } from "@app/core/query/description-status.lookup"; import { DescriptionStatus } from "@app/core/model/description-status/description-status"; @@ -12,6 +12,8 @@ import { IsActive } from "@app/core/common/enum/is-active.enum"; import { nameof } from "ts-simple-nameof"; import { FilterService } from "@common/modules/text-filter/filter-service"; import { DescriptionStatusEnum } from "@app/core/common/enum/description-status"; +import { SingleAutoCompleteConfiguration } from "@app/library/auto-complete/single/single-auto-complete-configuration"; +import { MultipleAutoCompleteConfiguration } from "@app/library/auto-complete/multiple/multiple-auto-complete-configuration"; @Injectable() export class DescriptionStatusService { @@ -56,6 +58,43 @@ export class DescriptionStatusService { catchError((error: any) => throwError(() => error))); } + // tslint:disable-next-line: member-ordering + singleAutocompleteConfiguration: SingleAutoCompleteConfiguration = { + initialItems: (data?: any) => this.query(this.buildAutocompleteLookup([IsActive.Active])).pipe(map(x => x.items)), + filterFn: (searchQuery: string, data?: any) => this.query(this.buildAutocompleteLookup([IsActive.Active], searchQuery)).pipe(map(x => x.items)), + getSelectedItem: (selectedItem: any) => this.query(this.buildAutocompleteLookup([IsActive.Active, IsActive.Inactive], null, null, [selectedItem])).pipe(map(x => x.items[0])), + displayFn: (item: DescriptionStatus) => item.name, + titleFn: (item: DescriptionStatus) => item.name, + valueAssign: (item: DescriptionStatus) => item.id, + }; + + // tslint:disable-next-line: member-ordering + multipleAutocompleteConfiguration: MultipleAutoCompleteConfiguration = { + initialItems: (excludedItems: any[], data?: any) => this.query(this.buildAutocompleteLookup([IsActive.Active], null, excludedItems ? excludedItems : null)).pipe(map(x => x.items)), + filterFn: (searchQuery: string, excludedItems: any[]) => this.query(this.buildAutocompleteLookup([IsActive.Active],searchQuery, excludedItems)).pipe(map(x => x.items)), + getSelectedItems: (selectedItems: any[]) => this.query(this.buildAutocompleteLookup([IsActive.Active, IsActive.Inactive], null, null, selectedItems)).pipe(map(x => x.items)), + displayFn: (item: DescriptionStatus) => item.name, + titleFn: (item: DescriptionStatus) => item.name, + valueAssign: (item: DescriptionStatus) => item.id, + }; + + public buildAutocompleteLookup(isActive: IsActive[], like?: string, excludedIds?: Guid[], ids?: Guid[]): DescriptionStatusLookup { + const lookup: DescriptionStatusLookup = new DescriptionStatusLookup(); + lookup.page = { size: 100, offset: 0 }; + if (excludedIds && excludedIds.length > 0) { lookup.excludedIds = excludedIds; } + if (ids && ids.length > 0) { lookup.ids = ids; } + lookup.isActive = isActive; + lookup.project = { + fields: [ + nameof(x => x.id), + nameof(x => x.name) + ] + }; + lookup.order = { items: [nameof(x => x.name)] }; + if (like) { lookup.like = this.filterService.transformLike(like); } + return lookup; + } + buildLookup(params: { like?: string, excludedIds?: Guid[], diff --git a/frontend/src/app/core/services/plan/plan-status.service.ts b/frontend/src/app/core/services/plan/plan-status.service.ts index 0d72b5644..9b53abed3 100644 --- a/frontend/src/app/core/services/plan/plan-status.service.ts +++ b/frontend/src/app/core/services/plan/plan-status.service.ts @@ -5,14 +5,15 @@ import { ConfigurationService } from "../configuration/configuration.service"; import { PlanStatusLookup } from "@app/core/query/plan-status.lookup"; import { PlanStatus } from "@app/core/model/plan-status/plan-status"; import { QueryResult } from "@common/model/query-result"; -import { catchError, Observable, throwError } from "rxjs"; +import { catchError, map, Observable, throwError } from "rxjs"; import { PlanStatusPersist } from "@app/core/model/plan-status/plan-status-persist"; import { Guid } from "@common/types/guid"; -import { PlanLookup } from "@app/core/query/plan.lookup"; import { FilterService } from "@common/modules/text-filter/filter-service"; import { IsActive } from "@notification-service/core/enum/is-active.enum"; import { nameof } from "ts-simple-nameof"; import { PlanStatusEnum } from "@app/core/common/enum/plan-status"; +import { SingleAutoCompleteConfiguration } from "@app/library/auto-complete/single/single-auto-complete-configuration"; +import { MultipleAutoCompleteConfiguration } from "@app/library/auto-complete/multiple/multiple-auto-complete-configuration"; @Injectable() export class PlanStatusService { @@ -57,6 +58,43 @@ export class PlanStatusService { catchError((error: any) => throwError(() => error))); } + // tslint:disable-next-line: member-ordering + singleAutocompleteConfiguration: SingleAutoCompleteConfiguration = { + initialItems: (data?: any) => this.query(this.buildAutocompleteLookup([IsActive.Active])).pipe(map(x => x.items)), + filterFn: (searchQuery: string, data?: any) => this.query(this.buildAutocompleteLookup([IsActive.Active], searchQuery)).pipe(map(x => x.items)), + getSelectedItem: (selectedItem: any) => this.query(this.buildAutocompleteLookup([IsActive.Active, IsActive.Inactive], null, null, [selectedItem])).pipe(map(x => x.items[0])), + displayFn: (item: PlanStatus) => item.name, + titleFn: (item: PlanStatus) => item.name, + valueAssign: (item: PlanStatus) => item.id, + }; + + // tslint:disable-next-line: member-ordering + multipleAutocompleteConfiguration: MultipleAutoCompleteConfiguration = { + initialItems: (excludedItems: any[], data?: any) => this.query(this.buildAutocompleteLookup([IsActive.Active], null, excludedItems ? excludedItems : null)).pipe(map(x => x.items)), + filterFn: (searchQuery: string, excludedItems: any[]) => this.query(this.buildAutocompleteLookup([IsActive.Active],searchQuery, excludedItems)).pipe(map(x => x.items)), + getSelectedItems: (selectedItems: any[]) => this.query(this.buildAutocompleteLookup([IsActive.Active, IsActive.Inactive], null, null, selectedItems)).pipe(map(x => x.items)), + displayFn: (item: PlanStatus) => item.name, + titleFn: (item: PlanStatus) => item.name, + valueAssign: (item: PlanStatus) => item.id, + }; + + public buildAutocompleteLookup(isActive: IsActive[], like?: string, excludedIds?: Guid[], ids?: Guid[]): PlanStatusLookup { + const lookup: PlanStatusLookup = new PlanStatusLookup(); + lookup.page = { size: 100, offset: 0 }; + if (excludedIds && excludedIds.length > 0) { lookup.excludedIds = excludedIds; } + if (ids && ids.length > 0) { lookup.ids = ids; } + lookup.isActive = isActive; + lookup.project = { + fields: [ + nameof(x => x.id), + nameof(x => x.name) + ] + }; + lookup.order = { items: [nameof(x => x.name)] }; + if (like) { lookup.like = this.filterService.transformLike(like); } + return lookup; + } + buildLookup(params: { like?: string, excludedIds?: Guid[], diff --git a/frontend/src/app/core/services/plan/plan.service.ts b/frontend/src/app/core/services/plan/plan.service.ts index 825218f63..7f8169103 100644 --- a/frontend/src/app/core/services/plan/plan.service.ts +++ b/frontend/src/app/core/services/plan/plan.service.ts @@ -94,24 +94,14 @@ export class PlanService { catchError((error: any) => throwError(error))); } - finalize(id: Guid, descriptionIds: Guid[] = []): Observable { - const url = `${this.apiBase}/finalize/${id}`; + setStatus(id: Guid, newStatusId: Guid, descriptionIds: Guid[] = []): Observable { + const url = `${this.apiBase}/set-status/${id}/${newStatusId}`; return this.http .post(url, {descriptionIds: descriptionIds}).pipe( catchError((error: any) => throwError(error))); } - undoFinalize(id: Guid, reqFields: string[] = []): Observable { - const url = `${this.apiBase}/undo-finalize/${id}`; - - const options = { params: { f: reqFields } }; - - return this.http - .get(url, options).pipe( - catchError((error: any) => throwError(error))); - } - validate(id: Guid): Observable { const url = `${this.apiBase}/validate/${id}`; @@ -261,13 +251,13 @@ export class PlanService { valueAssign: (item: Plan) => item.id, }; - public buildAutocompleteLookup(isActive: IsActive[], like?: string, excludedIds?: Guid[], ids?: Guid[], statuses?: PlanStatusEnum[], planDescriptionTemplateSubQuery?: PlanDescriptionTemplateLookup): PlanLookup { + public buildAutocompleteLookup(isActive: IsActive[], like?: string, excludedIds?: Guid[], ids?: Guid[], statusIds?: Guid[], planDescriptionTemplateSubQuery?: PlanDescriptionTemplateLookup): PlanLookup { const lookup: PlanLookup = new PlanLookup(); lookup.page = { size: 100, offset: 0 }; if (excludedIds && excludedIds.length > 0) { lookup.excludedIds = excludedIds; } if (ids && ids.length > 0) { lookup.ids = ids; } lookup.isActive = isActive; - lookup.statuses = statuses; + lookup.statusIds = statusIds; lookup.project = { fields: [ nameof(x => x.id), @@ -286,11 +276,15 @@ export class PlanService { // // - getCurrentUserRolesInPlan(planUsers: PlanUser[]): PlanUserRole[] { + getCurrentUserRolesInPlan(planUsers: PlanUser[], isDeletedPlan: boolean = false): PlanUserRole[] { const principalId: Guid = this.authService.userId(); let planUserRoles: PlanUserRole[] = null; if (principalId) { - planUserRoles = planUsers.filter(element => element.isActive == IsActive.Active && element?.user?.id === principalId).map(x => x.role); + if (isDeletedPlan) { + planUserRoles = planUsers.filter(element => element?.user?.id === principalId).map(x => x.role); + } else { + planUserRoles = planUsers.filter(element => element.isActive == IsActive.Active && element?.user?.id === principalId).map(x => x.role); + } } return planUserRoles; } diff --git a/frontend/src/app/core/services/utilities/enum-utils.service.ts b/frontend/src/app/core/services/utilities/enum-utils.service.ts index 37d986f0c..95cc0c653 100644 --- a/frontend/src/app/core/services/utilities/enum-utils.service.ts +++ b/frontend/src/app/core/services/utilities/enum-utils.service.ts @@ -31,6 +31,8 @@ import { PlanStatusEnum } from '../../common/enum/plan-status'; import { ValidationType } from '../../common/enum/validation-type'; import { UsageLimitTargetMetric } from '@app/core/common/enum/usage-limit-target-metric'; import { UsageLimitPeriodicityRange } from '@app/core/common/enum/usage-limit-periodicity-range'; +import { PlanStatusAvailableActionType } from '@app/core/common/enum/plan-status-available-action-type'; +import { DescriptionStatusAvailableActionType } from '@app/core/common/enum/description-status-available-action-type'; @Injectable() export class EnumUtils { @@ -76,6 +78,13 @@ export class EnumUtils { } } + toPlanStatusAvailableActionTypeString(status: PlanStatusAvailableActionType): string { + switch (status) { + case PlanStatusAvailableActionType.Deposit: return this.language.instant('TYPES.PLAN-STATUS-AVAILABLE-ACTION-TYPE.DEPOSIT'); + case PlanStatusAvailableActionType.Export: return this.language.instant('TYPES.PLAN-STATUS-AVAILABLE-ACTION-TYPE.EXPORT'); + } + } + toDescriptionTemplateFieldValidationTypeString(status: ValidationType): string { switch (status) { case ValidationType.None: return this.language.instant('TYPES.DESCRIPTION-TEMPLATE-FIELD-VALIDATION-TYPE.NONE'); @@ -218,6 +227,12 @@ export class EnumUtils { } } + toDescriptionStatusAvailableActionTypeString(status: DescriptionStatusAvailableActionType): string { + switch (status) { + case DescriptionStatusAvailableActionType.Export: return this.language.instant('TYPES.DESCRIPTION-STATUS-AVAILABLE-ACTION-TYPE.EXPORT'); + } + } + toPlanUserRolesString(roles: PlanUserRole[]): string { return roles.map(x => this.toPlanUserRoleString(x)).join(', ') } toPlanUserRoleString(role: PlanUserRole): string { switch (role) { diff --git a/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.html b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.html index c7a43d239..d4fa4c4cd 100644 --- a/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.html +++ b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.html @@ -44,6 +44,14 @@ {{formGroup.controls.internalStatus.getError('backendError').message}} +
+ + {{'DESCRIPTION-STATUS-EDITOR.FIELDS.ACTION' | translate}} + + {{formGroup.controls.action.getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +

{{'DESCRIPTION-STATUS-EDITOR.FIELDS.DESCRIPTION' | translate}}

@@ -61,6 +69,18 @@
+
+

+ {{'DESCRIPTION-STATUS-EDITOR.FIELDS.AVAILABLE-ACTIONS' | translate}} +

+ + {{'DESCRIPTION-STATUS-EDITOR.FIELDS.AVAILABLE-ACTIONS' | translate}} + + {{enumUtils.toDescriptionStatusAvailableActionTypeString(actionType)}} + + +
+

{{'DESCRIPTION-STATUS-EDITOR.FIELDS.AUTHORIZATION' | translate}} @@ -73,7 +93,7 @@
- {{'DESCRIPTION-STATUS-EDITOR.FIELDS.ROLES' | translate}}* + {{'DESCRIPTION-STATUS-EDITOR.FIELDS.ROLES' | translate}} {{enumUtils.toAppRoleString(userRole)}} @@ -83,7 +103,7 @@
- {{'DESCRIPTION-STATUS-EDITOR.FIELDS.PLAN-ROLES' | translate}}* + {{'DESCRIPTION-STATUS-EDITOR.FIELDS.PLAN-ROLES' | translate}} {{enumUtils.toPlanUserRoleString(planRole)}} diff --git a/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.ts b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.ts index 1c045470d..98176a8b2 100644 --- a/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.ts +++ b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.ts @@ -29,6 +29,7 @@ import { DescriptionStatusForm, DescriptionStatusDefinitionAuthorizationItemForm import { DescriptionStatusEditorResolver } from './description-status-editor.resolver'; import { BaseEditor } from '@common/base/base-editor'; import { PlanUserRole } from '@app/core/common/enum/plan-user-role'; +import { DescriptionStatusAvailableActionType } from '@app/core/common/enum/description-status-available-action-type'; @Component({ selector: 'app-description-status-editor', @@ -39,6 +40,7 @@ export class DescriptionStatusEditorComponent extends BaseEditor(DescriptionStatusEnum); protected userRolesEnum = this.enumUtils.getEnumValues(AppRole); protected planRolesEnum = this.enumUtils.getEnumValues(PlanUserRole); + protected descriptionStatusAvailableActionTypeEnumValues = this.enumUtils.getEnumValues(DescriptionStatusAvailableActionType); protected belongsToCurrentTenant: boolean; constructor( diff --git a/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.model.ts b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.model.ts index 13b9db7f5..20e476240 100644 --- a/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.model.ts +++ b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.model.ts @@ -1,6 +1,7 @@ import { FormControl, FormGroup, Validators } from "@angular/forms"; import { AppRole } from "@app/core/common/enum/app-role"; import { DescriptionStatusEnum } from "@app/core/common/enum/description-status"; +import { DescriptionStatusAvailableActionType } from "@app/core/common/enum/description-status-available-action-type"; import { PlanUserRole } from "@app/core/common/enum/plan-user-role"; import { DescriptionStatus, DescriptionStatusDefinition, DescriptionStatusDefinitionAuthorizationItem } from "@app/core/model/description-status/description-status"; import { DescriptionStatusPersist } from "@app/core/model/description-status/description-status-persist"; @@ -12,6 +13,7 @@ import { Guid } from "@common/types/guid"; export class DescriptionStatusEditorModel extends BaseEditorModel implements DescriptionStatusPersist { name: string; description: string; + action: string; internalStatus: DescriptionStatusEnum; definition: DescriptionStatusDefinition; @@ -20,6 +22,7 @@ export class DescriptionStatusEditorModel extends BaseEditorModel implements Des super.fromModel(item); this.name = item?.name; this.description = item?.description; + this.action = item?.action this.internalStatus = item?.internalStatus; this.definition = item?.definition; } @@ -33,6 +36,7 @@ export class DescriptionStatusEditorModel extends BaseEditorModel implements Des id: [{ value: this.id, disabled }, context.getValidation('id').validators], name: [{value: this.name, disabled}, context.getValidation('name').validators], description: [{value: this.description, disabled}, context.getValidation('description').validators], + action: [{value: this.action, disabled}, context.getValidation('action').validators], internalStatus: [{value: this.internalStatus, disabled}, context.getValidation('internalStatus').validators], hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators], definition: this.buildDefinitionForm({context, disabled}), @@ -46,6 +50,7 @@ export class DescriptionStatusEditorModel extends BaseEditorModel implements Des edit: this.buildDefinitionAuthorizationItemForm({item: this.definition?.authorization?.edit, rootPath: 'edit', disabled}) }) }); + definitionForm.addControl('availableActions', new FormControl({value: this.definition?.availableActions, disabled}, context.getValidation('availableActions').validators)); definitionForm.controls.authorization.addValidators(context.getValidation('authorization').validators); definitionForm.addValidators(context.getValidation('definition').validators); return definitionForm; @@ -68,11 +73,13 @@ export class DescriptionStatusEditorModel extends BaseEditorModel implements Des baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(this.validationErrorModel, 'id')] }); baseValidationArray.push({ key: 'name', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'name')] }); baseValidationArray.push({ key: 'description', validators: [BackendErrorValidator(this.validationErrorModel, 'description')] }); + baseValidationArray.push({ key: 'action', validators: [BackendErrorValidator(this.validationErrorModel, 'action')] }); baseValidationArray.push({ key: 'internalStatus', validators: [BackendErrorValidator(this.validationErrorModel, 'internalStatus')] }); baseValidationArray.push({ key: 'hash', validators: [] }); baseValidationArray.push({ key: 'definition', validators: [BackendErrorValidator(this.validationErrorModel, 'definition')] }); baseValidationArray.push({ key: 'authorization', validators: [BackendErrorValidator(this.validationErrorModel, 'definition.authorization')] }); baseValidationArray.push({ key: 'edit', validators: [BackendErrorValidator(this.validationErrorModel, 'definition.authorization.edit')] }); + baseValidationArray.push({ key: 'availableActions', validators: [BackendErrorValidator(this.validationErrorModel, 'definition.availableActions')] }); baseContext.validation = baseValidationArray; return baseContext; @@ -83,8 +90,8 @@ export class DescriptionStatusEditorModel extends BaseEditorModel implements Des const baseValidationArray: Validation[] = new Array(); baseValidationArray.push({ key: 'allowAnonymous', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}allowAnonymous`)] }); baseValidationArray.push({ key: 'allowAuthenticated', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}allowAuthenticated`)] }); - baseValidationArray.push({ key: 'planRoles', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}planRoles`)] }); - baseValidationArray.push({ key: 'roles', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}roles`)] }); + baseValidationArray.push({ key: 'planRoles', validators: [ BackendErrorValidator(this.validationErrorModel, `${rootPath}planRoles`)] }); + baseValidationArray.push({ key: 'roles', validators: [BackendErrorValidator(this.validationErrorModel, `${rootPath}roles`)] }); baseContext.validation = baseValidationArray; return baseContext; @@ -102,6 +109,7 @@ export interface DescriptionStatusForm { export interface DescriptionStatusDefinitionForm { authorization: FormGroup + availableActions?: FormControl } export interface DescriptionStatusDefinitionAuthorizationForm { diff --git a/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.resolver.ts b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.resolver.ts index d66a4be28..4e6b2db41 100644 --- a/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.resolver.ts +++ b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.resolver.ts @@ -20,6 +20,7 @@ export class DescriptionStatusEditorResolver extends BaseEditorResolver{ nameof(x => x.id), nameof(x => x.name), nameof(x => x.description), + nameof(x => x.action), nameof(x => x.internalStatus), nameof(x => x.definition), [nameof(x => x.definition), nameof(x => x.authorization)].join('.'), @@ -28,6 +29,8 @@ export class DescriptionStatusEditorResolver extends BaseEditorResolver{ [nameof(x => x.definition), nameof(x => x.authorization), nameof(x => x.edit), nameof(x => x.allowAnonymous)].join('.'), [nameof(x => x.definition), nameof(x => x.authorization), nameof(x => x.edit), nameof(x => x.allowAuthenticated)].join('.'), + [nameof(x => x.definition), nameof(x => x.availableActions)].join('.'), + nameof(x => x.updatedAt), nameof(x => x.createdAt), nameof(x => x.hash), diff --git a/frontend/src/app/ui/admin/description-status/listing/description-status-listing/description-status-listing.component.html b/frontend/src/app/ui/admin/description-status/listing/description-status-listing/description-status-listing.component.html index 09a535ed6..08c65c462 100644 --- a/frontend/src/app/ui/admin/description-status/listing/description-status-listing/description-status-listing.component.html +++ b/frontend/src/app/ui/admin/description-status/listing/description-status-listing/description-status-listing.component.html @@ -38,14 +38,7 @@
-
- {{enumUtils.toDescriptionStatusString(row.internalStatus)}} -
+ {{enumUtils.toDescriptionStatusString(row.internalStatus)}}
diff --git a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor.resolver.ts b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor.resolver.ts index fc09a146b..3397b4910 100644 --- a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor.resolver.ts +++ b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor.resolver.ts @@ -20,6 +20,7 @@ export class PlanStatusEditorResolver extends BaseEditorResolver{ nameof(x => x.id), nameof(x => x.name), nameof(x => x.description), + nameof(x => x.action), nameof(x => x.internalStatus), nameof(x => x.definition), [nameof(x => x.definition), nameof(x => x.authorization)].join('.'), @@ -28,6 +29,8 @@ export class PlanStatusEditorResolver extends BaseEditorResolver{ [nameof(x => x.definition), nameof(x => x.authorization), nameof(x => x.edit), nameof(x => x.allowAnonymous)].join('.'), [nameof(x => x.definition), nameof(x => x.authorization), nameof(x => x.edit), nameof(x => x.allowAuthenticated)].join('.'), + [nameof(x => x.definition), nameof(x => x.availableActions)].join('.'), + nameof(x => x.updatedAt), nameof(x => x.createdAt), nameof(x => x.hash), diff --git a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.html b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.html index d24c9095c..d898ca1da 100644 --- a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.html +++ b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.html @@ -44,6 +44,14 @@ {{formGroup.controls.internalStatus.getError('backendError').message}}
+
+ + {{'PLAN-STATUS-EDITOR.FIELDS.ACTION' | translate}} + + {{formGroup.controls.action.getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +

{{'PLAN-STATUS-EDITOR.FIELDS.DESCRIPTION' | translate}}

@@ -60,7 +68,17 @@ }
- +
+

+ {{'PLAN-STATUS-EDITOR.FIELDS.AVAILABLE-ACTIONS' | translate}} +

+ + {{'PLAN-STATUS-EDITOR.FIELDS.AVAILABLE-ACTIONS' | translate}} + + {{enumUtils.toPlanStatusAvailableActionTypeString(actionType)}} + + +

{{'PLAN-STATUS-EDITOR.FIELDS.AUTHORIZATION' | translate}} @@ -73,7 +91,7 @@
- {{'PLAN-STATUS-EDITOR.FIELDS.ROLES' | translate}}* + {{'PLAN-STATUS-EDITOR.FIELDS.ROLES' | translate}} {{enumUtils.toAppRoleString(userRole)}} @@ -83,7 +101,7 @@
- {{'PLAN-STATUS-EDITOR.FIELDS.PLAN-ROLES' | translate}}* + {{'PLAN-STATUS-EDITOR.FIELDS.PLAN-ROLES' | translate}} {{enumUtils.toPlanUserRoleString(planRole)}} diff --git a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.ts b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.ts index f999d7a0b..2f716aef7 100644 --- a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.ts +++ b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.ts @@ -29,6 +29,7 @@ import { PlanStatusEnum } from '@app/core/common/enum/plan-status'; import { FormGroup } from '@angular/forms'; import { PlanUserRole } from '@app/core/common/enum/plan-user-role'; import { AppRole } from '@app/core/common/enum/app-role'; +import { PlanStatusAvailableActionType } from '@app/core/common/enum/plan-status-available-action-type'; @Component({ selector: 'app-plan-status-editor', @@ -40,6 +41,7 @@ export class PlanStatusEditorComponent extends BaseEditor(PlanStatusEnum); protected userRolesEnum = this.enumUtils.getEnumValues(AppRole); protected planRolesEnum = this.enumUtils.getEnumValues(PlanUserRole); + protected planStatusAvailableActionTypeEnumValues = this.enumUtils.getEnumValues(PlanStatusAvailableActionType); protected belongsToCurrentTenant: boolean; constructor( diff --git a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.model.ts b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.model.ts index 5fd0ce7cc..1b07af72b 100644 --- a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.model.ts +++ b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.model.ts @@ -2,6 +2,7 @@ import { FormArray, FormControl, FormGroup, Validators } from "@angular/forms"; import { AppRole } from "@app/core/common/enum/app-role"; import { AppPermission } from "@app/core/common/enum/permission.enum"; import { PlanStatusEnum } from "@app/core/common/enum/plan-status"; +import { PlanStatusAvailableActionType } from "@app/core/common/enum/plan-status-available-action-type"; import { PlanUserRole } from "@app/core/common/enum/plan-user-role"; import { PlanStatus, PlanStatusDefinition, PlanStatusDefinitionAuthorizationItem } from "@app/core/model/plan-status/plan-status"; import { PlanStatusPersist } from "@app/core/model/plan-status/plan-status-persist"; @@ -13,6 +14,7 @@ import { Guid } from "@common/types/guid"; export class PlanStatusEditorModel extends BaseEditorModel implements PlanStatusPersist { name: string; description: string; + action: string; internalStatus: PlanStatusEnum; definition: PlanStatusDefinition; @@ -21,6 +23,7 @@ export class PlanStatusEditorModel extends BaseEditorModel implements PlanStatus super.fromModel(item); this.name = item?.name; this.description = item?.description; + this.action = item?.action; this.internalStatus = item?.internalStatus; this.definition = item?.definition; } @@ -34,6 +37,7 @@ export class PlanStatusEditorModel extends BaseEditorModel implements PlanStatus id: [{ value: this.id, disabled }, context.getValidation('id').validators], name: [{value: this.name, disabled}, context.getValidation('name').validators], description: [{value: this.description, disabled}, context.getValidation('description').validators], + action: [{value: this.action, disabled}, context.getValidation('action').validators], internalStatus: [{value: this.internalStatus, disabled}, context.getValidation('internalStatus').validators], hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators], definition: this.buildDefinitionForm({context, disabled}), @@ -47,6 +51,7 @@ export class PlanStatusEditorModel extends BaseEditorModel implements PlanStatus edit: this.buildDefinitionAuthorizationItemForm({item: this.definition?.authorization?.edit, rootPath: 'edit', disabled}) }) }); + definitionForm.addControl('availableActions', new FormControl({value: this.definition?.availableActions, disabled}, context.getValidation('availableActions').validators)); definitionForm.controls.authorization.addValidators(context.getValidation('authorization').validators); definitionForm.addValidators(context.getValidation('definition').validators); return definitionForm; @@ -69,11 +74,13 @@ export class PlanStatusEditorModel extends BaseEditorModel implements PlanStatus baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(this.validationErrorModel, 'id')] }); baseValidationArray.push({ key: 'name', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'name')] }); baseValidationArray.push({ key: 'description', validators: [BackendErrorValidator(this.validationErrorModel, 'description')] }); + baseValidationArray.push({ key: 'action', validators: [BackendErrorValidator(this.validationErrorModel, 'action')] }); baseValidationArray.push({ key: 'internalStatus', validators: [BackendErrorValidator(this.validationErrorModel, 'internalStatus')] }); baseValidationArray.push({ key: 'hash', validators: [] }); baseValidationArray.push({ key: 'definition', validators: [BackendErrorValidator(this.validationErrorModel, 'definition')] }); baseValidationArray.push({ key: 'authorization', validators: [BackendErrorValidator(this.validationErrorModel, 'definition.authorization')] }); baseValidationArray.push({ key: 'edit', validators: [BackendErrorValidator(this.validationErrorModel, 'definition.authorization.edit')] }); + baseValidationArray.push({ key: 'availableActions', validators: [BackendErrorValidator(this.validationErrorModel, 'definition.availableActions')] }); baseContext.validation = baseValidationArray; return baseContext; @@ -84,8 +91,8 @@ export class PlanStatusEditorModel extends BaseEditorModel implements PlanStatus const baseValidationArray: Validation[] = new Array(); baseValidationArray.push({ key: 'allowAnonymous', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}allowAnonymous`)] }); baseValidationArray.push({ key: 'allowAuthenticated', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}allowAuthenticated`)] }); - baseValidationArray.push({ key: 'planRoles', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}planRoles`)] }); - baseValidationArray.push({ key: 'roles', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}roles`)] }); + baseValidationArray.push({ key: 'planRoles', validators: [BackendErrorValidator(this.validationErrorModel, `${rootPath}planRoles`)] }); + baseValidationArray.push({ key: 'roles', validators: [BackendErrorValidator(this.validationErrorModel, `${rootPath}roles`)] }); baseContext.validation = baseValidationArray; return baseContext; @@ -104,6 +111,7 @@ export interface PlanStatusForm { export interface PlanStatusDefinitionForm { authorization: FormGroup + availableActions?: FormControl } export interface PlanStatusDefinitionAuthorizationForm { diff --git a/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.html b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.html index 004000c49..7bae0856e 100644 --- a/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.html +++ b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.html @@ -38,13 +38,7 @@
-
- {{enumUtils.toPlanStatusString(row.internalStatus)}} -
+ {{enumUtils.toPlanStatusString(row.internalStatus)}}
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..6f3024af5 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,8 @@ import { BehaviorSubject } from 'rxjs'; import { debounceTime, map, takeUntil } from 'rxjs/operators'; import { nameof } from 'ts-simple-nameof'; import { ActivityListingType } from '../dashboard.component'; +import { DescriptionStatus, DescriptionStatusDefinition } from '@app/core/model/description-status/description-status'; +import { PlanStatus, PlanStatusDefinition } from '@app/core/model/plan-status/plan-status'; @Component({ selector: 'app-recent-edited-activity', @@ -231,17 +233,17 @@ export class RecentEditedActivityComponent extends BaseComponent implements OnIn response.forEach(item => { 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); + if (item.plan.status.internalStatus == PlanStatusEnum.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); } }) @@ -270,7 +272,10 @@ export class RecentEditedActivityComponent extends BaseComponent implements OnIn [nameof(x => x.plan), nameof(x => x.id)].join('.'), [nameof(x => x.plan), nameof(x => x.label)].join('.'), [nameof(x => x.plan), nameof(x => x.description)].join('.'), - [nameof(x => x.plan), nameof(x => x.status)].join('.'), + [nameof(x => x.plan), nameof(x => x.status), nameof(x => x.id)].join('.'), + [nameof(x => x.plan), nameof(x => x.status), nameof(x => x.name)].join('.'), + [nameof(x => x.plan), nameof(x => x.status), nameof(x => x.internalStatus)].join('.'), + [nameof(x => x.plan), nameof(x => x.status), nameof(x => x.definition), nameof(x => x.availableActions)].join('.'), [nameof(x => x.plan), nameof(x => x.accessType)].join('.'), [nameof(x => x.plan), nameof(x => x.version)].join('.'), [nameof(x => x.plan), nameof(x => x.versionStatus)].join('.'), @@ -288,7 +293,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 +325,10 @@ 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.status), nameof(x => x.definition), nameof(x => x.availableActions)].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/description-copy-dialog/description-copy-dialog.component.ts b/frontend/src/app/ui/description/description-copy-dialog/description-copy-dialog.component.ts index a44c244a3..3992fd4bc 100644 --- a/frontend/src/app/ui/description/description-copy-dialog/description-copy-dialog.component.ts +++ b/frontend/src/app/ui/description/description-copy-dialog/description-copy-dialog.component.ts @@ -19,6 +19,7 @@ import { DescriptionTemplatesInSection, PlanBlueprint, PlanBlueprintDefinition, import { TenantLookup } from '@app/core/query/tenant.lookup'; import { Tenant } from '@app/core/model/tenant/tenant'; import { AuthService } from '@app/core/services/auth/auth.service'; +import { PlanStatusLookup } from '@app/core/query/plan-status.lookup'; @Component({ selector: 'description-copy-dialog-component', @@ -49,7 +50,9 @@ export class DescriptionCopyDialogComponent { if (excludedIds && excludedIds.length > 0) { lookup.excludedIds = excludedIds; } if (ids && ids.length > 0) { lookup.ids = ids; } lookup.isActive = [IsActive.Active]; - lookup.statuses = [PlanStatusEnum.Draft]; + const planStatusLookup: PlanStatusLookup = new PlanStatusLookup(); + planStatusLookup.internalStatuses = [PlanStatusEnum.Draft]; + lookup.planStatusSubQuery = planStatusLookup; lookup.project = { fields: [ nameof(x => x.id), 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..686a06144 100644 --- a/frontend/src/app/ui/description/editor/description-editor.component.html +++ b/frontend/src/app/ui/description/editor/description-editor.component.html @@ -27,10 +27,10 @@

-
+
- @@ -67,9 +67,10 @@ - + + + -
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 b00a3232d..298e7b174 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,9 @@ 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 { DescriptionStatus } from '@app/core/model/description-status/description-status'; +import { DescriptionStatusAvailableActionType } from '@app/core/common/enum/description-status-available-action-type'; +import { DescriptionStatusPermission } from '@app/core/common/enum/description-status-permission.enum'; @Component({ selector: 'app-description-editor-component', @@ -59,6 +62,7 @@ export class DescriptionEditorComponent extends BaseEditor; + oldStatusId: Guid; + constructor( // BaseFormEditor injected dependencies public routerUtils: RouterUtilsService, @@ -190,7 +196,8 @@ export class DescriptionEditorComponent extends BaseEditor { - this.finalize(); + const finalizedStatus = this.item.availableStatuses?.find(x => x.internalStatus === DescriptionStatusEnum.Finalized) || null; + if (finalizedStatus) this.finalize(finalizedStatus.id); }, 0); } }); @@ -215,6 +222,10 @@ export class DescriptionEditorComponent extends BaseEditor x === DescriptionStatusAvailableActionType.Export).length > 0) this.canExport = true; + } if (data && data?.plan?.planUsers) data.plan.planUsers = data.plan.planUsers.filter(x => x.isActive === IsActive.Active); this.item = data; this.initialTemplateId = data?.descriptionTemplate?.id?.toString(); @@ -227,6 +238,13 @@ export class DescriptionEditorComponent extends BaseEditor void): void { const formData = this.formService.getValue(this.formGroup.value) as DescriptionPersist; + const finalizedStatus = this.item.availableStatuses?.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; - this.formGroup.disable(); - } + if (finalizedStatus && this.formGroup.get('statusId').value == finalizedStatus.id) { + this.isFinalized = true; + this.formGroup.disable(); + } }, 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); @@ -327,7 +339,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(); @@ -554,12 +566,16 @@ export class DescriptionEditorComponent extends BaseEditor x.toLowerCase() === DescriptionStatusPermission.Edit.toLowerCase()) + } + registerFormListeners() { this.formGroup.get('descriptionTemplateId').valueChanges @@ -701,7 +721,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: { @@ -750,7 +782,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..74c2552ff 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,11 +1,13 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; -import { DescriptionStatusEnum } from '@app/core/common/enum/description-status'; +import { DescriptionStatusPermission } from '@app/core/common/enum/description-status-permission.enum'; import { IsActive } from '@app/core/common/enum/is-active.enum'; import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { DescriptionStatus, DescriptionStatusDefinition } 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'; +import { PlanStatus } from '@app/core/model/plan-status/plan-status'; import { Plan, PlanDescriptionTemplate, PlanUser } from '@app/core/model/plan/plan'; import { PrefillingSource } from '@app/core/model/prefilling-source/prefilling-source'; import { ReferenceType } from '@app/core/model/reference-type/reference-type'; @@ -52,15 +54,19 @@ 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.status), nameof(x => x.definition), nameof(x => x.availableActions)].join('.'), [nameof(x => x.authorizationFlags), AppPermission.EditDescription].join('.'), [nameof(x => x.authorizationFlags), AppPermission.DeleteDescription].join('.'), [nameof(x => x.authorizationFlags), AppPermission.FinalizeDescription].join('.'), [nameof(x => x.authorizationFlags), AppPermission.AnnotateDescription].join('.'), + [nameof(x => x.statusAuthorizationFlags), DescriptionStatusPermission.Edit].join('.'), + [nameof(x => x.planDescriptionTemplate), nameof(x => x.id)].join('.'), [nameof(x => x.planDescriptionTemplate), nameof(x => x.sectionId)].join('.'), [nameof(x => x.planDescriptionTemplate), nameof(x => x.isActive)].join('.'), @@ -100,7 +106,12 @@ export class DescriptionEditorEntityResolver extends BaseEditorResolver { nameof(x => x.createdAt), nameof(x => x.hash), - nameof(x => x.isActive) + nameof(x => x.isActive), + + [nameof(x => x.availableStatuses), nameof(x => x.id)].join('.'), + [nameof(x => x.availableStatuses), nameof(x => x.name)].join('.'), + [nameof(x => x.availableStatuses), nameof(x => x.internalStatus)].join('.'), + [nameof(x => x.availableStatuses), nameof(x => x.action)].join('.'), ] } @@ -120,7 +131,11 @@ export class DescriptionEditorEntityResolver extends BaseEditorResolver { return [ (prefix ? prefix + '.' : '') + [nameof(x => x.id)].join('.'), (prefix ? prefix + '.' : '') + [nameof(x => x.label)].join('.'), - (prefix ? prefix + '.' : '') + [nameof(x => x.status)].join('.'), + + (prefix ? prefix + '.' : '') + [nameof(x => x.status), nameof(x => x.id)].join('.'), + (prefix ? prefix + '.' : '') + [nameof(x => x.status), nameof(x => x.name)].join('.'), + (prefix ? prefix + '.' : '') + [nameof(x => x.status), nameof(x => x.internalStatus)].join('.'), + (prefix ? prefix + '.' : '') + [nameof(x => x.isActive)].join('.'), (prefix ? prefix + '.' : '') + [nameof(x => x.authorizationFlags), AppPermission.EditPlan].join('.'), @@ -203,7 +218,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..22406ed03 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,8 @@ 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, DescriptionStatusDefinition } from '@app/core/model/description-status/description-status'; +import { PlanStatus } from '@app/core/model/plan-status/plan-status'; @Component({ selector: 'app-description-listing-component', @@ -95,10 +97,10 @@ export class DescriptionListingComponent extends BaseListingComponent 0; } get hasFilters(): boolean { - return (this.lookup.like != null && this.lookup.like != '') || this.lookup.statuses != null || + return (this.lookup.like != null && this.lookup.like != '') || this.lookup.statusIds != null || this.lookup.planSubQuery != null || this.lookup.descriptionTemplateSubQuery != null || this.lookup.descriptionTagSubQuery != null || this.lookup.descriptionReferenceSubQuery != null || - this.lookup.tenantSubQuery != null; + this.lookup.tenantSubQuery != null || this.lookup.isActive != null; } constructor( @@ -413,7 +415,8 @@ export class DescriptionListingComponent extends BaseListingComponent(x => x.id), nameof(x => x.tenantId), nameof(x => x.label), - nameof(x => x.status), + nameof(x => x.isActive), + [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.status), nameof(x => x.definition), nameof(x => x.availableActions)].join('.'), + nameof(x => x.updatedAt), nameof(x => x.belongsToCurrentTenant), nameof(x => x.finalizedAt), @@ -504,7 +513,9 @@ export class DescriptionListingComponent extends BaseListingComponent(x => x.descriptionTemplate), nameof(x => x.groupId)].join('.'), [nameof(x => x.plan), nameof(x => x.id)].join('.'), [nameof(x => x.plan), nameof(x => x.label)].join('.'), - [nameof(x => x.plan), nameof(x => x.status)].join('.'), + [nameof(x => x.plan), nameof(x => x.status), nameof(x => x.id)].join('.'), + [nameof(x => x.plan), nameof(x => x.status), nameof(x => x.name)].join('.'), + [nameof(x => x.plan), nameof(x => x.status), nameof(x => x.internalStatus)].join('.'), [nameof(x => x.plan), nameof(x => x.accessType)].join('.'), [nameof(x => x.plan), nameof(x => x.finalizedAt)].join('.'), [nameof(x => x.plan), nameof(x => x.blueprint), nameof(x => x.id)].join('.'), diff --git a/frontend/src/app/ui/description/listing/filtering/description-filter-dialogue/description-filter-dialog.component.ts b/frontend/src/app/ui/description/listing/filtering/description-filter-dialogue/description-filter-dialog.component.ts index b747d42f6..f0c43d11e 100644 --- a/frontend/src/app/ui/description/listing/filtering/description-filter-dialogue/description-filter-dialog.component.ts +++ b/frontend/src/app/ui/description/listing/filtering/description-filter-dialogue/description-filter-dialog.component.ts @@ -3,6 +3,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { AnalyticsService } from '@app/core/services/matomo/analytics-service'; import { DescriptionFilterComponent, DescriptionListingFilters } from '../description-filter.component'; import { DescriptionLookup, ReferencesWithType } from '@app/core/query/description.lookup'; +import { IsActive } from '@app/core/common/enum/is-active.enum'; @Component({ selector: 'description-filter-dialog-component', @@ -47,7 +48,8 @@ export class DescriptionFilterDialogComponent implements OnInit { private _buildDescriptionFilters(lookup: DescriptionLookup, references: ReferencesWithType[]): DescriptionListingFilters { return { - status: lookup.statuses?.[0] ?? null, + statusId: lookup.statusIds?.[0] ?? null, + isActive: lookup.isActive?.[0] == IsActive.Active ?? false, viewOnlyTenant: lookup.tenantSubQuery?.codes?.length > 0, role: lookup.planSubQuery?.planUserSubQuery?.userRoles?.[0] ?? null, descriptionTemplates: lookup.descriptionTemplateSubQuery?.ids ?? [], diff --git a/frontend/src/app/ui/description/listing/filtering/description-filter.component.html b/frontend/src/app/ui/description/listing/filtering/description-filter.component.html index e493dbcca..9ef003838 100644 --- a/frontend/src/app/ui/description/listing/filtering/description-filter.component.html +++ b/frontend/src/app/ui/description/listing/filtering/description-filter.component.html @@ -11,16 +11,20 @@
{{ 'DESCRIPTION-LISTING.FILTERS.STATUS.NAME' | translate}}
- - {{ 'DESCRIPTION-LISTING.FILTERS.STATUS.TYPES.ANY' | translate }} - {{ 'DESCRIPTION-LISTING.FILTERS.STATUS.TYPES.DRAFT' | translate }} - {{ 'DESCRIPTION-LISTING.FILTERS.STATUS.TYPES.FINALIZED' | translate }} - {{ 'DESCRIPTION-LISTING.FILTERS.STATUS.TYPES.CANCELED' | translate }} - + + {{'DESCRIPTION-LISTING.FILTERS.STATUS.PLACEHOLDER' | translate }} + +
+ +
+ {{ 'DESCRIPTION-LISTING.FILTERS.IS-ACTIVE' | translate }} +
+
+
{{ 'DESCRIPTION-LISTING.FILTERS.RELATED-TENANT.NAME' | translate}}
diff --git a/frontend/src/app/ui/description/listing/filtering/description-filter.component.ts b/frontend/src/app/ui/description/listing/filtering/description-filter.component.ts index e5d49e44c..0aba25c8b 100644 --- a/frontend/src/app/ui/description/listing/filtering/description-filter.component.ts +++ b/frontend/src/app/ui/description/listing/filtering/description-filter.component.ts @@ -8,6 +8,7 @@ import { Reference } from '@app/core/model/reference/reference'; import { ReferencesWithType } from '@app/core/query/description.lookup'; import { ReferenceLookup } from '@app/core/query/reference.lookup'; import { AuthService } from '@app/core/services/auth/auth.service'; +import { DescriptionStatusService } from '@app/core/services/description-status/description-status.service'; import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service'; import { PlanService } from '@app/core/services/plan/plan.service'; import { ReferenceTypeService } from '@app/core/services/reference-type/reference-type.service'; @@ -57,6 +58,7 @@ export class DescriptionFilterComponent extends BaseCriteriaComponent; constructor( @@ -67,6 +69,7 @@ export class DescriptionFilterComponent extends BaseCriteriaComponent({ - status: new FormControl(filters?.status), + statusId: new FormControl(filters?.statusId), + isActive: new FormControl(filters?.isActive), viewOnlyTenant: new FormControl(filters.viewOnlyTenant), role: new FormControl(filters.role), descriptionTemplates: new FormControl(filters.descriptionTemplates), @@ -110,7 +114,8 @@ export class DescriptionFilterComponent extends BaseCriteriaComponent, + statusId: FormControl, + isActive: FormControl, viewOnlyTenant: FormControl, role: FormControl, descriptionTemplates: FormControl, 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..db5efb0cc 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)) }} + {{ enumUtils.toPlanUserRolesString(planService.getCurrentUserRolesInPlan(description?.plan?.planUsers, isDeleted)) }} . - 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}}
@@ -33,9 +36,9 @@ 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..8a3d255d6 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 @@ -30,6 +30,7 @@ import { AnalyticsService } from '@app/core/services/matomo/analytics-service'; import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; import { RouterUtilsService } from '@app/core/services/router/router-utils.service'; import { Tenant } from '@app/core/model/tenant/tenant'; +import { DescriptionStatusAvailableActionType } from '@app/core/common/enum/description-status-available-action-type'; @Component({ selector: 'app-description-listing-item-component', @@ -53,6 +54,7 @@ export class DescriptionListingItemComponent extends BaseComponent implements On canDelete: boolean = false; canEdit: boolean = false; canInvitePlanUsers: boolean = false; + canExport: boolean = false; constructor( public routerUtils: RouterUtilsService, @@ -82,7 +84,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 { @@ -91,13 +93,16 @@ export class DescriptionListingItemComponent extends BaseComponent implements On } this.canDelete = !this.isPublic && (this.authService.hasPermission(AppPermission.DeleteDescription) || - this.description.authorizationFlags?.some(x => x === AppPermission.DeleteDescription)) && this.description.belongsToCurrentTenant != false; + this.description.authorizationFlags?.some(x => x === AppPermission.DeleteDescription)) && !this.isDeleted &&this.description.belongsToCurrentTenant != false; this.canEdit = !this.isPublic && (this.authService.hasPermission(AppPermission.EditDescription) || this.description.authorizationFlags?.some(x => x === AppPermission.EditDescription)) && this.description.belongsToCurrentTenant != false; this.canInvitePlanUsers = !this.isPublic && (this.authService.hasPermission(AppPermission.InvitePlanUsers) || - this.description.authorizationFlags?.some(x => x === AppPermission.InvitePlanUsers)) && this.description.belongsToCurrentTenant != false; + this.description.authorizationFlags?.some(x => x === AppPermission.InvitePlanUsers)) && !this.isDeleted && this.description.belongsToCurrentTenant != false; + + this.canExport = this.description.authorizationFlags?.some(x => x === AppPermission.ExportDescription) || this.authentication.hasPermission(AppPermission.ExportDescription) && !this.isDeleted && + this.description.status?.definition?.availableActions?.filter(x => x === DescriptionStatusAvailableActionType.Export).length > 0; } public getTenantName(id: Guid): string { @@ -106,7 +111,7 @@ export class DescriptionListingItemComponent extends BaseComponent implements On isUserPlanRelated() { const principalId: Guid = this.authService.userId(); - return this.description.plan.planUsers?.some(x => (x.user.id === principalId)); + return this.description.plan?.planUsers?.some(x => (x.user.id === principalId)); } public isAuthenticated(): boolean { 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..931ba9d43 100644 --- a/frontend/src/app/ui/description/overview/description-overview.component.html +++ b/frontend/src/app/ui/description/overview/description-overview.component.html @@ -23,11 +23,11 @@

{{ enumUtils.toPlanUserRolesString(planService.getCurrentUserRolesInPlan(description?.plan?.planUsers)) }}

.
-
+
public {{'DESCRIPTION-OVERVIEW.PUBLIC' | translate}}
-
.
+
.
lock_outline {{'DESCRIPTION-OVERVIEW.LOCKED' | translate}} @@ -37,7 +37,7 @@ {{(isActive ? 'DESCRIPTION-OVERVIEW.EDITED' : 'DESCRIPTION-OVERVIEW.DELETED') | translate}} : {{description.updatedAt | dateTimeFormatter: "d MMMM y"}}
-
+
check {{'DESCRIPTION-OVERVIEW.FINALISED' | translate}} @@ -46,7 +46,7 @@
- @if(isActive && (canEdit || canAnnotate) && isDraftDescription(description) && !isLocked){ + @if(isActive && (canEdit || canAnnotate) && isNotFinalizedDescription(description) && !isLocked){ @@ -130,40 +130,33 @@
- +
- -
-
- + +
+
+
+ + +
+
+

{{ status.action?.length > 0 ? status.action : status.name }}

+
-
-

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

-
-
-
-
-
+
+
+
+
- -
-
- -
-
-

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

-
-
-
- +
- +
- -
-
- + +
+
+
+ + +
+
+

{{ status.action?.length > 0 ? status.action : status.name }}

+
-
-

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

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

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

+
+
+
+
+
@@ -254,7 +247,7 @@
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..dd023ebc7 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,10 @@ 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'; +import { PlanStatus, PlanStatusDefinition } from '@app/core/model/plan-status/plan-status'; +import { PlanStatusAvailableActionType } from '@app/core/common/enum/plan-status-available-action-type'; +import { PlanStatusPermission } from '@app/core/common/enum/plan-status-permission.enum'; @Component({ selector: 'app-plan-overview', @@ -79,6 +83,7 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { planAccessTypeEnum = PlanAccessType; planStatusEnum = PlanStatusEnum; planUserRoleEnum = PlanUserRole; + planVersionStatusEnum = PlanVersionStatus; authorFocus: string; userName: string; @@ -131,11 +136,11 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { this.plan = data; this.plan.planUsers = this.isActive ? data?.planUsers?.filter((x) => x.isActive === IsActive.Active) : data?.planUsers; 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); + if (this.plan.descriptions && this.isActive) { + if (this.plan.status?.internalStatus == PlanStatusEnum.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); @@ -229,6 +234,10 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { return this.language.instant('PLAN-OVERVIEW.INFOS.UNAUTHORIZED-ORCID'); } + get canEditStatus(): boolean{ + return (this.plan as Plan).statusAuthorizationFlags ?.some(x => x.toLowerCase() === PlanStatusPermission.Edit.toLowerCase()) + } + onFetchingDeletedCallbackError(redirectRoot: string) { this.router.navigate([this.routerUtils.generateUrl(redirectRoot)]); } @@ -295,7 +304,9 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { canExportPlan(): boolean { const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : []; - return (authorizationFlags?.some(x => x === AppPermission.ExportPlan) || this.authentication.hasPermission(AppPermission.ExportPlan)); + return (authorizationFlags?.some(x => x === AppPermission.ExportPlan) || this.authentication.hasPermission(AppPermission.ExportPlan)) && + this.plan?.status?.definition?.availableActions?.filter(x => x === PlanStatusAvailableActionType.Export).length > 0; + } canInvitePlanUsers(): boolean { @@ -308,12 +319,14 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { canAssignPlanUsers(): boolean { const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : []; - return (authorizationFlags?.some(x => x === AppPermission.AssignPlanUsers) || this.authentication.hasPermission(AppPermission.AssignPlanUsers)) && this.isPublicView == false && this.plan.belongsToCurrentTenant != false; + return (authorizationFlags?.some(x => x === AppPermission.AssignPlanUsers) || this.authentication.hasPermission(AppPermission.AssignPlanUsers)) && this.isPublicView == false && this.plan.belongsToCurrentTenant != false && + (this.plan.status.internalStatus == null || this.plan.status.internalStatus != PlanStatusEnum.Finalized); } canDepositPlan(): boolean { const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : []; - return (authorizationFlags?.some(x => x === AppPermission.DepositPlan) || this.authentication.hasPermission(AppPermission.DepositPlan)) && this.isPublicView == false && this.plan.belongsToCurrentTenant != false; + return (authorizationFlags?.some(x => x === AppPermission.DepositPlan) || this.authentication.hasPermission(AppPermission.DepositPlan)) && this.isPublicView == false && this.plan.belongsToCurrentTenant != false && + this.plan?.status?.definition?.availableActions?.filter(x => x === PlanStatusAvailableActionType.Deposit).length > 0; } @@ -411,15 +424,11 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { } isDraftPlan() { - return this.plan.status == PlanStatusEnum.Draft; - } - - isFinalizedPlan(plan: Plan) { - return plan.status == PlanStatusEnum.Finalized; + return this.plan.status?.internalStatus != PlanStatusEnum.Finalized; } isPublishedPlan() { - return (this.plan.status == PlanStatusEnum.Finalized && this.plan.accessType === PlanAccessType.Public); + return (this.plan.status?.internalStatus == PlanStatusEnum.Finalized && this.plan.accessType === PlanAccessType.Public); } hasDoi() { @@ -441,7 +450,24 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { return (this.plan.entityDois.length < this.depositRepos.length); } - finalize() { + persistStatus(status: PlanStatus) { + if (status.internalStatus != null && status.internalStatus === PlanStatusEnum.Finalized) { + this.finalize(status.id); + } else if (this.plan.status.internalStatus === PlanStatusEnum.Finalized){ + this.reverseFinalization(status.id); + } else { + // other statuses + this.planService.setStatus(this.plan.id, status.id).pipe(takeUntil(this._destroyed)) + .subscribe({ + complete: () => {this.reloadPage(); this.onUpdateCallbackSuccess()}, + error:(error: any) => { + this.onUpdateCallbackError(error) + } + }); + } + } + + finalize(newStatusId) { const dialogRef = this.dialog.open(PlanFinalizeDialogComponent, { maxWidth: '500px', restoreFocus: false, @@ -453,7 +479,7 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe((result: PlanFinalizeDialogOutput) => { if (result && !result.cancelled) { - this.planService.finalize(this.plan.id, result.descriptionsToBeFinalized) + this.planService.setStatus(this.plan.id, newStatusId, result.descriptionsToBeFinalized) .pipe(takeUntil(this._destroyed)) .subscribe({ complete: () => { @@ -511,7 +537,7 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { } } - reverseFinalization() { + reverseFinalization(newStatusId: Guid) { const dialogRef = this.dialog.open(ConfirmationDialogComponent, { restoreFocus: false, data: { @@ -523,7 +549,7 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { }); dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { if (result) { - this.planService.undoFinalize(this.plan.id, PlanEditorEntityResolver.lookupFields()).pipe(takeUntil(this._destroyed)) + this.planService.setStatus(this.plan.id, newStatusId).pipe(takeUntil(this._destroyed)) .subscribe({ complete: () => {this.reloadPage(); this.onUpdateCallbackSuccess()}, error:(error: any) => { @@ -636,7 +662,10 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { 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.status), nameof(x => x.definition), nameof(x => x.availableActions)].join('.'), nameof(x => x.accessType), nameof(x => x.version), nameof(x => x.versionStatus), @@ -654,13 +683,16 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { [nameof(x => x.authorizationFlags), AppPermission.AssignPlanUsers].join('.'), [nameof(x => x.authorizationFlags), AppPermission.EditPlan].join('.'), [nameof(x => x.authorizationFlags), AppPermission.DepositPlan].join('.'), + [nameof(x => x.statusAuthorizationFlags), PlanStatusPermission.Edit].join('.'), [nameof(x => x.entityDois), nameof(x => x.id)].join('.'), [nameof(x => x.entityDois), nameof(x => x.repositoryId)].join('.'), [nameof(x => x.entityDois), nameof(x => x.doi)].join('.'), [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('.'), @@ -695,6 +727,11 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { [nameof(x => x.otherPlanVersions), nameof(x => x.version)].join('.'), [nameof(x => x.otherPlanVersions), nameof(x => x.isActive)].join('.'), + [nameof(x => x.availableStatuses), nameof(x => x.id)].join('.'), + [nameof(x => x.availableStatuses), nameof(x => x.name)].join('.'), + [nameof(x => x.availableStatuses), nameof(x => x.internalStatus)].join('.'), + [nameof(x => x.availableStatuses), nameof(x => x.action)].join('.'), + nameof(x => x.hash), ] } diff --git a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.html b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.html index a54b40af6..f426a34fb 100644 --- a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.html +++ b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.html @@ -9,9 +9,9 @@
{{(canEdit ? 'PLAN-EDITOR.TITLE-EDIT' : 'PLAN-EDITOR.TITLE-PREVIEW') | translate}}
{{ formGroup.get('label').value }} ({{'PLAN-EDITOR.UNSAVED-CHANGES' | translate}})
-
+
-
+
-
- +
+
-
- - +
+
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 927ea50d1..a9d6bf212 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 @@ -60,6 +60,11 @@ import { PlanEditorService } from './plan-editor.service'; import { PlanEditorEntityResolver } from './resolvers/plan-editor-enitity.resolver'; import { FormAnnotationService } from '@app/ui/annotations/annotation-dialog-component/form-annotation.service'; import { PlanAssociatedUser } from '@app/core/model/user/user'; +import { PlanStatusService } from '@app/core/services/plan/plan-status.service'; +import { PlanStatus } from '@app/core/model/plan-status/plan-status'; +import { PlanStatusAvailableActionType } from '@app/core/common/enum/plan-status-available-action-type'; +import { PlanVersionStatus } from '@app/core/common/enum/plan-version-status'; +import { PlanStatusPermission } from '@app/core/common/enum/plan-status-permission.enum'; @Component({ selector: 'app-plan-editor', @@ -86,6 +91,7 @@ export class PlanEditorComponent extends BaseEditor imple planAccessTypeEnum = PlanAccessType; planAccessTypeEnumValues = this.enumUtils.getEnumValues(PlanAccessType); planUserTypeEnum = PlanUserType; + planVersionStatusEnum = PlanVersionStatus; planUserTypeEnumValues = this.enumUtils.getEnumValues(PlanUserType); planUserRoleEnumValues = this.enumUtils.getEnumValues(PlanUserRole); fileTransformerEntityTypeEnum = FileTransformerEntityType; @@ -136,7 +142,11 @@ export class PlanEditorComponent extends BaseEditor imple } protected get canReverseFinalize(): boolean { - return !this.isDeleted && !this.isNew && this.isLockedByUser && this.item.status == PlanStatusEnum.Finalized && (this.hasPermission(this.authService.permissionEnum.EditPlan) || this.item?.authorizationFlags?.some(x => x === AppPermission.EditPlan)); + return !this.isDeleted && !this.isNew && this.isLockedByUser && this.item.status?.internalStatus == PlanStatusEnum.Finalized && (this.hasPermission(this.authService.permissionEnum.EditPlan) || this.item?.authorizationFlags?.some(x => x === AppPermission.EditPlan)); + } + + protected get canExport(): boolean { + return this.item?.status?.definition?.availableActions?.filter(x => x === PlanStatusAvailableActionType.Export).length > 0 && (this.hasPermission(this.authService.permissionEnum.ExportPlan) || this.item?.authorizationFlags?.some(x => x === AppPermission.ExportPlan)); } protected canEditSection(id: Guid): boolean { @@ -155,6 +165,10 @@ export class PlanEditorComponent extends BaseEditor imple return !this.isDeleted && !this.isFinalized &&(this.isNew ? this.authService.hasPermission(AppPermission.NewPlan) : this.item.authorizationFlags?.some(x => x === AppPermission.EditPlan) || this.authService.hasPermission(AppPermission.EditPlan)); } + get canEditStatus(): boolean{ + return this.item.statusAuthorizationFlags?.some(x => x.toLowerCase() === PlanStatusPermission.Edit.toLowerCase()) + } + protected canAnnotate(id: Guid): boolean { return !this.isDeleted && this.permissionPerSection && this.permissionPerSection[id.toString()] && this.permissionPerSection[id.toString()].some(x => x === AppPermission.AnnotatePlan); } @@ -193,6 +207,7 @@ export class PlanEditorComponent extends BaseEditor imple private breadcrumbService: BreadcrumbService, public fileTransformerService: FileTransformerService, private formAnnotationService: FormAnnotationService, + private pLanStatusService: PlanStatusService ) { const descriptionLabel: string = route.snapshot.data['entity']?.label; if (descriptionLabel) { @@ -260,15 +275,16 @@ export class PlanEditorComponent extends BaseEditor imple } } this.editorModel = data ? new PlanEditorModel().fromModel(data) : new PlanEditorModel(); + this.isDeleted = data ? data.isActive === IsActive.Inactive : false; if (data) { - if (data.descriptions) { - if (data.status == PlanStatusEnum.Finalized) { - data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatusEnum.Finalized); + if (data.descriptions && !this.isDeleted) { + if (data.status?.internalStatus == PlanStatusEnum.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) { + if (data.planDescriptionTemplates && !this.isDeleted) { data.planDescriptionTemplates = data.planDescriptionTemplates.filter(x => x.isActive === IsActive.Active); } if (data.entityDois && data.entityDois.length > 0) data.entityDois = data.entityDois.filter(x => x.isActive === IsActive.Active); @@ -277,8 +293,6 @@ export class PlanEditorComponent extends BaseEditor imple this.item = data; this.selectedBlueprint = data?.blueprint; - this.isDeleted = data ? data.isActive === IsActive.Inactive : false; - this.isFinalized = data ? data.status === PlanStatusEnum.Finalized : false; if (data && data.id) { const descriptionSectionPermissionResolverModel: DescriptionSectionPermissionResolver = { @@ -291,6 +305,14 @@ export class PlanEditorComponent extends BaseEditor imple this.buildForm(); } + if (data && data.status?.internalStatus == PlanStatusEnum.Finalized || this.isDeleted) { + this.viewOnly = true; + this.isFinalized = true; + this.formGroup.disable(); + } else { + this.viewOnly = false; + } + if (this.item && this.item.id != null) { this.checkLock(this.item.id, LockTargetType.Plan, 'PLAN-EDITOR.LOCKED-DIALOG.TITLE', 'PLAN-EDITOR.LOCKED-DIALOG.MESSAGE'); } @@ -304,14 +326,6 @@ export class PlanEditorComponent extends BaseEditor imple this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !this.canEdit); this.sectionToFieldsMap = this.prepareErrorIndication(); - - if (this.editorModel.status == PlanStatusEnum.Finalized || this.isDeleted) { - this.viewOnly = true; - this.isFinalized = this.editorModel.status == PlanStatusEnum.Finalized; - this.formGroup.disable(); - } else { - this.viewOnly = false; - } } prepareErrorIndication(): Map { @@ -449,7 +463,23 @@ export class PlanEditorComponent extends BaseEditor imple return (this.item.entityDois == null || this.item.entityDois.length == 0); } - finalize() { + persistStatus(status: PlanStatus) { + if (status.internalStatus != null && status.internalStatus === PlanStatusEnum.Finalized) { + this.finalize(status.id); + } else if (this.item.status.internalStatus === PlanStatusEnum.Finalized){ + this.reverseFinalization(status.id); + } else { + // other statuses + this.planService.setStatus(this.item.id, status.id).pipe(takeUntil(this._destroyed)) + .subscribe(data => { + this.onCallbackSuccess() + }, (error: any) => { + this.onCallbackError(error) + }); + } + } + + finalize(newStatusId) { const dialogRef = this.dialog.open(PlanFinalizeDialogComponent, { maxWidth: '500px', restoreFocus: false, @@ -461,7 +491,7 @@ export class PlanEditorComponent extends BaseEditor imple dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe((result: PlanFinalizeDialogOutput) => { if (result && !result.cancelled) { - this.planService.finalize(this.item.id, result.descriptionsToBeFinalized) + this.planService.setStatus(this.item.id, newStatusId, result.descriptionsToBeFinalized) .pipe(takeUntil(this._destroyed)) .subscribe(data => { this.onCallbackSuccess() @@ -474,7 +504,7 @@ export class PlanEditorComponent extends BaseEditor imple } - reverseFinalization() { + reverseFinalization(newStatusId: Guid) { const dialogRef = this.dialog.open(ConfirmationDialogComponent, { restoreFocus: false, data: { @@ -486,7 +516,7 @@ export class PlanEditorComponent extends BaseEditor imple }); dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { if (result) { - this.planService.undoFinalize(this.item.id, PlanEditorEntityResolver.lookupFields()).pipe(takeUntil(this._destroyed)) + this.planService.setStatus(this.item.id, newStatusId).pipe(takeUntil(this._destroyed)) .subscribe(data => { this.onCallbackSuccess() }, (error: any) => { @@ -594,7 +624,6 @@ export class PlanEditorComponent extends BaseEditor imple label: this.formGroup.get('label').value, description: this.formGroup.get('description').value, blueprint: this.selectedBlueprint, - status: PlanStatusEnum.Draft } this.prepareForm(plan); @@ -679,6 +708,9 @@ export class PlanEditorComponent extends BaseEditor imple // // public descriptionsInSection(sectionId: Guid) { + if (this.isDeleted) { + return this.item?.descriptions?.filter(x => x?.planDescriptionTemplate?.sectionId === sectionId) || []; + } return this.item?.descriptions?.filter(x => x.isActive == IsActive.Active && x?.planDescriptionTemplate?.sectionId === sectionId && x.planDescriptionTemplate.isActive == IsActive.Active) || []; } diff --git a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.model.ts b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.model.ts index 5994f394a..e53cec248 100644 --- a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.model.ts +++ b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.model.ts @@ -18,7 +18,7 @@ import { Guid } from "@common/types/guid"; export class PlanEditorModel extends BaseEditorModel implements PlanPersist { label: string; - status: PlanStatusEnum; + statusId: Guid; properties: PlanPropertiesEditorModel = new PlanPropertiesEditorModel(this.validationErrorModel); description: String; language: String; @@ -37,7 +37,7 @@ export class PlanEditorModel extends BaseEditorModel implements PlanPersist { if (item) { super.fromModel(item); this.label = item.label; - this.status = item.status; + this.statusId = item.status?.id; this.properties = new PlanPropertiesEditorModel(this.validationErrorModel).fromModel(item.properties, item.planReferences?.filter(x => x.isActive === IsActive.Active), item.blueprint); this.description = item.description; this.language = item.language; @@ -48,7 +48,12 @@ export class PlanEditorModel extends BaseEditorModel implements PlanPersist { item?.blueprint?.definition?.sections?.forEach(section => { if (section.hasTemplates) { const isNew = (item.id == null); - const sectionTemplatesFromPlan = item.planDescriptionTemplates?.filter(x => x.sectionId == section.id && x.isActive == IsActive.Active) || []; + let sectionTemplatesFromPlan: PlanDescriptionTemplate[]; + if (item.isActive != IsActive.Active) { + sectionTemplatesFromPlan = item.planDescriptionTemplates?.filter(x => x.sectionId == section.id) || []; + } else { + sectionTemplatesFromPlan = item.planDescriptionTemplates?.filter(x => x.sectionId == section.id && x.isActive == IsActive.Active) || []; + } if (sectionTemplatesFromPlan.length > 0) { sectionTemplatesFromPlan?.filter(x => x.sectionId == section.id).forEach(planDescriptionTemplate => { @@ -85,7 +90,7 @@ export class PlanEditorModel extends BaseEditorModel implements PlanPersist { const formGroup = this.formBuilder.group({ id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators], label: [{ value: this.label, disabled: disabled }, context.getValidation('label').validators], - status: [{ value: this.status, disabled: disabled }, context.getValidation('status').validators], + statusId: [{ value: this.statusId, disabled: disabled }, context.getValidation('statusId').validators], properties: this.properties.buildForm({ rootPath: `properties.`, disabled: disabled @@ -121,7 +126,7 @@ export class PlanEditorModel extends BaseEditorModel implements PlanPersist { const baseValidationArray: Validation[] = new Array(); baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(this.validationErrorModel, 'id')] }); baseValidationArray.push({ key: 'label', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'label')] }); - baseValidationArray.push({ key: 'status', validators: [BackendErrorValidator(this.validationErrorModel, 'status')] }); + baseValidationArray.push({ key: 'statusId', validators: [BackendErrorValidator(this.validationErrorModel, 'status')] }); baseValidationArray.push({ key: 'properties', validators: [BackendErrorValidator(this.validationErrorModel, 'properties')] }); baseValidationArray.push({ key: 'description', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'description')] }); baseValidationArray.push({ key: 'language', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'language')] }); 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..46fc38b8d 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,9 @@ 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'; +import { PlanStatus, PlanStatusDefinition } from '@app/core/model/plan-status/plan-status'; +import { PlanStatusPermission } from '@app/core/common/enum/plan-status-permission.enum'; @Injectable() export class PlanEditorEntityResolver extends BaseEditorResolver { @@ -28,7 +31,12 @@ export class PlanEditorEntityResolver extends BaseEditorResolver { ...BaseEditorResolver.lookupFields(), nameof(x => x.id), 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.status), nameof(x => x.definition), nameof(x => x.availableActions)].join('.'), + nameof(x => x.versionStatus), nameof(x => x.groupId), nameof(x => x.description), @@ -44,6 +52,9 @@ export class PlanEditorEntityResolver extends BaseEditorResolver { [nameof(x => x.authorizationFlags), AppPermission.EditPlan].join('.'), [nameof(x => x.authorizationFlags), AppPermission.DeletePlan].join('.'), [nameof(x => x.authorizationFlags), AppPermission.EditDescription].join('.'), + [nameof(x => x.authorizationFlags), AppPermission.ExportPlan].join('.'), + + [nameof(x => x.statusAuthorizationFlags), PlanStatusPermission.Edit].join('.'), [nameof(x => x.properties), nameof(x => x.planBlueprintValues), nameof(x => x.fieldId)].join('.'), [nameof(x => x.properties), nameof(x => x.planBlueprintValues), nameof(x => x.fieldValue)].join('.'), @@ -56,7 +67,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('.'), @@ -90,6 +103,11 @@ export class PlanEditorEntityResolver extends BaseEditorResolver { [nameof(x => x.entityDois), nameof(x => x.doi)].join('.'), [nameof(x => x.entityDois), nameof(x => x.isActive)].join('.'), + [nameof(x => x.availableStatuses), nameof(x => x.id)].join('.'), + [nameof(x => x.availableStatuses), nameof(x => x.name)].join('.'), + [nameof(x => x.availableStatuses), nameof(x => x.internalStatus)].join('.'), + [nameof(x => x.availableStatuses), nameof(x => x.action)].join('.'), + ...PlanEditorEntityResolver.blueprintLookupFields(nameof(x => x.blueprint)), ] 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; }, diff --git a/frontend/src/assets/i18n/baq.json b/frontend/src/assets/i18n/baq.json index 4a83cfdaf..8eeb7dfa0 100644 --- a/frontend/src/assets/i18n/baq.json +++ b/frontend/src/assets/i18n/baq.json @@ -770,6 +770,7 @@ "NO-ITEMS-FOUND": "Your search didn't match any items.", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPE": { "ANY": "Any", "PUBLIC": "Published", @@ -777,6 +778,7 @@ "DRAFT": "Draft" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -909,6 +911,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -916,7 +919,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -951,6 +955,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -958,7 +963,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -1063,6 +1069,7 @@ "RESET-FILTERS": "Reset", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPES": { "ANY": "Any", "DRAFT": "Draft", @@ -1070,6 +1077,7 @@ "CANCELED": "Canceled" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -2107,6 +2115,9 @@ "FINALIZED": "Finalized", "CANCELED": "Canceled" }, + "DESCRIPTION-STATUS-AVAILABLE-ACTION-TYPE": { + "EXPORT": "Export" + }, "PLAN-USER-ROLE": { "OWNER": "Owner", "VIEWER": "Viewer", @@ -2120,6 +2131,10 @@ "FINALISED": "Amaituta", "DRAFT": "Zirriborroa" }, + "PLAN-STATUS-AVAILABLE-ACTION-TYPE": { + "DEPOSIT": "Deposit", + "EXPORT": "Export" + }, "PLAN-VISIBILITY": { "PUBLIC": "Argitaratuta" }, @@ -2244,11 +2259,13 @@ "PLAN-INVITATION-EXISTING-USER": "Plan Invitation Existing User", "PLAN-MODIFIED": "Plan Modified", "PLAN-FINALISED": "Plan Finalised", + "PLAN-STATUS-CHANGED": "Plan Status Changed", "PLAN-ANNOTATION-CREATED": "Plan Annotation Created", "PLAN-ANNOTATION-STATUS-CHANGED": "Plan Annotation Status Changed", "DESCRIPTION-CREATED": "Description Created", "DESCRIPTION-MODIFIED": "Description Modified", "DESCRIPTION-FINALISED": "Description Finalised", + "DESCRIPTION-STATUS-CHANGED": "Description Status Changed", "DESCRIPTION-ANNOTATION-CREATED": "Description Annotation Created", "DESCRIPTION-ANNOTATION-STATUS-CHANGED": "Description Annotation Status Changed", "MERGE-ACCOUNT-CONFIRMATION": "Merge Account Confirmation", diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index b403f0ae6..6bc0b1a94 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -773,6 +773,7 @@ "NO-ITEMS-FOUND": "Your search didn't match any items.", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPE": { "ANY": "Any", "PUBLIC": "Published", @@ -780,6 +781,7 @@ "DRAFT": "Draft" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -912,6 +914,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -919,7 +922,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -954,6 +958,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -961,7 +966,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -1066,6 +1072,7 @@ "RESET-FILTERS": "Reset", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPES": { "ANY": "Any", "DRAFT": "Draft", @@ -1073,6 +1080,7 @@ "CANCELED": "Canceled" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -2110,6 +2118,9 @@ "FINALIZED": "Finalized", "CANCELED": "Canceled" }, + "DESCRIPTION-STATUS-AVAILABLE-ACTION-TYPE": { + "EXPORT": "Export" + }, "PLAN-USER-ROLE": { "OWNER": "Owner", "VIEWER": "Viewer", @@ -2123,6 +2134,10 @@ "FINALISED": "Fertiggestellt", "DRAFT": "Entwurf" }, + "PLAN-STATUS-AVAILABLE-ACTION-TYPE": { + "DEPOSIT": "Deposit", + "EXPORT": "Export" + }, "PLAN-VISIBILITY": { "PUBLIC": "Veröffentlicht" }, @@ -2247,11 +2262,13 @@ "PLAN-INVITATION-EXISTING-USER": "Plan Invitation Existing User", "PLAN-MODIFIED": "Plan Modified", "PLAN-FINALISED": "Plan Finalised", + "PLAN-STATUS-CHANGED": "Plan Status Changed", "PLAN-ANNOTATION-CREATED": "Plan Annotation Created", "PLAN-ANNOTATION-STATUS-CHANGED": "Plan Annotation Status Changed", "DESCRIPTION-CREATED": "Description Created", "DESCRIPTION-MODIFIED": "Description Modified", "DESCRIPTION-FINALISED": "Description Finalised", + "DESCRIPTION-STATUS-CHANGED": "Description Status Changed", "DESCRIPTION-ANNOTATION-CREATED": "Description Annotation Created", "DESCRIPTION-ANNOTATION-STATUS-CHANGED": "Description Annotation Status Changed", "MERGE-ACCOUNT-CONFIRMATION": "Merge Account Confirmation", diff --git a/frontend/src/assets/i18n/en.json b/frontend/src/assets/i18n/en.json index 64c76edee..886c1d45f 100644 --- a/frontend/src/assets/i18n/en.json +++ b/frontend/src/assets/i18n/en.json @@ -771,6 +771,7 @@ "NO-ITEMS-FOUND": "Your search didn't match any items.", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPE": { "ANY": "Any", "PUBLIC": "Published", @@ -778,6 +779,7 @@ "DRAFT": "Draft" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -910,6 +912,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -917,7 +920,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -961,6 +965,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -968,7 +973,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -1064,6 +1070,7 @@ "RESET-FILTERS": "Reset", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPES": { "ANY": "Any", "DRAFT": "Draft", @@ -1071,6 +1078,7 @@ "CANCELED": "Canceled" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -2110,6 +2118,9 @@ "FINALIZED": "Finalized", "CANCELED": "Canceled" }, + "DESCRIPTION-STATUS-AVAILABLE-ACTION-TYPE": { + "EXPORT": "Export" + }, "PLAN-USER-ROLE": { "OWNER": "Owner", "VIEWER": "Viewer", @@ -2123,6 +2134,10 @@ "FINALISED": "Finalized", "DRAFT": "Draft" }, + "PLAN-STATUS-AVAILABLE-ACTION-TYPE": { + "DEPOSIT": "Deposit", + "EXPORT": "Export" + }, "PLAN-VISIBILITY": { "PUBLIC": "Published" }, @@ -2247,11 +2262,13 @@ "PLAN-INVITATION-EXISTING-USER": "Plan Invitation Existing User", "PLAN-MODIFIED": "Plan Modified", "PLAN-FINALISED": "Plan Finalised", + "PLAN-STATUS-CHANGED": "Plan Status Changed", "PLAN-ANNOTATION-CREATED": "Plan Annotation Created", "PLAN-ANNOTATION-STATUS-CHANGED": "Plan Annotation Status Changed", "DESCRIPTION-CREATED": "Description Created", "DESCRIPTION-MODIFIED": "Description Modified", "DESCRIPTION-FINALISED": "Description Finalised", + "DESCRIPTION-STATUS-CHANGED": "Description Status Changed", "DESCRIPTION-ANNOTATION-CREATED": "Description Annotation Created", "DESCRIPTION-ANNOTATION-STATUS-CHANGED": "Description Annotation Status Changed", "MERGE-ACCOUNT-CONFIRMATION": "Merge Account Confirmation", diff --git a/frontend/src/assets/i18n/es.json b/frontend/src/assets/i18n/es.json index 094bba3d4..a3ac07187 100644 --- a/frontend/src/assets/i18n/es.json +++ b/frontend/src/assets/i18n/es.json @@ -773,6 +773,7 @@ "NO-ITEMS-FOUND": "Your search didn't match any items.", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPE": { "ANY": "Any", "PUBLIC": "Published", @@ -780,6 +781,7 @@ "DRAFT": "Draft" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -912,6 +914,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -919,7 +922,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -954,6 +958,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -961,7 +966,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -1066,6 +1072,7 @@ "RESET-FILTERS": "Reset", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPES": { "ANY": "Any", "DRAFT": "Draft", @@ -1073,6 +1080,7 @@ "CANCELED": "Canceled" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -2110,6 +2118,9 @@ "FINALIZED": "Finalized", "CANCELED": "Canceled" }, + "DESCRIPTION-STATUS-AVAILABLE-ACTION-TYPE": { + "EXPORT": "Export" + }, "PLAN-USER-ROLE": { "OWNER": "Owner", "VIEWER": "Viewer", @@ -2123,6 +2134,10 @@ "FINALISED": "Finalizado", "DRAFT": "Borrador" }, + "PLAN-STATUS-AVAILABLE-ACTION-TYPE": { + "DEPOSIT": "Deposit", + "EXPORT": "Export" + }, "PLAN-VISIBILITY": { "PUBLIC": "Publicado" }, @@ -2247,11 +2262,13 @@ "PLAN-INVITATION-EXISTING-USER": "Plan Invitation Existing User", "PLAN-MODIFIED": "Plan Modified", "PLAN-FINALISED": "Plan Finalised", + "PLAN-STATUS-CHANGED": "Plan Status Changed", "PLAN-ANNOTATION-CREATED": "Plan Annotation Created", "PLAN-ANNOTATION-STATUS-CHANGED": "Plan Annotation Status Changed", "DESCRIPTION-CREATED": "Description Created", "DESCRIPTION-MODIFIED": "Description Modified", "DESCRIPTION-FINALISED": "Description Finalised", + "DESCRIPTION-STATUS-CHANGED": "Description Status Changed", "DESCRIPTION-ANNOTATION-CREATED": "Description Annotation Created", "DESCRIPTION-ANNOTATION-STATUS-CHANGED": "Description Annotation Status Changed", "MERGE-ACCOUNT-CONFIRMATION": "Merge Account Confirmation", diff --git a/frontend/src/assets/i18n/gr.json b/frontend/src/assets/i18n/gr.json index 62549a400..f61e136b6 100644 --- a/frontend/src/assets/i18n/gr.json +++ b/frontend/src/assets/i18n/gr.json @@ -773,6 +773,7 @@ "NO-ITEMS-FOUND": "Your search didn't match any items.", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPE": { "ANY": "Any", "PUBLIC": "Published", @@ -780,6 +781,7 @@ "DRAFT": "Draft" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -912,6 +914,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -919,7 +922,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -954,6 +958,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -961,7 +966,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -1066,6 +1072,7 @@ "RESET-FILTERS": "Reset", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPES": { "ANY": "Any", "DRAFT": "Draft", @@ -1073,6 +1080,7 @@ "CANCELED": "Canceled" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -2110,6 +2118,9 @@ "FINALIZED": "Finalized", "CANCELED": "Canceled" }, + "DESCRIPTION-STATUS-AVAILABLE-ACTION-TYPE": { + "EXPORT": "Export" + }, "PLAN-USER-ROLE": { "OWNER": "Owner", "VIEWER": "Viewer", @@ -2123,6 +2134,10 @@ "FINALISED": "Οριστικοποιημένο", "DRAFT": "Προσχέδιο" }, + "PLAN-STATUS-AVAILABLE-ACTION-TYPE": { + "DEPOSIT": "Deposit", + "EXPORT": "Export" + }, "PLAN-VISIBILITY": { "PUBLIC": "Δημοσιευμένο" }, @@ -2247,11 +2262,13 @@ "PLAN-INVITATION-EXISTING-USER": "Plan Invitation Existing User", "PLAN-MODIFIED": "Plan Modified", "PLAN-FINALISED": "Plan Finalised", + "PLAN-STATUS-CHANGED": "Plan Status Changed", "PLAN-ANNOTATION-CREATED": "Plan Annotation Created", "PLAN-ANNOTATION-STATUS-CHANGED": "Plan Annotation Status Changed", "DESCRIPTION-CREATED": "Description Created", "DESCRIPTION-MODIFIED": "Description Modified", "DESCRIPTION-FINALISED": "Description Finalised", + "DESCRIPTION-STATUS-CHANGED": "Description Status Changed", "DESCRIPTION-ANNOTATION-CREATED": "Description Annotation Created", "DESCRIPTION-ANNOTATION-STATUS-CHANGED": "Description Annotation Status Changed", "MERGE-ACCOUNT-CONFIRMATION": "Merge Account Confirmation", diff --git a/frontend/src/assets/i18n/hr.json b/frontend/src/assets/i18n/hr.json index e0e8d5efa..7305db63f 100644 --- a/frontend/src/assets/i18n/hr.json +++ b/frontend/src/assets/i18n/hr.json @@ -773,6 +773,7 @@ "NO-ITEMS-FOUND": "Your search didn't match any items.", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPE": { "ANY": "Any", "PUBLIC": "Published", @@ -780,6 +781,7 @@ "DRAFT": "Draft" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -912,6 +914,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -919,7 +922,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -954,6 +958,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -961,7 +966,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -1066,6 +1072,7 @@ "RESET-FILTERS": "Reset", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPES": { "ANY": "Any", "DRAFT": "Draft", @@ -1073,6 +1080,7 @@ "CANCELED": "Canceled" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -2110,6 +2118,9 @@ "FINALIZED": "Finalized", "CANCELED": "Canceled" }, + "DESCRIPTION-STATUS-AVAILABLE-ACTION-TYPE": { + "EXPORT": "Export" + }, "PLAN-USER-ROLE": { "OWNER": "Owner", "VIEWER": "Viewer", @@ -2123,6 +2134,10 @@ "FINALISED": "Završeno", "DRAFT": "Nacrt" }, + "PLAN-STATUS-AVAILABLE-ACTION-TYPE": { + "DEPOSIT": "Deposit", + "EXPORT": "Export" + }, "PLAN-VISIBILITY": { "PUBLIC": "Objavljeno" }, @@ -2247,11 +2262,13 @@ "PLAN-INVITATION-EXISTING-USER": "Plan Invitation Existing User", "PLAN-MODIFIED": "Plan Modified", "PLAN-FINALISED": "Plan Finalised", + "PLAN-STATUS-CHANGED": "Plan Status Changed", "PLAN-ANNOTATION-CREATED": "Plan Annotation Created", "PLAN-ANNOTATION-STATUS-CHANGED": "Plan Annotation Status Changed", "DESCRIPTION-CREATED": "Description Created", "DESCRIPTION-MODIFIED": "Description Modified", "DESCRIPTION-FINALISED": "Description Finalised", + "DESCRIPTION-STATUS-CHANGED": "Description Status Changed", "DESCRIPTION-ANNOTATION-CREATED": "Description Annotation Created", "DESCRIPTION-ANNOTATION-STATUS-CHANGED": "Description Annotation Status Changed", "MERGE-ACCOUNT-CONFIRMATION": "Merge Account Confirmation", diff --git a/frontend/src/assets/i18n/pl.json b/frontend/src/assets/i18n/pl.json index b915f0e04..5edb26acc 100644 --- a/frontend/src/assets/i18n/pl.json +++ b/frontend/src/assets/i18n/pl.json @@ -773,6 +773,7 @@ "NO-ITEMS-FOUND": "Your search didn't match any items.", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPE": { "ANY": "Any", "PUBLIC": "Published", @@ -780,6 +781,7 @@ "DRAFT": "Draft" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -912,6 +914,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -919,7 +922,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -954,6 +958,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -961,7 +966,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -1066,6 +1072,7 @@ "RESET-FILTERS": "Reset", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPES": { "ANY": "Any", "DRAFT": "Draft", @@ -1073,6 +1080,7 @@ "CANCELED": "Canceled" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -2110,6 +2118,9 @@ "FINALIZED": "Finalized", "CANCELED": "Canceled" }, + "DESCRIPTION-STATUS-AVAILABLE-ACTION-TYPE": { + "EXPORT": "Export" + }, "PLAN-USER-ROLE": { "OWNER": "Owner", "VIEWER": "Viewer", @@ -2123,6 +2134,10 @@ "FINALISED": "Sfinalizowane", "DRAFT": "Wersja robocza" }, + "PLAN-STATUS-AVAILABLE-ACTION-TYPE": { + "DEPOSIT": "Deposit", + "EXPORT": "Export" + }, "PLAN-VISIBILITY": { "PUBLIC": "Opublikowane" }, @@ -2247,11 +2262,13 @@ "PLAN-INVITATION-EXISTING-USER": "Plan Invitation Existing User", "PLAN-MODIFIED": "Plan Modified", "PLAN-FINALISED": "Plan Finalised", + "PLAN-STATUS-CHANGED": "Plan Status Changed", "PLAN-ANNOTATION-CREATED": "Plan Annotation Created", "PLAN-ANNOTATION-STATUS-CHANGED": "Plan Annotation Status Changed", "DESCRIPTION-CREATED": "Description Created", "DESCRIPTION-MODIFIED": "Description Modified", "DESCRIPTION-FINALISED": "Description Finalised", + "DESCRIPTION-STATUS-CHANGED": "Description Status Changed", "DESCRIPTION-ANNOTATION-CREATED": "Description Annotation Created", "DESCRIPTION-ANNOTATION-STATUS-CHANGED": "Description Annotation Status Changed", "MERGE-ACCOUNT-CONFIRMATION": "Merge Account Confirmation", diff --git a/frontend/src/assets/i18n/pt.json b/frontend/src/assets/i18n/pt.json index b469413c5..25cd8afaa 100644 --- a/frontend/src/assets/i18n/pt.json +++ b/frontend/src/assets/i18n/pt.json @@ -773,6 +773,7 @@ "NO-ITEMS-FOUND": "Your search didn't match any items.", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPE": { "ANY": "Any", "PUBLIC": "Published", @@ -780,6 +781,7 @@ "DRAFT": "Draft" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -912,6 +914,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -919,7 +922,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -954,6 +958,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -961,7 +966,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -1066,6 +1072,7 @@ "RESET-FILTERS": "Reset", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPES": { "ANY": "Any", "DRAFT": "Draft", @@ -1073,6 +1080,7 @@ "CANCELED": "Canceled" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -2110,6 +2118,9 @@ "FINALIZED": "Finalized", "CANCELED": "Canceled" }, + "DESCRIPTION-STATUS-AVAILABLE-ACTION-TYPE": { + "EXPORT": "Export" + }, "PLAN-USER-ROLE": { "OWNER": "Owner", "VIEWER": "Viewer", @@ -2123,6 +2134,10 @@ "FINALISED": "Concluído", "DRAFT": "Rascunho" }, + "PLAN-STATUS-AVAILABLE-ACTION-TYPE": { + "DEPOSIT": "Deposit", + "EXPORT": "Export" + }, "PLAN-VISIBILITY": { "PUBLIC": "Publicado" }, @@ -2247,11 +2262,13 @@ "PLAN-INVITATION-EXISTING-USER": "Plan Invitation Existing User", "PLAN-MODIFIED": "Plan Modified", "PLAN-FINALISED": "Plan Finalised", + "PLAN-STATUS-CHANGED": "Plan Status Changed", "PLAN-ANNOTATION-CREATED": "Plan Annotation Created", "PLAN-ANNOTATION-STATUS-CHANGED": "Plan Annotation Status Changed", "DESCRIPTION-CREATED": "Description Created", "DESCRIPTION-MODIFIED": "Description Modified", "DESCRIPTION-FINALISED": "Description Finalised", + "DESCRIPTION-STATUS-CHANGED": "Description Status Changed", "DESCRIPTION-ANNOTATION-CREATED": "Description Annotation Created", "DESCRIPTION-ANNOTATION-STATUS-CHANGED": "Description Annotation Status Changed", "MERGE-ACCOUNT-CONFIRMATION": "Merge Account Confirmation", diff --git a/frontend/src/assets/i18n/sk.json b/frontend/src/assets/i18n/sk.json index 6fd7dbf93..48dab0b5d 100644 --- a/frontend/src/assets/i18n/sk.json +++ b/frontend/src/assets/i18n/sk.json @@ -773,6 +773,7 @@ "NO-ITEMS-FOUND": "Your search didn't match any items.", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPE": { "ANY": "Any", "PUBLIC": "Published", @@ -780,6 +781,7 @@ "DRAFT": "Draft" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -912,6 +914,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -919,7 +922,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -954,6 +958,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -961,7 +966,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -1066,6 +1072,7 @@ "RESET-FILTERS": "Reset", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPES": { "ANY": "Any", "DRAFT": "Draft", @@ -1073,6 +1080,7 @@ "CANCELED": "Canceled" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -2110,6 +2118,9 @@ "FINALIZED": "Finalized", "CANCELED": "Canceled" }, + "DESCRIPTION-STATUS-AVAILABLE-ACTION-TYPE": { + "EXPORT": "Export" + }, "PLAN-USER-ROLE": { "OWNER": "Owner", "VIEWER": "Viewer", @@ -2123,6 +2134,10 @@ "FINALISED": "Dokončené", "DRAFT": "Návrh" }, + "PLAN-STATUS-AVAILABLE-ACTION-TYPE": { + "DEPOSIT": "Deposit", + "EXPORT": "Export" + }, "PLAN-VISIBILITY": { "PUBLIC": "Publikované" }, @@ -2247,11 +2262,13 @@ "PLAN-INVITATION-EXISTING-USER": "Plan Invitation Existing User", "PLAN-MODIFIED": "Plan Modified", "PLAN-FINALISED": "Plan Finalised", + "PLAN-STATUS-CHANGED": "Plan Status Changed", "PLAN-ANNOTATION-CREATED": "Plan Annotation Created", "PLAN-ANNOTATION-STATUS-CHANGED": "Plan Annotation Status Changed", "DESCRIPTION-CREATED": "Description Created", "DESCRIPTION-MODIFIED": "Description Modified", "DESCRIPTION-FINALISED": "Description Finalised", + "DESCRIPTION-STATUS-CHANGED": "Description Status Changed", "DESCRIPTION-ANNOTATION-CREATED": "Description Annotation Created", "DESCRIPTION-ANNOTATION-STATUS-CHANGED": "Description Annotation Status Changed", "MERGE-ACCOUNT-CONFIRMATION": "Merge Account Confirmation", diff --git a/frontend/src/assets/i18n/sr.json b/frontend/src/assets/i18n/sr.json index d42328a7f..9d5b14196 100644 --- a/frontend/src/assets/i18n/sr.json +++ b/frontend/src/assets/i18n/sr.json @@ -773,6 +773,7 @@ "NO-ITEMS-FOUND": "Your search didn't match any items.", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPE": { "ANY": "Any", "PUBLIC": "Published", @@ -780,6 +781,7 @@ "DRAFT": "Draft" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -912,6 +914,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -919,7 +922,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -954,6 +958,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -961,7 +966,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -1066,6 +1072,7 @@ "RESET-FILTERS": "Reset", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPES": { "ANY": "Any", "DRAFT": "Draft", @@ -1073,6 +1080,7 @@ "CANCELED": "Canceled" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -2110,6 +2118,9 @@ "FINALIZED": "Finalized", "CANCELED": "Canceled" }, + "DESCRIPTION-STATUS-AVAILABLE-ACTION-TYPE": { + "EXPORT": "Export" + }, "PLAN-USER-ROLE": { "OWNER": "Owner", "VIEWER": "Viewer", @@ -2123,6 +2134,10 @@ "FINALISED": "Završeno", "DRAFT": "Radna verzija" }, + "PLAN-STATUS-AVAILABLE-ACTION-TYPE": { + "DEPOSIT": "Deposit", + "EXPORT": "Export" + }, "PLAN-VISIBILITY": { "PUBLIC": "Objavljeno" }, @@ -2247,11 +2262,13 @@ "PLAN-INVITATION-EXISTING-USER": "Plan Invitation Existing User", "PLAN-MODIFIED": "Plan Modified", "PLAN-FINALISED": "Plan Finalised", + "PLAN-STATUS-CHANGED": "Plan Status Changed", "PLAN-ANNOTATION-CREATED": "Plan Annotation Created", "PLAN-ANNOTATION-STATUS-CHANGED": "Plan Annotation Status Changed", "DESCRIPTION-CREATED": "Description Created", "DESCRIPTION-MODIFIED": "Description Modified", "DESCRIPTION-FINALISED": "Description Finalised", + "DESCRIPTION-STATUS-CHANGED": "Description Status Changed", "DESCRIPTION-ANNOTATION-CREATED": "Description Annotation Created", "DESCRIPTION-ANNOTATION-STATUS-CHANGED": "Description Annotation Status Changed", "MERGE-ACCOUNT-CONFIRMATION": "Merge Account Confirmation", diff --git a/frontend/src/assets/i18n/tr.json b/frontend/src/assets/i18n/tr.json index 1fb1c8ee0..07a54e030 100644 --- a/frontend/src/assets/i18n/tr.json +++ b/frontend/src/assets/i18n/tr.json @@ -773,6 +773,7 @@ "NO-ITEMS-FOUND": "Your search didn't match any items.", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPE": { "ANY": "Any", "PUBLIC": "Published", @@ -780,6 +781,7 @@ "DRAFT": "Draft" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -912,6 +914,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -919,7 +922,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -954,6 +958,7 @@ "FIELDS": { "NAME": "Name", "DESCRIPTION": "Description", + "ACTION": "Action", "DEFINITION": "Definition", "INTERNAL-STATUS": "Internal Status", "AUTHORIZATION": "Authorization", @@ -961,7 +966,8 @@ "ALLOW-AUTHENTICATED": "Allow authenticated users", "ALLOW-ANONYMOUS": "Allow anonymous users", "ROLES": "User roles", - "PLAN-ROLES": "User plan roles" + "PLAN-ROLES": "User plan roles", + "AVAILABLE-ACTIONS": "Available Actions" }, "DEFAULT-VALUE": { "NONE": "None" @@ -1066,6 +1072,7 @@ "RESET-FILTERS": "Reset", "STATUS": { "NAME": "Status", + "PLACEHOLDER":"Select Status", "TYPES": { "ANY": "Any", "DRAFT": "Draft", @@ -1073,6 +1080,7 @@ "CANCELED": "Canceled" } }, + "IS-ACTIVE": "Active Filter", "RELATED-TENANT": { "NAME": "Related Tenant", "FILTER-BY-TENANT": "Filter by tenant" @@ -2110,6 +2118,9 @@ "FINALIZED": "Finalized", "CANCELED": "Canceled" }, + "DESCRIPTION-STATUS-AVAILABLE-ACTION-TYPE": { + "EXPORT": "Export" + }, "PLAN-USER-ROLE": { "OWNER": "Owner", "VIEWER": "Viewer", @@ -2123,6 +2134,10 @@ "FINALISED": "Tamamlandı", "DRAFT": "Taslak" }, + "PLAN-STATUS-AVAILABLE-ACTION-TYPE": { + "DEPOSIT": "Deposit", + "EXPORT": "Export" + }, "PLAN-VISIBILITY": { "PUBLIC": "Yayınlandı" }, @@ -2247,11 +2262,13 @@ "PLAN-INVITATION-EXISTING-USER": "Plan Invitation Existing User", "PLAN-MODIFIED": "Plan Modified", "PLAN-FINALISED": "Plan Finalised", + "PLAN-STATUS-CHANGED": "Plan Status Changed", "PLAN-ANNOTATION-CREATED": "Plan Annotation Created", "PLAN-ANNOTATION-STATUS-CHANGED": "Plan Annotation Status Changed", "DESCRIPTION-CREATED": "Description Created", "DESCRIPTION-MODIFIED": "Description Modified", "DESCRIPTION-FINALISED": "Description Finalised", + "DESCRIPTION-STATUS-CHANGED": "Description Status Changed", "DESCRIPTION-ANNOTATION-CREATED": "Description Annotation Created", "DESCRIPTION-ANNOTATION-STATUS-CHANGED": "Description Annotation Status Changed", "MERGE-ACCOUNT-CONFIRMATION": "Merge Account Confirmation", diff --git a/frontend/src/notification-service/core/enum/notification-type.enum.ts b/frontend/src/notification-service/core/enum/notification-type.enum.ts index 64123d82d..7b4489ae1 100644 --- a/frontend/src/notification-service/core/enum/notification-type.enum.ts +++ b/frontend/src/notification-service/core/enum/notification-type.enum.ts @@ -4,12 +4,14 @@ export enum NotificationType { descriptionCreatedType = '8965b1d5-99a6-4acf-9016-c0d0ce341364', planModifiedType = '4542262a-22f8-4baa-9db6-1c8e70ac1dbb', planFinalisedType = '90db0b46-42de-bd89-aebf-6f27efeb256e', + planStatusChangedType = "25bbc307-4fa8-4381-b2d7-a3395f57d575", planAnnotationCreatedType = '1cca80f5-2ea9-41ae-a204-9b4332216c24', planAnnotationStatusChangedType = '0c8a5c62-e48f-4eca-99ee-a7f262477061', descriptionAnnotationCreatedType = 'db1e99d2-a240-4e75-9bb2-ef25b234c1f0', descriptionAnnotationStatusChangedType = '3189e3a6-91e6-40c6-8ff8-275a68445aec', descriptionModifiedType = '4fdbfa80-7a71-4a69-b854-67cbb70648f1', descriptionFinalisedType = '33790bad-94d4-488a-8ee2-7f6295ca18ea', + descriptionStatusChangedType = "8389aca8-43bd-4aac-958d-24daf6ed47b4", mergeAccountConfirmationType = 'bfe68845-cb05-4c5a-a03d-29161a7c9660', removeCredentialConfirmationType = 'c9bc3f16-057e-4bba-8a5f-36bd835e5604', planDepositType = '55736f7a-83ab-4190-af43-9d031a6f9612', diff --git a/frontend/src/notification-service/core/formatting/enum-utils.service.ts b/frontend/src/notification-service/core/formatting/enum-utils.service.ts index 5d822fb51..cf5d91e6d 100644 --- a/frontend/src/notification-service/core/formatting/enum-utils.service.ts +++ b/frontend/src/notification-service/core/formatting/enum-utils.service.ts @@ -32,11 +32,13 @@ export class NotificationServiceEnumUtils extends BaseEnumUtilsService { case NotificationType.planInvitationExistingUserType: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-NOTIFICATION-TYPE.PLAN-INVITATION-EXISTING-USER'); case NotificationType.planModifiedType: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-NOTIFICATION-TYPE.PLAN-MODIFIED'); case NotificationType.planFinalisedType: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-NOTIFICATION-TYPE.PLAN-FINALISED'); + case NotificationType.planStatusChangedType: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-NOTIFICATION-TYPE.PLAN-STATUS-CHANGED'); case NotificationType.planAnnotationCreatedType: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-NOTIFICATION-TYPE.PLAN-ANNOTATION-CREATED'); case NotificationType.planAnnotationStatusChangedType: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-NOTIFICATION-TYPE.PLAN-ANNOTATION-STATUS-CHANGED'); case NotificationType.descriptionCreatedType: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-NOTIFICATION-TYPE.DESCRIPTION-CREATED'); case NotificationType.descriptionModifiedType: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-NOTIFICATION-TYPE.DESCRIPTION-MODIFIED'); case NotificationType.descriptionFinalisedType: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-NOTIFICATION-TYPE.DESCRIPTION-FINALISED'); + case NotificationType.descriptionStatusChangedType: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-NOTIFICATION-TYPE.DESCRIPTION-STATUS-CHANGED'); case NotificationType.descriptionAnnotationCreatedType: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-NOTIFICATION-TYPE.DESCRIPTION-ANNOTATION-CREATED'); case NotificationType.descriptionAnnotationStatusChangedType: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-NOTIFICATION-TYPE.DESCRIPTION-ANNOTATION-STATUS-CHANGED'); case NotificationType.mergeAccountConfirmationType: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-NOTIFICATION-TYPE.MERGE-ACCOUNT-CONFIRMATION');