+}
diff --git a/projects/admin/src/app/migration/conversion/record/brief-view/migration-data/migration-data.component.ts b/projects/admin/src/app/migration/conversion/record/brief-view/migration-data/migration-data.component.ts
new file mode 100644
index 000000000..76ed9aa67
--- /dev/null
+++ b/projects/admin/src/app/migration/conversion/record/brief-view/migration-data/migration-data.component.ts
@@ -0,0 +1,38 @@
+/*
+ * RERO ILS UI
+ * Copyright (C) 2024 RERO
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { Component, Input } from '@angular/core';
+
+@Component({
+ selector: 'admin-migration-data',
+ templateUrl: './migration-data.component.html',
+})
+export class MigrationDataBriefComponent {
+ // current record
+ @Input() record: any;
+
+ // detail URL
+ @Input() detailUrl: { link: string; external: boolean };
+
+ /**
+ * Get the conversion status.
+ */
+ get status() {
+ return this.record?.metadata?.conversion?.status;
+ }
+
+}
diff --git a/projects/admin/src/app/migration/conversion/record/detail-view/migration-data/migration-data.component.html b/projects/admin/src/app/migration/conversion/record/detail-view/migration-data/migration-data.component.html
new file mode 100644
index 000000000..65f06dedb
--- /dev/null
+++ b/projects/admin/src/app/migration/conversion/record/detail-view/migration-data/migration-data.component.html
@@ -0,0 +1,43 @@
+
+@if (record()) {
+
+}
diff --git a/projects/admin/src/app/migration/conversion/record/detail-view/migration-data/migration-data.component.scss b/projects/admin/src/app/migration/conversion/record/detail-view/migration-data/migration-data.component.scss
new file mode 100644
index 000000000..e52490be4
--- /dev/null
+++ b/projects/admin/src/app/migration/conversion/record/detail-view/migration-data/migration-data.component.scss
@@ -0,0 +1,37 @@
+/*
+ * RERO ILS UI
+ * Copyright (C) 2024 RERO
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+::ng-deep {
+ .markdown td,
+ .markdown th {
+ padding: 0 10px;
+ vertical-align: top;
+ }
+ tbody tr:nth-child(odd) {
+ background-color: #f8fafc;
+ }
+}
+
+.data {
+ .col {
+ max-height: 600px;
+ overflow: auto;
+ }
+ .json {
+ border-left: 1px solid gray;
+ }
+}
diff --git a/projects/admin/src/app/migration/conversion/record/detail-view/migration-data/migration-data.component.ts b/projects/admin/src/app/migration/conversion/record/detail-view/migration-data/migration-data.component.ts
new file mode 100644
index 000000000..8380446ee
--- /dev/null
+++ b/projects/admin/src/app/migration/conversion/record/detail-view/migration-data/migration-data.component.ts
@@ -0,0 +1,83 @@
+/*
+ * RERO ILS UI
+ * Copyright (C) 2024 RERO
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { HttpClient } from '@angular/common/http';
+import { Component, computed, inject } from '@angular/core';
+import { toSignal } from '@angular/core/rxjs-interop';
+import { ActivatedRoute } from '@angular/router';
+import { ApiService, Record } from '@rero/ng-core';
+import { of, switchMap } from 'rxjs';
+
+@Component({
+ selector: 'admin-migration-data',
+ templateUrl: './migration-data.component.html',
+ styleUrl: './migration-data.component.scss',
+})
+export class MigrationDataDetailComponent {
+
+ // services
+ protected route: ActivatedRoute = inject(ActivatedRoute);
+ protected http: HttpClient = inject(HttpClient);
+ protected apiService: ApiService = inject(ApiService);
+
+ // current record from the route params
+ record = toSignal(
+ this.route.paramMap.pipe(
+ switchMap(() => {
+ // route params
+ const docType = this.route.snapshot.params.type;
+ const id = this.route.snapshot.params.pid;
+ const migrationId = this.route.snapshot.queryParams.migration;
+ // nothing to do
+ if (docType == null || id == null || migrationId == null) {
+ return of(null);
+ }
+ // get the record from the backend
+ return this.http.get(
+ `${this.apiService.getEndpointByType(docType, true)}/${id}?migration=${migrationId}`
+ );
+ })
+ )
+ );
+
+ // log messages from the backend
+ messages = computed((): {severity: string, detail: string}[] => this.getMessages());
+
+ // conversion status
+ status = computed((): string => this.record()?.conversion?.status);
+
+ /** Get the list of the log message from the record
+ *
+ * @returns the list messages on the primeng format.
+ */
+ getMessages(): {severity: string, detail: string}[] {
+ const messages = [];
+ if (this.record()?.conversion.logs) {
+ ['info', 'warning', 'error'].map((field) => {
+ const log = this.record().conversion.logs[field];
+ if (log) {
+ messages.push({
+ severity: field == 'warning' ? 'warn' : field,
+ detail: log.join(' '),
+ });
+ }
+ });
+ }
+ return messages;
+ }
+
+}
diff --git a/projects/admin/src/app/migration/conversion/record/detail-view/pipes/highlight-json.pipe.ts b/projects/admin/src/app/migration/conversion/record/detail-view/pipes/highlight-json.pipe.ts
new file mode 100644
index 000000000..e84fadc44
--- /dev/null
+++ b/projects/admin/src/app/migration/conversion/record/detail-view/pipes/highlight-json.pipe.ts
@@ -0,0 +1,72 @@
+/*
+ * RERO ILS UI
+ * Copyright (C) 2024 RERO
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { inject, Pipe, PipeTransform } from '@angular/core';
+import { DomSanitizer } from '@angular/platform-browser';
+
+/**
+ * Highlight a JSON structure.
+ *
+ * Copied from the SONAR project.
+ */
+@Pipe({
+ name: 'highlightJson',
+})
+export class HighlightJsonPipe implements PipeTransform {
+
+ // services
+ protected sanitizer: DomSanitizer = inject(DomSanitizer);
+
+ /**
+ * Highlight a JSON structure.
+ *
+ * @param value Json structure.
+ * @return Highlighted string.
+ */
+ transform(value: string): any {
+ if (value == null) {
+ return;
+ }
+ let json = value
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/(?:\r\n|\r|\n)/g, ' ')
+ .replace(/( )/g, ' ');
+
+ json = json.replace(
+ /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
+ (match: any) => {
+ let cls = 'text-gray-600';
+ if (/^"/.test(match)) {
+ if (/:$/.test(match)) {
+ cls = 'text-cyan-600';
+ } else {
+ cls = 'text-orange-600';
+ }
+ } else if (/true|false/.test(match)) {
+ cls = 'text-blue-600';
+ } else if (/null/.test(match)) {
+ cls = 'text-blue-600';
+ }
+ return `${match}`;
+ }
+ );
+ const html = this.sanitizer.bypassSecurityTrustHtml(json);
+ return html;
+ }
+}
diff --git a/projects/admin/src/app/migration/deduplication/record/brief-view/migration-data-deduplication/migration-data-deduplication.component.html b/projects/admin/src/app/migration/deduplication/record/brief-view/migration-data-deduplication/migration-data-deduplication.component.html
new file mode 100644
index 000000000..08610426f
--- /dev/null
+++ b/projects/admin/src/app/migration/deduplication/record/brief-view/migration-data-deduplication/migration-data-deduplication.component.html
@@ -0,0 +1,100 @@
+
+
+@if (record()) {
+
+
+
+}
diff --git a/projects/admin/src/app/migration/deduplication/record/brief-view/migration-data-deduplication/migration-data-deduplication.component.scss b/projects/admin/src/app/migration/deduplication/record/brief-view/migration-data-deduplication/migration-data-deduplication.component.scss
new file mode 100644
index 000000000..440053314
--- /dev/null
+++ b/projects/admin/src/app/migration/deduplication/record/brief-view/migration-data-deduplication/migration-data-deduplication.component.scss
@@ -0,0 +1,22 @@
+/*
+ * RERO ILS UI
+ * Copyright (C) 2024 RERO
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+:host ::ng-deep {
+ .p-message .p-message-wrapper {
+ padding: 0.5rem 0.75rem;
+ }
+}
diff --git a/projects/admin/src/app/migration/deduplication/record/brief-view/migration-data-deduplication/migration-data-deduplication.component.ts b/projects/admin/src/app/migration/deduplication/record/brief-view/migration-data-deduplication/migration-data-deduplication.component.ts
new file mode 100644
index 000000000..241f6812d
--- /dev/null
+++ b/projects/admin/src/app/migration/deduplication/record/brief-view/migration-data-deduplication/migration-data-deduplication.component.ts
@@ -0,0 +1,212 @@
+/*
+ * RERO ILS UI
+ * Copyright (C) 2024 RERO
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { Component, computed, inject, input, OnInit, output } from '@angular/core';
+import { RecordService } from '@rero/ng-core';
+import { MessageService } from 'primeng/api';
+
+@Component({
+ selector: 'admin-migration-data-deduplication',
+ templateUrl: './migration-data-deduplication.component.html',
+ styleUrl: './migration-data-deduplication.component.scss',
+})
+export class MigrationDataDeduplicationBriefComponent implements OnInit {
+ // services
+ protected toastService: MessageService = inject(MessageService);
+ protected recordService: RecordService = inject(RecordService);
+
+ // current record
+ record = input();
+
+ // need a result list refresh
+ refresh = output();
+
+ // current ILS pid
+ ilsPid = null;
+
+ // toggle in place edit ils pid
+ isInplaceActive = false;
+
+ // current candidate
+ currentCandidate = null;
+
+ // current candidate index
+ currentCandidateIndex = null;
+
+ // message logs server
+ messages = computed((): { severity: string; detail: string }[] => this.getMessages());
+
+ //** OnInit hook */
+ ngOnInit(): void {
+ let ilsPid = this.record()?.metadata?.deduplication?.ils_pid;
+ // get value from the backend if it exists
+ if (ilsPid == null && this.candidates.length > 0) {
+ ilsPid = this.candidates[0].json.pid;
+ }
+ // display the current candidate if exists
+ this.updateCurrentCandidate(ilsPid);
+ }
+
+ /**
+ * Get the backend log messages from the record.
+ *
+ * @returns list of messages on primeng format
+ */
+ getMessages(): { severity: string; detail: string }[] {
+ const messages = [];
+ if (this.record()?.metadata?.deduplication?.logs) {
+ ['info', 'warning', 'error'].map((field) => {
+ const log = this.record()?.metadata?.deduplication?.logs[field];
+ if (log) {
+ messages.push({
+ severity: field == 'warning' ? 'warn' : field,
+ detail: log.join(' '),
+ });
+ }
+ });
+ }
+ return messages;
+ }
+
+ // deduplication status.
+ status = computed(() => this.record()?.metadata?.deduplication?.status);
+
+ /**
+ * Updates the current candidates.
+ *
+ * Retrieve the candidate from the current list else retrieve the candidate from the backend.
+ *
+ * @param ilsPid - the ILS pid value.
+ */
+ updateCurrentCandidate(ilsPid: string): void {
+ // pid is null thus unselect
+ if (ilsPid == null || ilsPid === '') {
+ this.currentCandidateIndex = -1;
+ this.currentCandidate = null;
+ this.ilsPid = null;
+ } else {
+ const existingCandidateIndex = this.candidates.findIndex((v) => v?.json?.pid == ilsPid);
+ // exists in the current list
+ if (existingCandidateIndex > -1) {
+ this.currentCandidateIndex = existingCandidateIndex;
+ this.currentCandidate = this.record()?.metadata?.deduplication?.candidates[this.currentCandidateIndex];
+ this.updateIlsPid();
+ } else {
+ // retrieve from the backend
+ this.recordService.getRecords('documents', `pid:${ilsPid}`, 1, 1).subscribe((results: any) => {
+ if (results.hits.hits.length == 1) {
+ const record = results.hits.hits[0].metadata;
+ // add to the candidate list at the first position
+ this.record().metadata.deduplication.candidates = [
+ { pid: record.pid, json: record },
+ ...this.candidates.filter((c) => c.score),
+ ];
+ this.currentCandidateIndex = 0;
+ this.currentCandidate = this.record()?.metadata?.deduplication?.candidates[this.currentCandidateIndex];
+ this.updateIlsPid();
+ } else {
+ // no document from the backend
+ this.toastService.add({ severity: 'warn', summary: 'Record not found.' });
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Updates the ILS pid value if possible.
+ */
+ updateIlsPid(): void {
+ if (this.currentCandidate) {
+ if (this.ilsPid != this.currentCandidate.json.pid) {
+ this.ilsPid = this.currentCandidate.json.pid;
+ }
+ }
+ }
+
+ // candidates shortcut
+ get candidates(): any[] {
+ const candidates = this.record()?.metadata?.deduplication?.candidates;
+ if (candidates) {
+ return candidates;
+ }
+ return [];
+ }
+
+ /**
+ * Has the current candidate a previous value?
+ * @returns true if exists
+ */
+ hasPrevious(): boolean {
+ return this.currentCandidateIndex > 0;
+ }
+ /**
+ * Has the current candidate a next value?
+ * @returns true if exists
+ */
+ hasNext(): boolean {
+ return this.currentCandidateIndex < this.candidates.length - 1;
+ }
+
+ /***
+ * Set the next candidate as the current candidate.
+ */
+ nextCandidate(): void {
+ if (this.hasNext()) {
+ this.currentCandidateIndex += 1;
+ this.updateCurrentCandidate(this.candidates[this.currentCandidateIndex].json.pid);
+ }
+ }
+
+ /***
+ * Set the previous candidate as the current candidate.
+ */
+ previousCandidate(): void {
+ if (this.hasPrevious()) {
+ this.currentCandidateIndex -= 1;
+ this.updateCurrentCandidate(this.candidates[this.currentCandidateIndex].json.pid);
+ }
+ }
+
+ /**
+ * Set the current ILS pid from the input value and set the candidate accordingly.
+ * @param event - keyboard event
+ */
+ saveIlsPid(event): void {
+ const ilsPid = event.target.value != '' ? event.target.value : null;
+ this.updateCurrentCandidate(ilsPid);
+ }
+
+ /**
+ * Save the candidates and the ILS pid value in the backend.
+ */
+ save(): void {
+ this.recordService
+ .update('migration_data', `${this.record().id}?migration=${this.record().metadata.migration_id}`, {
+ ils_pid: this.ilsPid,
+ candidates: this.candidates,
+ })
+ .subscribe((record: any) => {
+ this.refresh.emit(true);
+ this.toastService.add({
+ severity: 'success',
+ summary: `${record.id} has been sucessfully updated`,
+ detail: `the status is now: ${record.deduplication.status}`,
+ });
+ });
+ }
+}
diff --git a/projects/admin/src/app/migration/deduplication/record/brief-view/migration-metadata/migration-metadata.component.html b/projects/admin/src/app/migration/deduplication/record/brief-view/migration-metadata/migration-metadata.component.html
new file mode 100644
index 000000000..0110a9753
--- /dev/null
+++ b/projects/admin/src/app/migration/deduplication/record/brief-view/migration-metadata/migration-metadata.component.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+
diff --git a/projects/admin/src/app/migration/deduplication/record/brief-view/migration-metadata/migration-metadata.component.scss b/projects/admin/src/app/migration/deduplication/record/brief-view/migration-metadata/migration-metadata.component.scss
new file mode 100644
index 000000000..0c866e3e2
--- /dev/null
+++ b/projects/admin/src/app/migration/deduplication/record/brief-view/migration-metadata/migration-metadata.component.scss
@@ -0,0 +1,23 @@
+/*
+ * RERO ILS UI
+ * Copyright (C) 2024 RERO
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+ ::ng-deep {
+ td {
+ vertical-align: top;
+ }
+
+ table td:last-child, table th:last-child { text-align: right; }
+}
diff --git a/projects/admin/src/app/migration/deduplication/record/brief-view/migration-metadata/migration-metadata.component.ts b/projects/admin/src/app/migration/deduplication/record/brief-view/migration-metadata/migration-metadata.component.ts
new file mode 100644
index 000000000..6490965ae
--- /dev/null
+++ b/projects/admin/src/app/migration/deduplication/record/brief-view/migration-metadata/migration-metadata.component.ts
@@ -0,0 +1,168 @@
+/*
+ * RERO ILS UI
+ * Copyright (C) 2024 RERO
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { Component, computed, inject, input } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { MainTitlePipe } from '@rero/shared';
+
+@Component({
+ selector: 'admin-migration-metadata',
+ templateUrl: './migration-metadata.component.html',
+ styleUrl: './migration-metadata.component.scss',
+})
+export class MigrationMetadataBriefComponent {
+ // services
+ protected translateService: TranslateService = inject(TranslateService);
+ // pipes
+ protected mainTitlePipe: MainTitlePipe = inject(MainTitlePipe);
+
+ // current record
+ record = input();
+
+ // current candidate
+ candidate = input();
+
+ // data to load in the table
+ data = computed(() => this.getData());
+
+ /**
+ * Set the color based on the score value.
+ *
+ * @param value score value as string: i.e. (0.68)^2.0 or 1.0.
+ * @returns html code with different text colors.
+ */
+ getScore(value): string {
+ if (value == null) {
+ return ''
+ }
+ if (value?.weight == null) {
+ return `${value.value.toFixed(2)} | 1.0`;
+ }
+ var color = 'text-orange-500';
+ if (value.value < 0.6) {
+ color = 'text-red-500';
+ } else if (value.value > 0.8) {
+ color = 'text-green-500';
+ }
+ return `${value.value.toFixed(2)} | ${value.weight.toFixed(1)}`
+ }
+
+ /**
+ * Get the date for the primeng table.
+ * @returns the list of object for each column and row.
+ */
+ getData(): any[] {
+ // record data
+ const data = this.record()?.metadata?.conversion?.json;
+
+ // candidates data
+ const c_data = this?.candidate()?.json;
+
+ // detailed scores
+ const scores = this?.candidate()?.detailed_score;
+ return [
+ {
+ field: this.record().id ?? '',
+ link: {
+ router: ['/migrations', 'records', 'format', 'migration_data', 'detail', this.record().id],
+ params: { migration: this.record().metadata.migration_id },
+ },
+ c_field: c_data?.pid ?? '',
+ c_link: {
+ router: c_data?.pid ? ['/records', 'documents', 'detail', c_data?.pid] : null,
+ },
+ label: 'pid',
+ },
+ {
+ field: data?.type ? data.type.map((v) => this.translateService.instant(v.main_type)).join('; ') : '',
+ c_field: c_data?.type ? c_data.type.map((v) => this.translateService.instant(v.main_type)).join('; ') : '',
+ label: 'Type',
+ score: this.getScore(scores?.main_type),
+ },
+ {
+ field: data?.title ? this.mainTitlePipe.transform(data.title) : '',
+ c_field: c_data?.title ? this.mainTitlePipe.transform(c_data.title) : '',
+ label: 'Title',
+ score: this.getScore(scores?.title),
+ },
+ {
+ field: data?.responsibilityStatement
+ ? data.responsibilityStatement.map((resp) => resp.map((data) => data.value)).join(' ')
+ : '',
+ c_field: c_data?.responsibilityStatement
+ ? c_data.responsibilityStatement.map((resp) => resp.map((data) => data.value)).join(' ')
+ : '',
+ label: 'Responsibility',
+ score: this.getScore(scores?.responsibility_statement),
+ },
+ {
+ field: data?.sort_date_old ?? '',
+ c_field: c_data?.sort_date_old ?? '',
+ label: 'Publication year',
+ score: this.getScore(scores?.publication_date),
+ },
+ {
+ field: data?.provisionActivity ? data.provisionActivity[0]._text[0].value : '',
+ c_field: c_data?.provisionActivity ? c_data.provisionActivity[0]._text[0].value : '',
+ label: 'Provision activity',
+ score: this.getScore(scores?.provision_activity),
+ },
+ {
+ field: data?.extent ? data.extent : '',
+ c_field: c_data?.extent ? c_data.extent : '',
+ label: 'Extent',
+ score: this.getScore(scores?.extent),
+ },
+ {
+ field: data?.editionStatement
+ ? data.editionStatement.map((resp) => resp?._text.map((data) => data.value)).join(' ')
+ : '',
+ c_field: c_data?.editionStatement
+ ? c_data.editionStatement.map((resp) => resp?._text.map((data) => data.value)).join(' ')
+ : '',
+ label: 'Edition statement',
+ score: this.getScore(scores?.edition_statement),
+ },
+ {
+ field: data?.seriesStatement
+ ? data.seriesStatement.map((resp) => resp?._text.map((data) => data.value)).join(' ')
+ : '',
+ c_field: c_data?.seriesStatement
+ ? c_data.seriesStatement.map((resp) => resp?._text.map((data) => data.value)).join(' ')
+ : '',
+ label: 'Series statement',
+ score: this.getScore(scores?.series_statement),
+ },
+ {
+ field: data?.identifiedBy
+ ? data.identifiedBy
+ .map((id) => `${id.type}: ${id.value}`)
+ .sort()
+ .join(' ')
+ : '',
+ c_field: c_data?.identifiedBy
+ ? c_data.identifiedBy
+ .map((id) => `${id.type}: ${id.value}`)
+ .sort()
+ .join(' ')
+ : '',
+ label: 'Identifiers',
+ score: this.getScore(scores?.identifier),
+ },
+ ];
+ }
+}
diff --git a/projects/admin/src/app/migration/deduplication/record/search/migration-search-page.component.ts b/projects/admin/src/app/migration/deduplication/record/search/migration-search-page.component.ts
new file mode 100644
index 000000000..5fdddc903
--- /dev/null
+++ b/projects/admin/src/app/migration/deduplication/record/search/migration-search-page.component.ts
@@ -0,0 +1,32 @@
+/*
+ * RERO ILS UI
+ * Copyright (C) 2024 RERO
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { Component } from '@angular/core';
+import { RecordSearchPageComponent } from '@rero/ng-core';
+
+@Component({
+ selector: 'admin-migration-search-page',
+ template: `
+
+
+ `,
+})
+export class MigrationSearchPageComponent extends RecordSearchPageComponent {
+
+}
diff --git a/projects/admin/src/app/migration/deduplication/record/search/migration-search/migration-search.component.html b/projects/admin/src/app/migration/deduplication/record/search/migration-search/migration-search.component.html
new file mode 100644
index 000000000..a89238b10
--- /dev/null
+++ b/projects/admin/src/app/migration/deduplication/record/search/migration-search/migration-search.component.html
@@ -0,0 +1,327 @@
+
+
+
+
+@if (error) {
+
+} @else {
+
+@if (typesInTabs.length > 1) {
+
+}
diff --git a/projects/admin/src/app/migration/record/brief-view/migration/migration.component.ts b/projects/admin/src/app/migration/record/brief-view/migration/migration.component.ts
new file mode 100644
index 000000000..437dba48a
--- /dev/null
+++ b/projects/admin/src/app/migration/record/brief-view/migration/migration.component.ts
@@ -0,0 +1,29 @@
+/*
+ * RERO ILS UI
+ * Copyright (C) 2024 RERO
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { Component, Input } from '@angular/core';
+
+@Component({
+ selector: 'admin-migration',
+ templateUrl: './migration.component.html'
+})
+export class MigrationDetailComponent {
+
+ // current record
+ @Input() record: any;
+
+}
diff --git a/projects/admin/src/manual_translations.ts b/projects/admin/src/manual_translations.ts
index f1ca46b0a..aaaef2295 100644
--- a/projects/admin/src/manual_translations.ts
+++ b/projects/admin/src/manual_translations.ts
@@ -254,3 +254,4 @@ _('/ day');
_('fiction');
_('non_fiction');
_('unspecified');
+
diff --git a/projects/shared/src/lib/util/permissions.ts b/projects/shared/src/lib/util/permissions.ts
index ab6274d28..95dd2053a 100644
--- a/projects/shared/src/lib/util/permissions.ts
+++ b/projects/shared/src/lib/util/permissions.ts
@@ -129,6 +129,9 @@ export const PERMISSIONS = {
'VNDR_ACCESS': 'vndr-access',
'VNDR_CREATE': 'vndr-create',
'VNDR_SEARCH': 'vndr-search',
+ // Migrations
+ 'MIG_ACCESS': 'mig-access',
+ 'MIG_SEARCH': 'mig-search'
};
// User with permission service