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

279 lines
12 KiB
TypeScript

import { Injectable } from '@angular/core';
import { forkJoin, Observable, throwError, zip } from 'rxjs';
import { EntitiesService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, IDictionary, IUpdateDictionary, SuperTypes } from '@red/domain';
import { catchError, map, switchMap, tap } from 'rxjs/operators';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DossierTemplateStatsService } from './dossier-template-stats.service';
import { DictionariesMapService } from './dictionaries-map.service';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { FALLBACK_COLOR } from '@utils/constants';
import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service';
const MIN_WORD_LENGTH = 2;
@Injectable({
providedIn: 'root',
})
export class DictionaryService extends EntitiesService<IDictionary, Dictionary> {
protected readonly _defaultModelPath = 'dictionary';
protected readonly _entityClass = Dictionary;
constructor(
private readonly _toaster: Toaster,
private readonly _dossierTemplateStatsService: DossierTemplateStatsService,
private readonly _dictionariesMapService: DictionariesMapService,
private readonly _dossierDictionariesMapService: DossierDictionariesMapService,
) {
super();
}
/**
* Retrieves all dictionary entries of an entry type
*/
@Validate()
getForType(@RequiredParam() dossierTemplateId: string, @RequiredParam() type: string, dossierId?: string): Observable<IDictionary> {
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
return this._getOne([type, dossierTemplateId], this._defaultModelPath, queryParams);
}
/**
* Deletes entry types
*/
@Validate()
deleteDictionaries(
@RequiredParam() dictionaryIds: List,
@RequiredParam() 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])),
);
}
/**
* Retrieve all entry types
*/
@Validate()
getAllDictionaries(@RequiredParam() dossierTemplateId: string, dossierId?: string) {
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
return this._getOne<{ types: IDictionary[] }>(['type', dossierTemplateId], this._defaultModelPath, queryParams);
}
/**
* Updates colors, hint and caseInsensitive of an entry type.
*/
@Validate()
updateDictionary(
@RequiredParam() body: IUpdateDictionary,
@RequiredParam() dossierTemplateId: string,
@RequiredParam() type: string,
dossierId?: string,
): Observable<unknown> {
const url = `${this._defaultModelPath}/type/${type}/${dossierTemplateId}`;
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
return this._post(body, url, queryParams).pipe(
catchError((error: HttpErrorResponse) => this.#addUpdateDictionaryErrorToast(error)),
switchMap(() => this.loadDictionaryDataForDossierTemplate(dossierTemplateId)),
switchMap(() => this._dossierTemplateStatsService.getFor([dossierTemplateId])),
);
}
/**
* Creates entry type with colors, hint and caseInsensitive
*/
@Validate()
addDictionary(@RequiredParam() dictionary: IDictionary, dossierId?: string): Observable<unknown> {
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
return this._post(dictionary, `${this._defaultModelPath}/type`, queryParams).pipe(
catchError((error: HttpErrorResponse) => this.#addUpdateDictionaryErrorToast(error)),
switchMap(() => this.loadDictionaryDataForDossierTemplate(dictionary.dossierTemplateId)),
switchMap(() => this._dossierTemplateStatsService.getFor([dictionary.dossierTemplateId])),
);
}
saveEntries(
entries: List,
initialEntries: List,
dossierTemplateId: string,
type: string,
dossierId: string,
showToast = true,
dictionaryEntryType = DictionaryEntryTypes.ENTRY,
): Observable<unknown> {
const entriesToAdd = entries.map(e => e.trim()).filter(e => !!e);
const deletedEntries = initialEntries.filter(e => !entries.includes(e));
console.log({ entriesToAdd, deletedEntries });
// remove empty lines
const invalidRowsExist = entriesToAdd.filter(e => e.length < MIN_WORD_LENGTH);
if (invalidRowsExist.length === 0) {
// can add at least 1 - block UI
const obs: Observable<IDictionary>[] = [];
if (deletedEntries.length) {
obs.push(this._deleteEntries(deletedEntries, dossierTemplateId, type, dictionaryEntryType, dossierId));
}
if (entriesToAdd.filter(e => !initialEntries.includes(e)).length) {
obs.push(this._addEntries(entriesToAdd, dossierTemplateId, type, dictionaryEntryType, dossierId));
}
return zip(obs).pipe(
switchMap(dictionary => this._dossierTemplateStatsService.getFor([dossierTemplateId]).pipe(map(() => dictionary))),
tap({
next: () => {
if (showToast) {
this._toaster.success(_('dictionary-overview.success.generic'));
}
},
error: error => {
if (error.status === 400) {
this._toaster.error(_('dictionary-overview.error.400'));
} else {
this._toaster.error(_('dictionary-overview.error.generic'));
}
},
}),
);
}
this._toaster.error(_('dictionary-overview.error.entries-too-short'));
return throwError(() => 'Entries too short');
}
hasManualType(dossierTemplateId: string): boolean {
return !!this._dictionariesMapService.get(dossierTemplateId).find(e => e.type === SuperTypes.ManualRedaction && !e.virtual);
}
getRedactionTypes(dossierTemplateId: string): Dictionary[] {
const possibleDictionaries: Dictionary[] = [];
for (const dictionary of this._dictionariesMapService.get(dossierTemplateId)) {
if (!dictionary.virtual && !dictionary.hint && !dictionary.systemManaged) {
possibleDictionaries.push(dictionary);
}
}
possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label));
return possibleDictionaries;
}
async getDictionariesOptions(dossierTemplateId: string, dossierId: string): Promise<Dictionary[]> {
const possibleDictionaries: Dictionary[] = [];
const dossierDictionary: Dictionary = this._dossierDictionariesMapService.get(dossierId, 'dossier_redaction');
for (const dictionary of this._dictionariesMapService.get(dossierTemplateId)) {
if (!dictionary.virtual && dictionary.addToDictionaryAction) {
possibleDictionaries.push(dictionary);
}
}
if (dossierDictionary?.addToDictionaryAction) {
possibleDictionaries.push(dossierDictionary);
}
possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label));
return possibleDictionaries;
}
loadDossierDictionary(dossierTemplateId: string, dossierId: string): Observable<Dictionary> {
return this.getForType(dossierTemplateId, 'dossier_redaction', dossierId).pipe(
map(
dictionary =>
new Dictionary({
...dictionary,
type: 'dossier_redaction',
}),
),
tap(dictionary => {
this._dossierDictionariesMapService.set(dossierId, [dictionary]);
}),
catchError(() => {
this._dossierDictionariesMapService.set(dossierId, []);
return of(null);
}),
);
}
loadDictionaryData(dossierTemplatesIds: string[]): Observable<Dictionary[][]> {
const observables: Observable<Dictionary[]>[] = [];
for (const dossierTemplateId of dossierTemplatesIds) {
observables.push(this.loadDictionaryDataForDossierTemplate(dossierTemplateId));
}
return forkJoin(observables);
}
loadDictionaryDataForDossierTemplate(dossierTemplateId: string): Observable<Dictionary[]> {
return this.getAllDictionaries(dossierTemplateId).pipe(
map(typesResponse => typesResponse.types.map(type => new Dictionary(type))),
map(dictionaries => {
let manualTypeExists = false;
for (const dictionary of dictionaries) {
if (dictionary.type === SuperTypes.ManualRedaction) {
manualTypeExists = true;
break;
}
}
if (!manualTypeExists) {
dictionaries.push(new Dictionary({ hexColor: FALLBACK_COLOR, type: SuperTypes.ManualRedaction }, true));
}
return dictionaries;
}),
tap(dictionaries => this._dictionariesMapService.set(dossierTemplateId, dictionaries)),
);
}
#addUpdateDictionaryErrorToast(error: HttpErrorResponse): Observable<never> {
if (error.status === HttpStatusCode.Conflict) {
this._toaster.error(_('add-edit-entity.error.entity-already-exists'));
} else if (error.status === HttpStatusCode.BadRequest) {
this._toaster.error(_('add-edit-entity.error.invalid-color-or-rank'));
} else {
this._toaster.error(_('add-edit-entity.error.generic'));
}
return throwError(() => error);
}
/**
* Add dictionary entries with entry type.
*/
@Validate()
private _addEntries(
entries: List,
dossierTemplateId: string,
type: string,
dictionaryEntryType: DictionaryEntryType,
dossierId: string,
) {
const queryParams: List<QueryParam> = [
{ key: 'dossierId', value: dossierId },
{ key: 'dictionaryEntryType', value: dictionaryEntryType },
{ key: 'removeCurrent', value: true },
];
const url = `${this._defaultModelPath}/${type}/${dossierTemplateId}`;
return this._post(entries, url, queryParams);
}
/**
* Delete dictionary entries with entry type.
*/
@Validate()
private _deleteEntries(
entries: List,
dossierTemplateId: string,
type: string,
dictionaryEntryType: DictionaryEntryType,
dossierId: string,
) {
const queryParams = dossierId
? [{ key: 'dossierId', value: dossierId }]
: [{ key: 'dictionaryEntryType', value: dictionaryEntryType }];
const url = `${this._defaultModelPath}/delete/${type}/${dossierTemplateId}`;
return this._post(entries, url, queryParams);
}
}