mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-14 04:40:36 +03:00
F: Prepare Association backend api
This commit is contained in:
parent
593b483936
commit
41e0ba64ba
|
@ -4,7 +4,7 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from apps.library.models import LibraryItem
|
from apps.library.models import LibraryItem
|
||||||
from apps.rsform.models import Constituenta, CstType, OrderManager, RSFormCached
|
from apps.rsform.models import Association, Constituenta, CstType, OrderManager, RSFormCached
|
||||||
|
|
||||||
from .Argument import Argument
|
from .Argument import Argument
|
||||||
from .Inheritance import Inheritance
|
from .Inheritance import Inheritance
|
||||||
|
@ -283,15 +283,15 @@ class OperationSchemaCached:
|
||||||
mapping=alias_mapping
|
mapping=alias_mapping
|
||||||
)
|
)
|
||||||
|
|
||||||
def before_delete_cst(self, sourceID: int, target: list[int]) -> None:
|
def before_delete_cst(self, operationID: int, target: list[int]) -> None:
|
||||||
''' Trigger cascade resolutions before Constituents are deleted. '''
|
''' Trigger cascade resolutions before Constituents are deleted. '''
|
||||||
operation = self.cache.get_operation(sourceID)
|
operation = self.cache.get_operation(operationID)
|
||||||
self.engine.on_delete_inherited(operation.pk, target)
|
self.engine.on_delete_inherited(operation.pk, target)
|
||||||
|
|
||||||
def before_substitute(self, schemaID: int, substitutions: CstSubstitution) -> None:
|
def before_substitute(self, schemaID: int, substitutions: CstSubstitution) -> None:
|
||||||
''' Trigger cascade resolutions before Constituents are substituted. '''
|
''' Trigger cascade resolutions before Constituents are substituted. '''
|
||||||
operation = self.cache.get_operation(schemaID)
|
operation = self.cache.get_operation(schemaID)
|
||||||
self.engine.on_before_substitute(substitutions, operation)
|
self.engine.on_before_substitute(operation.pk, substitutions)
|
||||||
|
|
||||||
def before_delete_arguments(self, target: Operation, arguments: list[Operation]) -> None:
|
def before_delete_arguments(self, target: Operation, arguments: list[Operation]) -> None:
|
||||||
''' Trigger cascade resolutions before arguments are deleted. '''
|
''' Trigger cascade resolutions before arguments are deleted. '''
|
||||||
|
@ -318,6 +318,17 @@ class OperationSchemaCached:
|
||||||
mapping={}
|
mapping={}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def after_create_association(self, schemaID: int, associations: list[Association],
|
||||||
|
exclude: Optional[list[int]] = None) -> None:
|
||||||
|
''' Trigger cascade resolutions when association is created. '''
|
||||||
|
operation = self.cache.get_operation(schemaID)
|
||||||
|
self.engine.on_inherit_association(operation.pk, associations, exclude)
|
||||||
|
|
||||||
|
def before_delete_association(self, schemaID: int, associations: list[Association]) -> None:
|
||||||
|
''' Trigger cascade resolutions when association is deleted. '''
|
||||||
|
operation = self.cache.get_operation(schemaID)
|
||||||
|
self.engine.on_delete_association(operation.pk, associations)
|
||||||
|
|
||||||
def _on_add_substitutions(self, schema: Optional[RSFormCached], added: list[Substitution]) -> None:
|
def _on_add_substitutions(self, schema: Optional[RSFormCached], added: list[Substitution]) -> None:
|
||||||
''' Trigger cascade resolutions when Constituenta substitution is added. '''
|
''' Trigger cascade resolutions when Constituenta substitution is added. '''
|
||||||
if not added:
|
if not added:
|
||||||
|
|
|
@ -3,7 +3,7 @@ from typing import Optional
|
||||||
|
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
|
|
||||||
from apps.rsform.models import INSERT_LAST, Constituenta, CstType, RSFormCached
|
from apps.rsform.models import INSERT_LAST, Association, Constituenta, CstType, RSFormCached
|
||||||
|
|
||||||
from .Inheritance import Inheritance
|
from .Inheritance import Inheritance
|
||||||
from .Operation import Operation
|
from .Operation import Operation
|
||||||
|
@ -126,9 +126,41 @@ class PropagationEngine:
|
||||||
mapping=new_mapping
|
mapping=new_mapping
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_before_substitute(self, substitutions: CstSubstitution, operation: Operation) -> None:
|
def on_inherit_association(self, operationID: int,
|
||||||
|
items: list[Association],
|
||||||
|
exclude: Optional[list[int]] = None) -> None:
|
||||||
|
''' Trigger cascade resolutions when association is inherited. '''
|
||||||
|
children = self.cache.extend_graph.outputs[operationID]
|
||||||
|
if not children:
|
||||||
|
return
|
||||||
|
for child_id in children:
|
||||||
|
if not exclude or child_id not in exclude:
|
||||||
|
self.inherit_association(child_id, items)
|
||||||
|
|
||||||
|
def inherit_association(self, target: int, items: list[Association]) -> None:
|
||||||
|
''' Execute inheritance of Associations. '''
|
||||||
|
operation = self.cache.operation_by_id[target]
|
||||||
|
if operation.result is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.cache.ensure_loaded_subs()
|
||||||
|
new_associations: list[Association] = []
|
||||||
|
for assoc in items:
|
||||||
|
new_container = self.cache.get_inheritor(assoc.container_id, target)
|
||||||
|
new_associate = self.cache.get_inheritor(assoc.associate_id, target)
|
||||||
|
if new_container is None or new_associate is None:
|
||||||
|
continue
|
||||||
|
new_associations.append(Association(
|
||||||
|
container_id=new_container,
|
||||||
|
associate_id=new_associate
|
||||||
|
))
|
||||||
|
if new_associations:
|
||||||
|
new_associations = Association.objects.bulk_create(new_associations)
|
||||||
|
self.on_inherit_association(target, new_associations)
|
||||||
|
|
||||||
|
def on_before_substitute(self, operationID: int, substitutions: CstSubstitution) -> None:
|
||||||
''' Trigger cascade resolutions when Constituenta substitution is executed. '''
|
''' Trigger cascade resolutions when Constituenta substitution is executed. '''
|
||||||
children = self.cache.extend_graph.outputs[operation.pk]
|
children = self.cache.extend_graph.outputs[operationID]
|
||||||
if not children:
|
if not children:
|
||||||
return
|
return
|
||||||
self.cache.ensure_loaded_subs()
|
self.cache.ensure_loaded_subs()
|
||||||
|
@ -140,9 +172,37 @@ class PropagationEngine:
|
||||||
new_substitutions = self._transform_substitutions(substitutions, child_id, child_schema)
|
new_substitutions = self._transform_substitutions(substitutions, child_id, child_schema)
|
||||||
if not new_substitutions:
|
if not new_substitutions:
|
||||||
continue
|
continue
|
||||||
self.on_before_substitute(new_substitutions, child_operation)
|
self.on_before_substitute(child_operation.pk, new_substitutions)
|
||||||
child_schema.substitute(new_substitutions)
|
child_schema.substitute(new_substitutions)
|
||||||
|
|
||||||
|
def on_delete_association(self, operationID: int, associations: list[Association]) -> None:
|
||||||
|
''' Trigger cascade resolutions when association is deleted. '''
|
||||||
|
children = self.cache.extend_graph.outputs[operationID]
|
||||||
|
if not children:
|
||||||
|
return
|
||||||
|
self.cache.ensure_loaded_subs()
|
||||||
|
for child_id in children:
|
||||||
|
child_operation = self.cache.operation_by_id[child_id]
|
||||||
|
child_schema = self.cache.get_schema(child_operation)
|
||||||
|
if child_schema is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
deleted: list[Association] = []
|
||||||
|
for assoc in associations:
|
||||||
|
new_container = self.cache.get_inheritor(assoc.container_id, child_id)
|
||||||
|
new_associate = self.cache.get_inheritor(assoc.associate_id, child_id)
|
||||||
|
if new_container is None or new_associate is None:
|
||||||
|
continue
|
||||||
|
deleted_assoc = Association.objects.filter(
|
||||||
|
container=new_container,
|
||||||
|
associate=new_associate
|
||||||
|
)
|
||||||
|
if deleted_assoc.exists():
|
||||||
|
deleted.append(deleted_assoc[0])
|
||||||
|
if deleted:
|
||||||
|
self.on_delete_association(child_id, deleted)
|
||||||
|
Association.objects.filter(pk__in=[assoc.pk for assoc in deleted]).delete()
|
||||||
|
|
||||||
def on_delete_inherited(self, operation: int, target: list[int]) -> None:
|
def on_delete_inherited(self, operation: int, target: list[int]) -> None:
|
||||||
''' Trigger cascade resolutions when Constituenta inheritance is deleted. '''
|
''' Trigger cascade resolutions when Constituenta inheritance is deleted. '''
|
||||||
children = self.cache.extend_graph.outputs[operation]
|
children = self.cache.extend_graph.outputs[operation]
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from apps.library.models import LibraryItem, LibraryItemType
|
from apps.library.models import LibraryItem, LibraryItemType
|
||||||
from apps.rsform.models import Constituenta, CstType, RSFormCached
|
from apps.rsform.models import Association, Constituenta, CstType, RSFormCached
|
||||||
|
|
||||||
from .OperationSchemaCached import CstSubstitution, OperationSchemaCached
|
from .OperationSchemaCached import CstSubstitution, OperationSchemaCached
|
||||||
|
|
||||||
|
@ -80,3 +80,22 @@ class PropagationFacade:
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
if exclude is None or host.pk not in exclude:
|
if exclude is None or host.pk not in exclude:
|
||||||
OperationSchemaCached(host).before_delete_cst(item.pk, ids)
|
OperationSchemaCached(host).before_delete_cst(item.pk, ids)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def after_create_association(sourceID: int, associations: list[Association],
|
||||||
|
exclude: Optional[list[int]] = None) -> None:
|
||||||
|
''' Trigger cascade resolutions when association is created. '''
|
||||||
|
hosts = _get_oss_hosts(sourceID)
|
||||||
|
for host in hosts:
|
||||||
|
if exclude is None or host.pk not in exclude:
|
||||||
|
OperationSchemaCached(host).after_create_association(sourceID, associations)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def before_delete_association(sourceID: int,
|
||||||
|
associations: list[Association],
|
||||||
|
exclude: Optional[list[int]] = None) -> None:
|
||||||
|
''' Trigger cascade resolutions before association is deleted. '''
|
||||||
|
hosts = _get_oss_hosts(sourceID)
|
||||||
|
for host in hosts:
|
||||||
|
if exclude is None or host.pk not in exclude:
|
||||||
|
OperationSchemaCached(host).before_delete_association(sourceID, associations)
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .basics import (
|
||||||
WordFormSerializer
|
WordFormSerializer
|
||||||
)
|
)
|
||||||
from .data_access import (
|
from .data_access import (
|
||||||
|
AssociationDataSerializer,
|
||||||
CrucialUpdateSerializer,
|
CrucialUpdateSerializer,
|
||||||
CstCreateSerializer,
|
CstCreateSerializer,
|
||||||
CstInfoSerializer,
|
CstInfoSerializer,
|
||||||
|
|
|
@ -30,6 +30,25 @@ class AssociationSerializer(StrictModelSerializer):
|
||||||
fields = ('container', 'associate')
|
fields = ('container', 'associate')
|
||||||
|
|
||||||
|
|
||||||
|
class AssociationDataSerializer(StrictSerializer):
|
||||||
|
''' Serializer: Association data. '''
|
||||||
|
container = PKField(many=False, queryset=Constituenta.objects.all().only('schema_id'))
|
||||||
|
associate = PKField(many=False, queryset=Constituenta.objects.all().only('schema_id'))
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
schema = cast(LibraryItem, self.context['schema'])
|
||||||
|
if schema and attrs['container'].schema_id != schema.id:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'container': msg.constituentaNotInRSform(schema.title)
|
||||||
|
})
|
||||||
|
if schema and attrs['associate'].schema_id != schema.id:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'associate': msg.constituentaNotInRSform(schema.title)
|
||||||
|
})
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class CstBaseSerializer(StrictModelSerializer):
|
class CstBaseSerializer(StrictModelSerializer):
|
||||||
''' Serializer: Constituenta all data. '''
|
''' Serializer: Constituenta all data. '''
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -49,6 +49,9 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
'restore_order',
|
'restore_order',
|
||||||
'reset_aliases',
|
'reset_aliases',
|
||||||
'produce_structure',
|
'produce_structure',
|
||||||
|
'add_association',
|
||||||
|
'delete_association',
|
||||||
|
'clear_associations'
|
||||||
]:
|
]:
|
||||||
permission_list = [permissions.ItemEditor]
|
permission_list = [permissions.ItemEditor]
|
||||||
elif self.action in [
|
elif self.action in [
|
||||||
|
@ -281,6 +284,104 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
data=s.RSFormParseSerializer(item).data
|
data=s.RSFormParseSerializer(item).data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
summary='create Association',
|
||||||
|
tags=['Constituenta'],
|
||||||
|
request=s.AssociationDataSerializer,
|
||||||
|
responses={
|
||||||
|
c.HTTP_201_CREATED: s.RSFormParseSerializer,
|
||||||
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
|
c.HTTP_403_FORBIDDEN: None,
|
||||||
|
c.HTTP_404_NOT_FOUND: None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@action(detail=True, methods=['post'], url_path='create-association')
|
||||||
|
def create_association(self, request: Request, pk) -> HttpResponse:
|
||||||
|
''' Create Association. '''
|
||||||
|
item = self._get_item()
|
||||||
|
serializer = s.AssociationDataSerializer(data=request.data, context={'schema': item})
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
new_association = m.Association.objects.create(
|
||||||
|
container=serializer.validated_data['container'],
|
||||||
|
associate=serializer.validated_data['associate']
|
||||||
|
)
|
||||||
|
PropagationFacade.after_create_association(item.pk, [new_association])
|
||||||
|
item.save(update_fields=['time_update'])
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
status=c.HTTP_201_CREATED,
|
||||||
|
data=s.RSFormParseSerializer(item).data
|
||||||
|
)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
summary='delete Association',
|
||||||
|
tags=['RSForm'],
|
||||||
|
request=s.AssociationDataSerializer,
|
||||||
|
responses={
|
||||||
|
c.HTTP_200_OK: s.RSFormParseSerializer,
|
||||||
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
|
c.HTTP_403_FORBIDDEN: None,
|
||||||
|
c.HTTP_404_NOT_FOUND: None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@action(detail=True, methods=['patch'], url_path='delete-association')
|
||||||
|
def delete_association(self, request: Request, pk) -> HttpResponse:
|
||||||
|
''' Endpoint: Delete Association. '''
|
||||||
|
item = self._get_item()
|
||||||
|
serializer = s.AssociationDataSerializer(data=request.data, context={'schema': item})
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
target = list(m.Association.objects.filter(
|
||||||
|
container=serializer.validated_data['container'],
|
||||||
|
associate=serializer.validated_data['associate']
|
||||||
|
))
|
||||||
|
if not target:
|
||||||
|
raise ValidationError({
|
||||||
|
'container': msg.invalidAssociation()
|
||||||
|
})
|
||||||
|
|
||||||
|
PropagationFacade.before_delete_association(item.pk, target)
|
||||||
|
m.Association.objects.filter(pk__in=[assoc.pk for assoc in target]).delete()
|
||||||
|
item.save(update_fields=['time_update'])
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
status=c.HTTP_200_OK,
|
||||||
|
data=s.RSFormParseSerializer(item).data
|
||||||
|
)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
summary='delete all associations for target constituenta',
|
||||||
|
tags=['RSForm'],
|
||||||
|
request=s.CstTargetSerializer,
|
||||||
|
responses={
|
||||||
|
c.HTTP_200_OK: s.RSFormParseSerializer,
|
||||||
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
|
c.HTTP_403_FORBIDDEN: None,
|
||||||
|
c.HTTP_404_NOT_FOUND: None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@action(detail=True, methods=['patch'], url_path='clear-associations')
|
||||||
|
def clear_associations(self, request: Request, pk) -> HttpResponse:
|
||||||
|
''' Endpoint: Delete Associations for target Constituenta. '''
|
||||||
|
item = self._get_item()
|
||||||
|
serializer = s.CstTargetSerializer(data=request.data, context={'schema': item})
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
target = list(m.Association.objects.filter(container=serializer.validated_data['target']))
|
||||||
|
if target:
|
||||||
|
PropagationFacade.before_delete_association(item.pk, target)
|
||||||
|
m.Association.objects.filter(pk__in=[assoc.pk for assoc in target]).delete()
|
||||||
|
item.save(update_fields=['time_update'])
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
status=c.HTTP_200_OK,
|
||||||
|
data=s.RSFormParseSerializer(item).data
|
||||||
|
)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='move constituenta',
|
summary='move constituenta',
|
||||||
tags=['RSForm'],
|
tags=['RSForm'],
|
||||||
|
|
|
@ -142,6 +142,10 @@ def typificationInvalidStr():
|
||||||
return 'Invalid typification string'
|
return 'Invalid typification string'
|
||||||
|
|
||||||
|
|
||||||
|
def invalidAssociation():
|
||||||
|
return f'Ассоциация не найдена'
|
||||||
|
|
||||||
|
|
||||||
def exteorFileVersionNotSupported():
|
def exteorFileVersionNotSupported():
|
||||||
return 'Некорректный формат файла Экстеор. Сохраните файл в новой версии'
|
return 'Некорректный формат файла Экстеор. Сохраните файл в новой версии'
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { type Edge, MarkerType, type Node, useEdgesState, useNodesState } from 'reactflow';
|
import { type Edge, MarkerType, type Node, useEdgesState, useNodesState } from 'reactflow';
|
||||||
|
|
||||||
|
import { type IConstituenta, type IRSForm } from '@/features/rsform';
|
||||||
import { TGEdgeTypes } from '@/features/rsform/components/term-graph/graph/tg-edge-types';
|
import { TGEdgeTypes } from '@/features/rsform/components/term-graph/graph/tg-edge-types';
|
||||||
import { TGNodeTypes } from '@/features/rsform/components/term-graph/graph/tg-node-types';
|
import { TGNodeTypes } from '@/features/rsform/components/term-graph/graph/tg-node-types';
|
||||||
import { SelectColoring } from '@/features/rsform/components/term-graph/select-coloring';
|
import { SelectColoring } from '@/features/rsform/components/term-graph/select-coloring';
|
||||||
import { ToolbarFocusedCst } from '@/features/rsform/components/term-graph/toolbar-focused-cst';
|
import { ToolbarFocusedCst } from '@/features/rsform/components/term-graph/toolbar-focused-cst';
|
||||||
import { applyLayout, produceFilteredGraph, type TGNodeData } from '@/features/rsform/models/graph-api';
|
import { applyLayout, produceFilteredGraph, type TGNodeData } from '@/features/rsform/models/graph-api';
|
||||||
import { type IConstituenta, type IRSForm } from '@/features/rsform/models/rsform';
|
|
||||||
import { useTermGraphStore } from '@/features/rsform/stores/term-graph';
|
import { useTermGraphStore } from '@/features/rsform/stores/term-graph';
|
||||||
|
|
||||||
import { DiagramFlow, useReactFlow } from '@/components/flow/diagram-flow';
|
import { DiagramFlow, useReactFlow } from '@/components/flow/diagram-flow';
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { DELAYS, KEYS } from '@/backend/configuration';
|
||||||
import { infoMsg } from '@/utils/labels';
|
import { infoMsg } from '@/utils/labels';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
type IAssociationDataDTO,
|
||||||
|
type IAssociationTargetDTO,
|
||||||
type ICheckConstituentaDTO,
|
type ICheckConstituentaDTO,
|
||||||
type IConstituentaCreatedResponse,
|
type IConstituentaCreatedResponse,
|
||||||
type IConstituentaList,
|
type IConstituentaList,
|
||||||
|
@ -150,5 +152,33 @@ export const rsformsApi = {
|
||||||
schema: schemaExpressionParse,
|
schema: schemaExpressionParse,
|
||||||
endpoint: `/api/rsforms/${itemID}/check-constituenta`,
|
endpoint: `/api/rsforms/${itemID}/check-constituenta`,
|
||||||
request: { data: data }
|
request: { data: data }
|
||||||
|
}),
|
||||||
|
|
||||||
|
createAssociation: ({ itemID, data }: { itemID: number; data: IAssociationDataDTO }) =>
|
||||||
|
axiosPost<IAssociationDataDTO, IRSFormDTO>({
|
||||||
|
schema: schemaRSForm,
|
||||||
|
endpoint: `/api/rsforms/${itemID}/create-association`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: infoMsg.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
deleteAssociation: ({ itemID, data }: { itemID: number; data: IAssociationDataDTO }) =>
|
||||||
|
axiosPatch<IAssociationDataDTO, IRSFormDTO>({
|
||||||
|
schema: schemaRSForm,
|
||||||
|
endpoint: `/api/rsforms/${itemID}/delete-association`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: infoMsg.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
clearAssociations: ({ itemID, data }: { itemID: number; data: IAssociationTargetDTO }) =>
|
||||||
|
axiosPatch<IAssociationTargetDTO, IRSFormDTO>({
|
||||||
|
schema: schemaRSForm,
|
||||||
|
endpoint: `/api/rsforms/${itemID}/clear-associations`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: infoMsg.changesSaved
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -94,6 +94,12 @@ export interface ICheckConstituentaDTO {
|
||||||
/** Represents data, used in merging multiple {@link IConstituenta}. */
|
/** Represents data, used in merging multiple {@link IConstituenta}. */
|
||||||
export type ISubstitutionsDTO = z.infer<typeof schemaSubstitutions>;
|
export type ISubstitutionsDTO = z.infer<typeof schemaSubstitutions>;
|
||||||
|
|
||||||
|
/** Represents data for creating or deleting an association. */
|
||||||
|
export type IAssociationDataDTO = z.infer<typeof schemaAssociationData>;
|
||||||
|
|
||||||
|
/** Represents data for clearing all associations for a target constituenta. */
|
||||||
|
export type IAssociationTargetDTO = z.infer<typeof schemaAssociationTarget>;
|
||||||
|
|
||||||
/** Represents Constituenta list. */
|
/** Represents Constituenta list. */
|
||||||
export interface IConstituentaList {
|
export interface IConstituentaList {
|
||||||
items: number[];
|
items: number[];
|
||||||
|
@ -386,6 +392,15 @@ export const schemaSubstitutions = z.strictObject({
|
||||||
substitutions: z.array(schemaSubstituteConstituents).min(1, { message: errorMsg.emptySubstitutions })
|
substitutions: z.array(schemaSubstituteConstituents).min(1, { message: errorMsg.emptySubstitutions })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const schemaAssociationData = z.strictObject({
|
||||||
|
container: z.number(),
|
||||||
|
associate: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const schemaAssociationTarget = z.strictObject({
|
||||||
|
target: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
export const schemaInlineSynthesis = z.strictObject({
|
export const schemaInlineSynthesis = z.strictObject({
|
||||||
receiver: z.number(),
|
receiver: z.number(),
|
||||||
source: z.number().nullable(),
|
source: z.number().nullable(),
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
|
||||||
|
|
||||||
|
import { KEYS } from '@/backend/configuration';
|
||||||
|
|
||||||
|
import { rsformsApi } from './api';
|
||||||
|
import { type IAssociationTargetDTO } from './types';
|
||||||
|
|
||||||
|
export const useClearAssociations = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'clear-associations'],
|
||||||
|
mutationFn: rsformsApi.clearAssociations,
|
||||||
|
onSuccess: async data => {
|
||||||
|
updateTimestamp(data.id, data.time_update);
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
await client.invalidateQueries({
|
||||||
|
queryKey: [rsformsApi.baseKey],
|
||||||
|
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== String(data.id)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: () => client.invalidateQueries()
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
clearAssociations: (data: { itemID: number; data: IAssociationTargetDTO }) => mutation.mutateAsync(data)
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
|
||||||
|
|
||||||
|
import { KEYS } from '@/backend/configuration';
|
||||||
|
|
||||||
|
import { rsformsApi } from './api';
|
||||||
|
import { type IAssociationDataDTO } from './types';
|
||||||
|
|
||||||
|
export const useCreateAssociation = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'create-association'],
|
||||||
|
mutationFn: rsformsApi.createAssociation,
|
||||||
|
onSuccess: async data => {
|
||||||
|
updateTimestamp(data.id, data.time_update);
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
await client.invalidateQueries({
|
||||||
|
queryKey: [rsformsApi.baseKey],
|
||||||
|
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== String(data.id)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: () => client.invalidateQueries()
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
createAssociation: (data: { itemID: number; data: IAssociationDataDTO }) => mutation.mutateAsync(data)
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
|
||||||
|
|
||||||
|
import { KEYS } from '@/backend/configuration';
|
||||||
|
|
||||||
|
import { rsformsApi } from './api';
|
||||||
|
import { type IAssociationDataDTO } from './types';
|
||||||
|
|
||||||
|
export const useDeleteAssociation = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'delete-association'],
|
||||||
|
mutationFn: rsformsApi.deleteAssociation,
|
||||||
|
onSuccess: async data => {
|
||||||
|
updateTimestamp(data.id, data.time_update);
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
await client.invalidateQueries({
|
||||||
|
queryKey: [rsformsApi.baseKey],
|
||||||
|
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== String(data.id)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: () => client.invalidateQueries()
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
deleteAssociation: (data: { itemID: number; data: IAssociationDataDTO }) => mutation.mutateAsync(data)
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user