279 lines
12 KiB
TypeScript
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);
|
|
}
|
|
}
|