red-ui/apps/red-ui/src/app/services/entity-services/dictionary.service.ts

271 lines
12 KiB
TypeScript

import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { EntitiesService, isIqserDevMode, Toaster } from '@iqser/common-ui';
import { List } from '@iqser/common-ui/lib/utils';
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, IDictionary, IUpdateDictionary, SuperTypes } from '@red/domain';
import { firstValueFrom, forkJoin, Observable } from 'rxjs';
import { map, switchMap, tap } from 'rxjs/operators';
import { IMAGE_CATEGORIES } from '../../modules/file-preview/utils/constants';
import { DictionariesMapService } from './dictionaries-map.service';
import { DossierTemplateStatsService } from './dossier-template-stats.service';
const IMAGE_TYPES = ['image', 'formula', 'ocr'];
@Injectable({
providedIn: 'root',
})
export class DictionaryService extends EntitiesService<IDictionary, Dictionary> {
protected readonly _defaultModelPath = 'dictionary';
protected readonly _entityClass = Dictionary;
readonly #isIqserDevMode = isIqserDevMode();
constructor(
private readonly _toaster: Toaster,
private readonly _dossierTemplateStatsService: DossierTemplateStatsService,
private readonly _dictionariesMapService: DictionariesMapService,
) {
super();
}
/**
* Retrieves all dictionary entries of an entry type
*/
getForType(dossierTemplateId: string, type: string, dossierId?: string) {
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
return firstValueFrom(this._getOne([type, dossierTemplateId], this._defaultModelPath, queryParams));
}
deleteDictionaries(dictionaryIds: List, dossierTemplateId: string, dossierId?: string): Observable<unknown> {
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
const url = `${this._defaultModelPath}/type/${dossierTemplateId}/delete`;
return this._post(dictionaryIds, url, queryParams).pipe(
switchMap(() => this.loadDictionaryDataForDossierTemplate(dossierTemplateId)),
switchMap(() => this._dossierTemplateStatsService.getFor([dossierTemplateId])),
);
}
getAllDictionaries(dossierTemplateId: string, readOnly = false, dossierId?: string) {
const queryParams = [];
if (dossierId) {
queryParams.push({ key: 'dossierId', value: dossierId });
}
if (readOnly) {
queryParams.push({ key: 'includeDeleted', value: true });
}
return this._getOne<{ types: IDictionary[] }>(['type', dossierTemplateId], this._defaultModelPath, queryParams).pipe(
map(typesResponse => typesResponse.types.map(type => new Dictionary(type))),
);
}
/**
* Updates colors, hint and caseInsensitive of an entry type.
*/
async update(body: IUpdateDictionary, dossierTemplateId: string, type: string, dossierId?: string): Promise<unknown> {
const url = `${this._defaultModelPath}/type/${type}/${dossierTemplateId}`;
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
const request = this._post(body, url, queryParams).pipe(
switchMap(() => this.loadDictionaryDataForDossierTemplate(dossierTemplateId)),
switchMap(() => this._dossierTemplateStatsService.getFor([dossierTemplateId])),
);
return await firstValueFrom(request);
}
async updateFlag(dossierTemplateId: string, type: string, dossierId: string, addToDictionary: boolean): Promise<unknown> {
const url = `${this._defaultModelPath}/updateFlag/${type}/${dossierTemplateId}/${dossierId}`;
const queryParams = [{ key: 'addToDictionary', value: addToDictionary }];
const request = this._post(null, url, queryParams).pipe(
switchMap(() => this.loadDictionaryDataForDossier(dossierTemplateId, dossierId)),
);
return await firstValueFrom(request);
}
async add(dictionary: IDictionary, dossierId?: string): Promise<unknown> {
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
const request = this._post(dictionary, `${this._defaultModelPath}/type`, queryParams).pipe(
switchMap(() => this.loadDictionaryDataForDossierTemplate(dictionary.dossierTemplateId)),
switchMap(() => this._dossierTemplateStatsService.getFor([dictionary.dossierTemplateId])),
);
return await firstValueFrom(request);
}
async saveEntries(
entries: List,
initialEntries: List,
dossierTemplateId: string,
type: string,
dossierId: string,
showToast = true,
dictionaryEntryType = DictionaryEntryTypes.ENTRY,
) {
const entriesToAdd: Array<string> = [];
const initialEntriesSet = new Set(initialEntries);
for (let i = 0; i < entries.length; i++) {
const entry = entries.at(i);
if (!entry.trim() || initialEntriesSet.has(entry)) {
continue;
}
entriesToAdd.push(entry);
}
const entriesToDelete: Array<string> = [];
const entriesSet = new Set(entries);
for (let i = 0; i < initialEntries.length; i++) {
const entry = initialEntries.at(i);
if (entriesSet.has(entry)) {
continue;
}
entriesToDelete.push(entry);
}
try {
await this.#updateEntries(entriesToAdd, entriesToDelete, dossierTemplateId, type, dictionaryEntryType, dossierId);
if (showToast) {
this._toaster.success(_('dictionary-overview.success.generic'));
}
} catch (error) {
if ((error as HttpErrorResponse).status === 400) {
this._toaster.error(_('dictionary-overview.error.400'));
} else {
this._toaster.error(_('dictionary-overview.error.generic'));
}
throw error;
}
}
hasManualType(dossierTemplateId: string): boolean {
return !!this._dictionariesMapService.get(dossierTemplateId).find(e => e.type === SuperTypes.ManualRedaction && !e.virtual);
}
hasType(dossierTemplateId: string, type: string): boolean {
return !!this._dictionariesMapService.get(dossierTemplateId).find(e => e.type === type && !e.virtual);
}
getDictionaries(dossierTemplateId: string) {
return this._dictionariesMapService
.get(dossierTemplateId)
.filter(d => d.model['typeId'] && (d.hasDictionary || d.addToDictionaryAction));
}
getRedactTextDictionaries(dossierId: string, dossierDictionaryOnly: boolean, dossierTemplateId: string): Dictionary[] {
const types = dossierDictionaryOnly ? this.#extractDossierLevelTypes(dossierId) : this.getDictionariesOptions(dossierTemplateId);
return types
.filter(d => d.model['typeId'] && !d.hint && d.addToDictionaryAction && (dossierDictionaryOnly || !d.dossierDictionaryOnly))
.sort((a, b) => a.label.localeCompare(b.label));
}
getEditableRedactionTypes(
dossierId: string,
isImage: boolean,
isHint: boolean,
isOCR: boolean,
currentlySelectedType: string,
): Dictionary[] {
return this.#extractDossierLevelTypes(dossierId)
.filter(
d =>
d.model['typeId'] &&
(isImage
? (isOCR ? [...IMAGE_CATEGORIES, 'ocr'] : IMAGE_CATEGORIES).includes(d.type)
: (isHint ? d.hint : !d.hint) &&
(d.addToDictionaryAction || currentlySelectedType === d.type) &&
!d.virtual &&
!d.systemManaged &&
![...IMAGE_CATEGORIES, 'ocr'].includes(d.type)),
)
.sort((a, b) => a.label.localeCompare(b.label));
}
getAddHintDictionaries(dossierId: string, dossierDictionaryOnly: boolean, dictionaryRequest: boolean): Dictionary[] {
const dictionaries: Dictionary[] = [];
this.#extractDossierLevelTypes(dossierId).forEach((d: Dictionary) => {
if (d.hint) {
if (dictionaryRequest) {
if (d.hasDictionary && d.addToDictionaryAction) {
if (d.dossierDictionaryOnly) {
if (dossierDictionaryOnly) {
dictionaries.push(d);
}
} else {
dictionaries.push(d);
}
}
} else if (!IMAGE_TYPES.includes(d.type)) {
dictionaries.push(d);
}
}
});
return dictionaries.sort((a, b) => a.label.localeCompare(b.label));
}
getRedactionTypes(dossierTemplateId: string): Dictionary[] {
return this._dictionariesMapService
.get(dossierTemplateId)
.filter(dictionary => !dictionary.virtual && !dictionary.hint && !dictionary.systemManaged)
.sort((a, b) => a.label.localeCompare(b.label));
}
getDictionariesOptions(dossierTemplateId: string): Dictionary[] {
return this._dictionariesMapService
.get(dossierTemplateId)
.filter(dictionary => !dictionary.virtual && dictionary.addToDictionaryAction)
.sort((a, b) => a.label.localeCompare(b.label));
}
loadDictionaryDataForDossierTemplate(dossierTemplateId: string): Observable<Dictionary[]> {
return this.getAllDictionaries(dossierTemplateId).pipe(
tap(dictionaries => this._dictionariesMapService.set(dossierTemplateId, dictionaries)),
);
}
loadDictionaryDataForDossier(dossierTemplateId: string, dossierId: string): Observable<Dictionary[]> {
return this.getAllDictionaries(dossierTemplateId, false, dossierId).pipe(
tap(dictionaries => this._dictionariesMapService.set(dossierId, dictionaries)),
);
}
loadTemporaryDictionaryData(dossierTemplateId: string, readOnlyFile = true): Observable<Dictionary[]> {
return this.getAllDictionaries(dossierTemplateId, readOnlyFile);
}
loadDictionaryEntriesByType(types: string[], dossierTemplateId: string, dossierId: string): Observable<Dictionary[]> {
const queryParams = [{ key: 'dossierId', value: dossierId }];
const requests = [];
for (const type of types) {
const request = this.getAll(`${this._defaultModelPath}/merged/${type}/${dossierTemplateId}`, queryParams);
requests.push(request);
}
return forkJoin(requests);
}
#updateEntries(
entriesToAdd: List,
entriesToDelete: List,
dossierTemplateId: string,
type: string,
dictionaryEntryType: DictionaryEntryType,
dossierId: string,
) {
const queryParams = [
{ key: 'dossierId', value: dossierId },
{ key: 'dictionaryEntryType', value: dictionaryEntryType },
];
const url = `${this._defaultModelPath}/update/${type}/${dossierTemplateId}`;
return firstValueFrom(this._post({ entriesToAdd, entriesToDelete }, url, queryParams));
}
#extractDossierLevelTypes(dossierId: string) {
return this._dictionariesMapService
.get(dossierId)
.filter(dictionary => dictionary.model['typeId']?.includes(dossierId))
.filter(dictionary => !(dictionary.experimental && !this.#isIqserDevMode));
}
}