diff --git a/backend/core/src/main/java/org/opencdmp/model/builder/UserAdditionalInfoBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/UserAdditionalInfoBuilder.java index 27d5e0f89..e81e38102 100644 --- a/backend/core/src/main/java/org/opencdmp/model/builder/UserAdditionalInfoBuilder.java +++ b/backend/core/src/main/java/org/opencdmp/model/builder/UserAdditionalInfoBuilder.java @@ -53,7 +53,7 @@ public class UserAdditionalInfoBuilder extends BaseBuilder(); - FieldSet referenceFields = fields.extractPrefixed(this.asPrefix(DescriptionReference._reference)); + FieldSet referenceFields = fields.extractPrefixed(this.asPrefix(UserAdditionalInfo._organization)); Map referenceItemsMap = this.collectReferences(referenceFields, data); List models = new ArrayList<>(); diff --git a/backend/core/src/main/java/org/opencdmp/model/persist/UserAdditionalInfoPersist.java b/backend/core/src/main/java/org/opencdmp/model/persist/UserAdditionalInfoPersist.java index 76d34e882..24a89f6d6 100644 --- a/backend/core/src/main/java/org/opencdmp/model/persist/UserAdditionalInfoPersist.java +++ b/backend/core/src/main/java/org/opencdmp/model/persist/UserAdditionalInfoPersist.java @@ -1,5 +1,6 @@ package org.opencdmp.model.persist; +import gr.cite.tools.validation.ValidatorFactory; import org.opencdmp.commons.validation.BaseValidator; import gr.cite.tools.validation.specification.Specification; import org.opencdmp.convention.ConventionService; @@ -12,7 +13,6 @@ import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; -import java.util.UUID; public class UserAdditionalInfoPersist { @@ -32,9 +32,9 @@ public class UserAdditionalInfoPersist { private String roleOrganization; - private UUID organizationId; + private ReferencePersist organization; - public static final String _organizationId = "organizationId"; + public static final String _organization = "organization"; public String getAvatarUrl() { return avatarUrl; @@ -76,12 +76,12 @@ public class UserAdditionalInfoPersist { this.roleOrganization = roleOrganization; } - public UUID getOrganizationId() { - return organizationId; + public ReferencePersist getOrganization() { + return organization; } - public void setOrganizationId(UUID organizationId) { - this.organizationId = organizationId; + public void setOrganization(ReferencePersist organization) { + this.organization = organization; } @Component(UserAdditionalInfoPersistValidator.ValidatorName) @@ -91,10 +91,12 @@ public class UserAdditionalInfoPersist { public static final String ValidatorName = "UserAdditionalInfoPersistValidator"; private final MessageSource messageSource; + private final ValidatorFactory validatorFactory; - protected UserAdditionalInfoPersistValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) { + protected UserAdditionalInfoPersistValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource, ValidatorFactory validatorFactory) { super(conventionService, errors); this.messageSource = messageSource; + this.validatorFactory = validatorFactory; } @Override @@ -113,7 +115,12 @@ public class UserAdditionalInfoPersist { .failOn(UserAdditionalInfoPersist._culture).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserAdditionalInfoPersist._culture}, LocaleContextHolder.getLocale())), this.spec() .must(() -> !this.isEmpty(item.getLanguage())) - .failOn(UserAdditionalInfoPersist._language).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserAdditionalInfoPersist._language}, LocaleContextHolder.getLocale())) + .failOn(UserAdditionalInfoPersist._language).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserAdditionalInfoPersist._language}, LocaleContextHolder.getLocale())), + this.refSpec() + .iff(() -> !this.isNull(item.getOrganization())) + .on(UserAdditionalInfoPersist._organization) + .over(item.getOrganization()) + .using(() -> this.validatorFactory.validator(ReferencePersist.ReferenceWithoutTypePersistValidator.class)) ); } } diff --git a/backend/core/src/main/java/org/opencdmp/service/user/UserServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/user/UserServiceImpl.java index c4a865f19..c3139f5f4 100644 --- a/backend/core/src/main/java/org/opencdmp/service/user/UserServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/user/UserServiceImpl.java @@ -26,10 +26,7 @@ import org.opencdmp.authorization.OwnedResource; import org.opencdmp.authorization.Permission; import org.opencdmp.commons.JsonHandlingService; import org.opencdmp.commons.XmlHandlingService; -import org.opencdmp.commons.enums.ActionConfirmationStatus; -import org.opencdmp.commons.enums.ActionConfirmationType; -import org.opencdmp.commons.enums.ContactInfoType; -import org.opencdmp.commons.enums.IsActive; +import org.opencdmp.commons.enums.*; import org.opencdmp.commons.enums.notification.NotificationContactType; import org.opencdmp.commons.notification.NotificationProperties; import org.opencdmp.commons.scope.tenant.TenantScope; @@ -37,6 +34,7 @@ import org.opencdmp.commons.scope.user.UserScope; import org.opencdmp.commons.types.actionconfirmation.MergeAccountConfirmationEntity; import org.opencdmp.commons.types.actionconfirmation.RemoveCredentialRequestEntity; import org.opencdmp.commons.types.notification.*; +import org.opencdmp.commons.types.reference.DefinitionEntity; import org.opencdmp.commons.types.user.AdditionalInfoEntity; import org.opencdmp.commons.types.usercredential.UserCredentialDataEntity; import org.opencdmp.convention.ConventionService; @@ -54,6 +52,9 @@ import org.opencdmp.model.deleter.*; import org.opencdmp.model.persist.*; import org.opencdmp.model.persist.actionconfirmation.MergeAccountConfirmationPersist; import org.opencdmp.model.persist.actionconfirmation.RemoveCredentialRequestPersist; +import org.opencdmp.model.persist.referencedefinition.DefinitionPersist; +import org.opencdmp.model.reference.Reference; +import org.opencdmp.model.referencetype.ReferenceType; import org.opencdmp.model.user.User; import org.opencdmp.model.usercredential.UserCredential; import org.opencdmp.query.*; @@ -187,10 +188,13 @@ public class UserServiceImpl implements UserService { return this.builderFactory.builder(UserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, User._id), data); } - private @NotNull AdditionalInfoEntity buildAdditionalInfoEntity(UserAdditionalInfoPersist persist){ + private @NotNull AdditionalInfoEntity buildAdditionalInfoEntity(UserAdditionalInfoPersist persist) throws InvalidApplicationException { AdditionalInfoEntity data = new AdditionalInfoEntity(); if (persist == null) return data; - data.setOrganizationId(persist.getOrganizationId()); + if (persist.getOrganization() != null) { + ReferenceEntity organization = this.buildReferenceEntity(persist.getOrganization()); + data.setOrganizationId(organization != null ? organization.getId() : null); + } data.setRoleOrganization(persist.getRoleOrganization()); data.setCulture(persist.getCulture()); data.setTimezone(persist.getTimezone()); @@ -199,6 +203,73 @@ public class UserServiceImpl implements UserService { return data; } + private @NotNull ReferenceEntity buildReferenceEntity(ReferencePersist model) throws InvalidApplicationException { + + ReferenceEntity referenceEntity = null; + if (this.conventionService.isValidGuid(model.getId())) { + referenceEntity = this.entityManager.find(ReferenceEntity.class, model.getId()); + if (referenceEntity == null) + throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Reference.class.getSimpleName()}, LocaleContextHolder.getLocale())); + } else { + referenceEntity = this.queryFactory.query(ReferenceQuery.class).sourceTypes(model.getSourceType()).typeIds(model.getTypeId()).sources(model.getSource()).isActive(IsActive.Active).references(model.getReference()).first(); + if (referenceEntity == null) { + referenceEntity = new ReferenceEntity(); + referenceEntity.setId(UUID.randomUUID()); + referenceEntity.setLabel(model.getLabel()); + referenceEntity.setIsActive(IsActive.Active); + referenceEntity.setCreatedAt(Instant.now()); + referenceEntity.setTypeId(model.getTypeId()); + + referenceEntity.setDefinition(this.xmlHandlingService.toXmlSafe(this.buildDefinitionEntity(model.getDefinition()))); + referenceEntity.setUpdatedAt(Instant.now()); + referenceEntity.setReference(model.getReference()); + referenceEntity.setAbbreviation(model.getAbbreviation()); + referenceEntity.setSource(model.getSource()); + referenceEntity.setSourceType(model.getSourceType()); + try { + ReferenceTypeEntity referenceType = this.queryFactory.query(ReferenceTypeQuery.class).ids(model.getTypeId()).firstAs(new BaseFieldSet().ensure(ReferenceType._id).ensure(ReferenceTypeEntity._tenantId)); + if (referenceType == null) + throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getTypeId(), ReferenceType.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + if (referenceEntity.getSourceType().equals(ReferenceSourceType.External) && !this.tenantScope.isDefaultTenant() && referenceType.getTenantId() == null) { + this.tenantScope.setTempTenant(this.entityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode()); + } + this.entityManager.persist(referenceEntity); + } finally { + this.tenantScope.removeTempTenant(this.entityManager.getEntityManager()); + } + } + } + + this.entityManager.flush(); + + return referenceEntity; + } + + private @NotNull DefinitionEntity buildDefinitionEntity(DefinitionPersist persist) { + DefinitionEntity data = new DefinitionEntity(); + if (persist == null) return data; + if (!this.conventionService.isListNullOrEmpty(persist.getFields())) { + data.setFields(new ArrayList<>()); + for (org.opencdmp.model.persist.referencedefinition.FieldPersist fieldPersist : persist.getFields()) { + data.getFields().add(this.buildFieldEntity(fieldPersist)); + } + } + + return data; + } + + private @NotNull org.opencdmp.commons.types.reference.FieldEntity buildFieldEntity(org.opencdmp.model.persist.referencedefinition.FieldPersist persist) { + org.opencdmp.commons.types.reference.FieldEntity data = new org.opencdmp.commons.types.reference.FieldEntity(); + if (persist == null) return data; + + data.setCode(persist.getCode()); + data.setDataType(persist.getDataType()); + data.setCode(persist.getCode()); + + return data; + } + //endregion //region delete diff --git a/dmp-frontend/src/app/core/model/user/user.ts b/dmp-frontend/src/app/core/model/user/user.ts index fa39e12cf..7e6b6be34 100644 --- a/dmp-frontend/src/app/core/model/user/user.ts +++ b/dmp-frontend/src/app/core/model/user/user.ts @@ -1,7 +1,7 @@ import { RoleOrganizationType } from "@app/core/common/enum/role-organization-type"; import { BaseEntity, BaseEntityPersist } from "@common/base/base-entity.model"; import { Guid } from "@common/types/guid"; -import { Reference } from "../reference/reference"; +import { Reference, ReferencePersist } from "../reference/reference"; import { ContactInfoType } from "@notification-service/core/enum/contact-info-type.enum"; export interface User extends BaseEntity { @@ -24,7 +24,7 @@ export interface UserAdditionalInfoPersist { culture: String; language: String; roleOrganization: String; - organizationId: Guid; + organization: ReferencePersist; } export interface UserAdditionalInfo { diff --git a/dmp-frontend/src/app/core/services/configuration/configuration.service.ts b/dmp-frontend/src/app/core/services/configuration/configuration.service.ts index 43863854a..e19029555 100644 --- a/dmp-frontend/src/app/core/services/configuration/configuration.service.ts +++ b/dmp-frontend/src/app/core/services/configuration/configuration.service.ts @@ -154,6 +154,21 @@ export class ConfigurationService extends BaseComponent { return this._authProviders; } + private _researcherId: any; + get researcherId(): boolean { + return this._researcherId; + } + + private _grantId: any; + get grantId(): boolean { + return this._grantId; + } + + private _organizationId: any; + get organizationId(): boolean { + return this._organizationId; + } + public loadConfiguration(): Promise { return new Promise((r, e) => { // We need to exclude all interceptors here, for the initial configuration request. @@ -224,6 +239,10 @@ export class ConfigurationService extends BaseComponent { this._newReleaseNotificationLink = config.newReleaseNotification?.link; this._newReleaseNotificationVersionCode = config.newReleaseNotification?.versionCode; this._authProviders = AuthProviders.parseValue(config.authProviders); + this._researcherId = config.referenceTypes.researcherId; + this._grantId = config.referenceTypes.grantId; + this._organizationId = config.referenceTypes.organizationId; + } } diff --git a/dmp-frontend/src/app/core/services/reference-type/reference-type.service.ts b/dmp-frontend/src/app/core/services/reference-type/reference-type.service.ts index 100a0a5bd..d4c22fd73 100644 --- a/dmp-frontend/src/app/core/services/reference-type/reference-type.service.ts +++ b/dmp-frontend/src/app/core/services/reference-type/reference-type.service.ts @@ -131,14 +131,14 @@ export class ReferenceTypeService { } public getResearcherReferenceType(): any { - return '5a2112e7-ea99-4cfe-98a1-68665e26726e'; + return this.configurationService.researcherId; } public getGrantReferenceType(): any { - return '5b9c284f-f041-4995-96cc-fad7ad13289c'; + return this.configurationService.grantId; } public getOrganizationReferenceType(): any { - return '7eeffb98-58fb-4921-82ec-e27f32f8e738'; + return this.configurationService.organizationId; } } \ No newline at end of file diff --git a/dmp-frontend/src/app/ui/user-profile/user-profile-editor.model.ts b/dmp-frontend/src/app/ui/user-profile/user-profile-editor.model.ts index bb0d15c26..0e06a9b65 100644 --- a/dmp-frontend/src/app/ui/user-profile/user-profile-editor.model.ts +++ b/dmp-frontend/src/app/ui/user-profile/user-profile-editor.model.ts @@ -1,6 +1,6 @@ import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { RoleOrganizationType } from '@app/core/common/enum/role-organization-type'; -import { Reference } from '@app/core/model/reference/reference'; +import { ReferencePersist } from '@app/core/model/reference/reference'; import { User, UserAdditionalInfo, UserAdditionalInfoPersist, UserPersist } from '@app/core/model/user/user'; import { BaseEditorModel } from '@common/base/base-form-editor-model'; import { BackendErrorValidator } from '@common/forms/validation/custom-validator'; @@ -77,7 +77,7 @@ export class UserAdditionalInfoEditorModel implements UserAdditionalInfoPersist culture: String; language: String; roleOrganization: RoleOrganizationType; - organizationId: Guid; + organization: ReferencePersist; protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); @@ -92,7 +92,19 @@ export class UserAdditionalInfoEditorModel implements UserAdditionalInfoPersist this.culture = item.culture; this.language = item.language; this.roleOrganization = item.roleOrganization - this.organizationId = item.organization?.id + if (item.organization){ + this.organization = { + id: item.organization.id, + label: item.organization.label, + reference: item.organization.reference, + source: item.organization.source, + typeId: item.organization.type.id, + description: item.organization.source, + definition: item.organization.definition, + abbreviation: item.organization.abbreviation, + sourceType: item.organization.sourceType + } + } } return this; @@ -118,7 +130,7 @@ export class UserAdditionalInfoEditorModel implements UserAdditionalInfoPersist culture: [{ value: this.culture, disabled: disabled }, context.getValidation('culture').validators], language: [{ value: this.language ? availableLanguages.filter(x => x === this.language).pop() : '', disabled: disabled }, context.getValidation('language').validators], roleOrganization: [{ value: this.roleOrganization, disabled: disabled }, context.getValidation('roleOrganization').validators], - organizationId: [{ value: this.organizationId, disabled: disabled }, context.getValidation('organizationId').validators], + organization: [{ value: this.organization, disabled: disabled }, context.getValidation('organization').validators], }); } @@ -135,7 +147,7 @@ export class UserAdditionalInfoEditorModel implements UserAdditionalInfoPersist baseValidationArray.push({ key: 'culture', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}culture`)] }); baseValidationArray.push({ key: 'language', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}language`)] }); baseValidationArray.push({ key: 'roleOrganization', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}roleOrganization`)] }); - baseValidationArray.push({ key: 'organizationId', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}organizationId`)] }); + baseValidationArray.push({ key: 'organization', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}organization`)] }); baseContext.validation = baseValidationArray; return baseContext; @@ -152,7 +164,7 @@ export class UserAdditionalInfoEditorModel implements UserAdditionalInfoPersist validationErrorModel }); - ['avatarUrl', 'timezone', 'culture', 'language', 'roleOrganization', 'organizationId'].forEach(keyField => { + ['avatarUrl', 'timezone', 'culture', 'language', 'roleOrganization', 'organization'].forEach(keyField => { const control = formGroup?.get(keyField); control?.clearValidators(); control?.addValidators(context.getValidation(keyField).validators); diff --git a/dmp-frontend/src/app/ui/user-profile/user-profile.component.html b/dmp-frontend/src/app/ui/user-profile/user-profile.component.html index e5b1353a4..701b5d4ce 100644 --- a/dmp-frontend/src/app/ui/user-profile/user-profile.component.html +++ b/dmp-frontend/src/app/ui/user-profile/user-profile.component.html @@ -111,7 +111,7 @@
- +
diff --git a/dmp-frontend/src/app/ui/user-profile/user-profile.component.ts b/dmp-frontend/src/app/ui/user-profile/user-profile.component.ts index 14dac24ba..0e9893d41 100644 --- a/dmp-frontend/src/app/ui/user-profile/user-profile.component.ts +++ b/dmp-frontend/src/app/ui/user-profile/user-profile.component.ts @@ -6,7 +6,7 @@ import { ActivatedRoute, Params, Router } from '@angular/router'; import { RoleOrganizationType } from '@app/core/common/enum/role-organization-type'; import { CultureInfo } from '@app/core/model/culture-info'; import { Reference } from '@app/core/model/reference/reference'; -import { User, UserCredential, UserPersist } from '@app/core/model/user/user'; +import { User, UserAdditionalInfo, UserCredential, UserPersist } from '@app/core/model/user/user'; import { AuthService } from '@app/core/services/auth/auth.service'; import { ConfigurationService } from '@app/core/services/configuration/configuration.service'; import { CultureService } from '@app/core/services/culture/culture-service'; @@ -37,6 +37,7 @@ import { ReferenceService } from '@app/core/services/reference/reference.service import { ReferenceTypeService } from '@app/core/services/reference-type/reference-type.service'; import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration'; import { ReferenceSourceType } from '@app/core/common/enum/reference-source-type'; +import { ReferenceType } from '@app/core/model/reference-type/reference-type'; @Component({ selector: 'app-user-profile', @@ -147,7 +148,11 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes nameof(x => x.additionalInfo.language), nameof(x => x.additionalInfo.timezone), nameof(x => x.additionalInfo.culture), - nameof(x => x.additionalInfo.organization), + [nameof(x => x.additionalInfo), nameof(x => x.organization), nameof(x => x.id)].join('.'), + [nameof(x => x.additionalInfo), nameof(x => x.organization), nameof(x => x.label)].join('.'), + [nameof(x => x.additionalInfo), nameof(x => x.organization), nameof(x => x.type), nameof(x => x.id)].join('.'), + [nameof(x => x.additionalInfo), nameof(x => x.organization), nameof(x => x.reference)].join('.'), + [nameof(x => x.additionalInfo), nameof(x => x.organization), nameof(x => x.isActive)].join('.'), nameof(x => x.additionalInfo.roleOrganization), nameof(x => x.createdAt), nameof(x => x.updatedAt), @@ -242,6 +247,7 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes return; } const formData = this.formService.getValue(this.formGroup.value) as UserPersist; + formData.additionalInfo.organization.typeId = Guid.parse(this.referenceTypeService.getOrganizationReferenceType()); this.userService.persist(formData) .pipe(takeUntil(this._destroyed)) .subscribe( diff --git a/dmp-frontend/src/assets/config/config.json b/dmp-frontend/src/assets/config/config.json index f43a62736..83be8f664 100644 --- a/dmp-frontend/src/assets/config/config.json +++ b/dmp-frontend/src/assets/config/config.json @@ -65,5 +65,10 @@ "cultures": ["en"] } ] + }, + "referenceTypes": { + "researcherId": "5a2112e7-ea99-4cfe-98a1-68665e26726e", + "grantId": "5b9c284f-f041-4995-96cc-fad7ad13289c", + "organizationId": "7eeffb98-58fb-4921-82ec-e27f32f8e738" } }