F: Rework constituenta editing endpoints

This commit is contained in:
Ivan 2025-07-08 18:27:52 +03:00
parent 087ec8cf56
commit d46fa536e6
13 changed files with 111 additions and 238 deletions

View File

@ -112,18 +112,6 @@ class TestChangeConstituents(EndpointTester):
self.assertEqual(inherited_cst.definition_formal, 'X1 = X2') self.assertEqual(inherited_cst.definition_formal, 'X1 = X2')
@decl_endpoint('/api/rsforms/{schema}/rename-cst', method='patch')
def test_rename_constituenta(self):
data = {'target': self.ks1X1.pk, 'alias': 'D21', 'cst_type': CstType.TERM}
response = self.executeOK(data=data, schema=self.ks1.model.pk)
self.ks1X1.refresh_from_db()
inherited_cst = Constituenta.objects.get(as_child__parent_id=self.ks1X1.pk)
self.assertEqual(self.ks1X1.alias, data['alias'])
self.assertEqual(self.ks1X1.cst_type, data['cst_type'])
self.assertEqual(inherited_cst.alias, 'D2')
self.assertEqual(inherited_cst.cst_type, data['cst_type'])
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch') @decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch')
def test_update_constituenta(self): def test_update_constituenta(self):
d2 = self.ks3.insert_new('D2', cst_type=CstType.TERM, definition_raw='@{X1|sing,nomn}') d2 = self.ks3.insert_new('D2', cst_type=CstType.TERM, definition_raw='@{X1|sing,nomn}')

View File

@ -16,7 +16,6 @@ from .data_access import (
CstInfoSerializer, CstInfoSerializer,
CstListSerializer, CstListSerializer,
CstMoveSerializer, CstMoveSerializer,
CstRenameSerializer,
CstSubstituteSerializer, CstSubstituteSerializer,
CstTargetSerializer, CstTargetSerializer,
CstUpdateSerializer, CstUpdateSerializer,

View File

@ -45,11 +45,12 @@ class CstUpdateSerializer(serializers.Serializer):
class Meta: class Meta:
''' serializer metadata. ''' ''' serializer metadata. '''
model = Constituenta model = Constituenta
fields = 'convention', 'definition_formal', 'definition_raw', 'term_raw', 'term_forms' fields = 'alias', 'cst_type', 'convention', 'definition_formal', 'definition_raw', 'term_raw', 'term_forms'
target = PKField( target = PKField(
many=False, many=False,
queryset=Constituenta.objects.all().only('convention', 'definition_formal', 'definition_raw', 'term_raw') queryset=Constituenta.objects.all().only(
'alias', 'cst_type', 'convention', 'definition_formal', 'definition_raw', 'term_raw')
) )
item_data = ConstituentaUpdateData() item_data = ConstituentaUpdateData()
@ -60,6 +61,12 @@ class CstUpdateSerializer(serializers.Serializer):
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{cst.pk}': msg.constituentaNotInRSform(schema.title) f'{cst.pk}': msg.constituentaNotInRSform(schema.title)
}) })
if 'alias' in attrs['item_data']:
new_alias = attrs['item_data']['alias']
if cst.alias != new_alias and RSForm(schema).constituents().filter(alias=new_alias).exists():
raise serializers.ValidationError({
'alias': msg.aliasTaken(new_alias)
})
return attrs return attrs
@ -258,32 +265,6 @@ class CstTargetSerializer(serializers.Serializer):
return attrs return attrs
class CstRenameSerializer(serializers.Serializer):
''' Serializer: Constituenta renaming. '''
target = PKField(many=False, queryset=Constituenta.objects.only('alias', 'cst_type', 'schema'))
alias = serializers.CharField()
cst_type = serializers.CharField()
def validate(self, attrs):
attrs = super().validate(attrs)
schema = cast(LibraryItem, self.context['schema'])
cst = cast(Constituenta, attrs['target'])
if cst.schema_id != schema.pk:
raise serializers.ValidationError({
f'{cst.pk}': msg.constituentaNotInRSform(schema.title)
})
new_alias = self.initial_data['alias']
if cst.alias == new_alias:
raise serializers.ValidationError({
'alias': msg.renameTrivial(new_alias)
})
if RSForm(schema).constituents().filter(alias=new_alias).exists():
raise serializers.ValidationError({
'alias': msg.aliasTaken(new_alias)
})
return attrs
class CstListSerializer(serializers.Serializer): class CstListSerializer(serializers.Serializer):
''' Serializer: List of constituents from one origin. ''' ''' Serializer: List of constituents from one origin. '''
items = PKField(many=True, queryset=Constituenta.objects.all().only('schema_id')) items = PKField(many=True, queryset=Constituenta.objects.all().only('schema_id'))

View File

@ -244,56 +244,6 @@ class TestRSFormViewset(EndpointTester):
self.assertEqual(response.data['new_cst']['alias'], data['alias']) self.assertEqual(response.data['new_cst']['alias'], data['alias'])
@decl_endpoint('/api/rsforms/{item}/rename-cst', method='patch')
def test_rename_constituenta(self):
x1 = self.owned.insert_new(
alias='X1',
convention='Test',
term_raw='Test1',
term_resolved='Test1',
term_forms=[{'text': 'form1', 'tags': 'sing,datv'}]
)
x2_2 = self.unowned.insert_new('X2')
x3 = self.owned.insert_new(
alias='X3',
term_raw='Test3',
term_resolved='Test3',
definition_raw='Test1',
definition_resolved='Test2'
)
data = {'target': x2_2.pk, 'alias': 'D2', 'cst_type': CstType.TERM}
self.executeForbidden(data=data, item=self.unowned_id)
self.executeBadData(data=data, item=self.owned_id)
data = {'target': x1.pk, 'alias': x1.alias, 'cst_type': CstType.TERM}
self.executeBadData(data=data, item=self.owned_id)
data = {'target': x1.pk, 'alias': x3.alias}
self.executeBadData(data=data, item=self.owned_id)
d1 = self.owned.insert_new(
alias='D1',
term_raw='@{X1|plur}',
definition_formal='X1'
)
self.assertEqual(x1.order, 0)
self.assertEqual(x1.alias, 'X1')
self.assertEqual(x1.cst_type, CstType.BASE)
data = {'target': x1.pk, 'alias': 'D2', 'cst_type': CstType.TERM}
response = self.executeOK(data=data, item=self.owned_id)
self.assertEqual(response.data['new_cst']['alias'], 'D2')
self.assertEqual(response.data['new_cst']['cst_type'], CstType.TERM)
d1.refresh_from_db()
x1.refresh_from_db()
self.assertEqual(d1.term_resolved, '')
self.assertEqual(d1.term_raw, '@{D2|plur}')
self.assertEqual(x1.order, 0)
self.assertEqual(x1.alias, 'D2')
self.assertEqual(x1.cst_type, CstType.TERM)
@decl_endpoint('/api/rsforms/{item}/substitute', method='patch') @decl_endpoint('/api/rsforms/{item}/substitute', method='patch')
def test_substitute_multiple(self): def test_substitute_multiple(self):
self.set_params(item=self.owned_id) self.set_params(item=self.owned_id)
@ -507,12 +457,14 @@ class TestConstituentaAPI(EndpointTester):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.rsform_owned = RSForm.create(title='Test', alias='T1', owner=self.user) self.owned = RSForm.create(title='Test', alias='T1', owner=self.user)
self.rsform_unowned = RSForm.create(title='Test2', alias='T2') self.owned_id = self.owned.model.pk
self.unowned = RSForm.create(title='Test2', alias='T2')
self.unowned_id = self.unowned.model.pk
self.cst1 = Constituenta.objects.create( self.cst1 = Constituenta.objects.create(
alias='X1', alias='X1',
cst_type=CstType.BASE, cst_type=CstType.BASE,
schema=self.rsform_owned.model, schema=self.owned.model,
order=0, order=0,
convention='Test', convention='Test',
term_raw='Test1', term_raw='Test1',
@ -521,7 +473,7 @@ class TestConstituentaAPI(EndpointTester):
self.cst2 = Constituenta.objects.create( self.cst2 = Constituenta.objects.create(
alias='X2', alias='X2',
cst_type=CstType.BASE, cst_type=CstType.BASE,
schema=self.rsform_unowned.model, schema=self.unowned.model,
order=0, order=0,
convention='Test1', convention='Test1',
term_raw='Test2', term_raw='Test2',
@ -529,7 +481,7 @@ class TestConstituentaAPI(EndpointTester):
) )
self.cst3 = Constituenta.objects.create( self.cst3 = Constituenta.objects.create(
alias='X3', alias='X3',
schema=self.rsform_owned.model, schema=self.owned.model,
order=1, order=1,
term_raw='Test3', term_raw='Test3',
term_resolved='Test3', term_resolved='Test3',
@ -541,18 +493,42 @@ class TestConstituentaAPI(EndpointTester):
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch') @decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch')
def test_partial_update(self): def test_partial_update(self):
data = {'target': self.cst1.pk, 'item_data': {'convention': 'tt'}} data = {'target': self.cst1.pk, 'item_data': {'convention': 'tt'}}
self.executeForbidden(data=data, schema=self.rsform_unowned.model.pk) self.executeForbidden(data=data, schema=self.unowned_id)
self.logout() self.logout()
self.executeForbidden(data=data, schema=self.rsform_owned.model.pk) self.executeForbidden(data=data, schema=self.owned_id)
self.login() self.login()
response = self.executeOK(data=data, schema=self.rsform_owned.model.pk) self.executeOK(data=data, schema=self.owned_id)
self.cst1.refresh_from_db() self.cst1.refresh_from_db()
self.assertEqual(response.data['convention'], 'tt')
self.assertEqual(self.cst1.convention, 'tt') self.assertEqual(self.cst1.convention, 'tt')
self.executeOK(data=data, schema=self.rsform_owned.model.pk) self.executeOK(data=data, schema=self.owned_id)
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch')
def test_partial_update_rename(self):
data = {'target': self.cst1.pk, 'item_data': {'alias': self.cst3.alias}}
self.executeBadData(data=data, schema=self.owned_id)
d1 = self.owned.insert_new(
alias='D1',
term_raw='@{X1|plur}',
definition_formal='X1'
)
self.assertEqual(self.cst1.order, 0)
self.assertEqual(self.cst1.alias, 'X1')
self.assertEqual(self.cst1.cst_type, CstType.BASE)
data = {'target': self.cst1.pk, 'item_data': {'alias': 'D2', 'cst_type': CstType.TERM}}
self.executeOK(data=data, schema=self.owned_id)
d1.refresh_from_db()
self.cst1.refresh_from_db()
self.assertEqual(d1.term_resolved, '')
self.assertEqual(d1.term_raw, '@{D2|plur}')
self.assertEqual(self.cst1.order, 0)
self.assertEqual(self.cst1.alias, 'D2')
self.assertEqual(self.cst1.cst_type, CstType.TERM)
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch') @decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch')
@ -564,11 +540,9 @@ class TestConstituentaAPI(EndpointTester):
'definition_raw': 'New def' 'definition_raw': 'New def'
} }
} }
response = self.executeOK(data=data, schema=self.rsform_owned.model.pk) self.executeOK(data=data, schema=self.owned_id)
self.cst3.refresh_from_db() self.cst3.refresh_from_db()
self.assertEqual(response.data['term_resolved'], 'New term')
self.assertEqual(self.cst3.term_resolved, 'New term') self.assertEqual(self.cst3.term_resolved, 'New term')
self.assertEqual(response.data['definition_resolved'], 'New def')
self.assertEqual(self.cst3.definition_resolved, 'New def') self.assertEqual(self.cst3.definition_resolved, 'New def')
@ -581,12 +555,10 @@ class TestConstituentaAPI(EndpointTester):
'definition_raw': '@{X1|nomn,sing} @{X1|sing,datv}' 'definition_raw': '@{X1|nomn,sing} @{X1|sing,datv}'
} }
} }
response = self.executeOK(data=data, schema=self.rsform_owned.model.pk) self.executeOK(data=data, schema=self.owned_id)
self.cst3.refresh_from_db() self.cst3.refresh_from_db()
self.assertEqual(self.cst3.term_resolved, self.cst1.term_resolved) self.assertEqual(self.cst3.term_resolved, self.cst1.term_resolved)
self.assertEqual(response.data['term_resolved'], self.cst1.term_resolved)
self.assertEqual(self.cst3.definition_resolved, f'{self.cst1.term_resolved} form1') self.assertEqual(self.cst3.definition_resolved, f'{self.cst1.term_resolved} form1')
self.assertEqual(response.data['definition_resolved'], f'{self.cst1.term_resolved} form1')
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch') @decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch')
def test_update_term_forms(self): def test_update_term_forms(self):
@ -597,25 +569,10 @@ class TestConstituentaAPI(EndpointTester):
'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}] 'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}]
} }
} }
response = self.executeOK(data=data, schema=self.rsform_owned.model.pk) self.executeOK(data=data, schema=self.owned_id)
self.cst3.refresh_from_db() self.cst3.refresh_from_db()
self.assertEqual(self.cst3.definition_resolved, 'form1') self.assertEqual(self.cst3.definition_resolved, 'form1')
self.assertEqual(response.data['definition_resolved'], 'form1')
self.assertEqual(self.cst3.term_forms, data['item_data']['term_forms']) self.assertEqual(self.cst3.term_forms, data['item_data']['term_forms'])
self.assertEqual(response.data['term_forms'], data['item_data']['term_forms'])
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch')
def test_readonly_cst_fields(self):
data = {
'target': self.cst1.pk,
'item_data': {
'alias': 'X33'
}
}
response = self.executeOK(data=data, schema=self.rsform_owned.model.pk)
self.assertEqual(response.data['alias'], 'X1')
self.assertEqual(response.data['alias'], self.cst1.alias)
class TestInlineSynthesis(EndpointTester): class TestInlineSynthesis(EndpointTester):

View File

@ -41,7 +41,6 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
if self.action in [ if self.action in [
'load_trs', 'load_trs',
'create_cst', 'create_cst',
'rename_cst',
'update_cst', 'update_cst',
'move_cst', 'move_cst',
'delete_multiple_cst', 'delete_multiple_cst',
@ -102,7 +101,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
tags=['RSForm'], tags=['RSForm'],
request=s.CstUpdateSerializer, request=s.CstUpdateSerializer,
responses={ responses={
c.HTTP_200_OK: s.CstInfoSerializer, c.HTTP_200_OK: s.RSFormParseSerializer,
c.HTTP_400_BAD_REQUEST: None, c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None, c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None c.HTTP_404_NOT_FOUND: None
@ -120,9 +119,22 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
with transaction.atomic(): with transaction.atomic():
old_data = schema.update_cst(cst, data) old_data = schema.update_cst(cst, data)
PropagationFacade.after_update_cst(schema, cst, data, old_data) PropagationFacade.after_update_cst(schema, cst, data, old_data)
if 'alias' in data and data['alias'] != cst.alias:
cst.refresh_from_db()
changed_type = 'cst_type' in data and cst.cst_type != data['cst_type']
mapping = {cst.alias: data['alias']}
cst.alias = data['alias']
if changed_type:
cst.cst_type = data['cst_type']
cst.save()
schema.apply_mapping(mapping=mapping, change_aliases=False)
schema.save()
cst.refresh_from_db()
if changed_type:
PropagationFacade.after_change_cst_type(schema, cst)
return Response( return Response(
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
data=s.CstInfoSerializer(m.Constituenta.objects.get(pk=request.data['target'])).data data=s.RSFormParseSerializer(schema.model).data
) )
@extend_schema( @extend_schema(
@ -169,43 +181,6 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
} }
) )
@extend_schema(
summary='rename constituenta',
tags=['Constituenta'],
request=s.CstRenameSerializer,
responses={
c.HTTP_200_OK: s.NewCstResponse,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['patch'], url_path='rename-cst')
def rename_cst(self, request: Request, pk) -> HttpResponse:
''' Rename constituenta possibly changing type. '''
model = self._get_item()
serializer = s.CstRenameSerializer(data=request.data, context={'schema': model})
serializer.is_valid(raise_exception=True)
cst = cast(m.Constituenta, serializer.validated_data['target'])
changed_type = cst.cst_type != serializer.validated_data['cst_type']
mapping = {cst.alias: serializer.validated_data['alias']}
schema = m.RSForm(model)
with transaction.atomic():
cst.alias = serializer.validated_data['alias']
cst.cst_type = serializer.validated_data['cst_type']
cst.save()
schema.apply_mapping(mapping=mapping, change_aliases=False)
schema.save()
cst.refresh_from_db()
if changed_type:
PropagationFacade.after_change_cst_type(schema, cst)
return Response(
status=c.HTTP_200_OK,
data={
'new_cst': s.CstInfoSerializer(cst).data,
'schema': s.RSFormParseSerializer(schema.model).data
}
)
@extend_schema( @extend_schema(
summary='execute substitutions', summary='execute substitutions',

View File

@ -27,7 +27,7 @@ export function ApplicationLayout() {
<NavigationState> <NavigationState>
<div className='min-w-80 antialiased h-full max-w-480 mx-auto'> <div className='min-w-80 antialiased h-full max-w-480 mx-auto'>
<ToasterThemed <ToasterThemed
className={clsx('sm:text-[14px]/[20px] text-[12px]/[16px]', noNavigationAnimation ? 'mt-6' : 'mt-14')} className={clsx('sm:text-[14px]/[20px] text-[12px]/[16px]', noNavigationAnimation ? 'mt-10' : 'mt-18')}
aria-label='Оповещения' aria-label='Оповещения'
autoClose={3000} autoClose={3000}
draggable={false} draggable={false}

View File

@ -8,7 +8,15 @@ import { generateAlias } from '@/features/rsform/models/rsform-api';
import { useCstSearchStore } from '@/features/rsform/stores/cst-search'; import { useCstSearchStore } from '@/features/rsform/stores/cst-search';
import { MiniButton } from '@/components/control'; import { MiniButton } from '@/components/control';
import { IconClone, IconDestroy, IconMoveDown, IconMoveUp, IconNewItem, IconRSForm } from '@/components/icons'; import {
IconClone,
IconDestroy,
IconEdit2,
IconMoveDown,
IconMoveUp,
IconNewItem,
IconRSForm
} from '@/components/icons';
import { cn } from '@/components/utils'; import { cn } from '@/components/utils';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER, prefixes } from '@/utils/constants'; import { PARAMETER, prefixes } from '@/utils/constants';
@ -20,6 +28,7 @@ interface ToolbarConstituentsProps {
activeCst: IConstituenta | null; activeCst: IConstituenta | null;
setActive: (cstID: number) => void; setActive: (cstID: number) => void;
resetActive: () => void; resetActive: () => void;
onEditActive: () => void;
className?: string; className?: string;
} }
@ -28,6 +37,7 @@ export function ToolbarConstituents({
activeCst, activeCst,
setActive, setActive,
resetActive, resetActive,
onEditActive,
isMutable, isMutable,
className className
}: ToolbarConstituentsProps) { }: ToolbarConstituentsProps) {
@ -160,6 +170,13 @@ export function ToolbarConstituents({
onClick={navigateRSForm} onClick={navigateRSForm}
/> />
<MiniButton
title='Редактировать конституенту'
icon={<IconEdit2 size='1rem' className='icon-primary' />}
onClick={onEditActive}
disabled={!isMutable || isProcessing || !activeCst}
/>
<MiniButton <MiniButton
title='Создать конституенту' title='Создать конституенту'
icon={<IconNewItem size='1rem' className='icon-green' />} icon={<IconNewItem size='1rem' className='icon-green' />}

View File

@ -5,6 +5,7 @@ import { RSFormStats } from '@/features/rsform/components/rsform-stats';
import { ViewConstituents } from '@/features/rsform/components/view-constituents'; import { ViewConstituents } from '@/features/rsform/components/view-constituents';
import { useFitHeight } from '@/stores/app-layout'; import { useFitHeight } from '@/stores/app-layout';
import { notImplemented } from '@/utils/utils';
import { ToolbarConstituents } from './toolbar-constituents'; import { ToolbarConstituents } from './toolbar-constituents';
@ -27,6 +28,7 @@ export function ViewSchema({ schemaID, isMutable }: ViewSchemaProps) {
schema={schema} schema={schema}
activeCst={activeCst} activeCst={activeCst}
isMutable={isMutable} isMutable={isMutable}
onEditActive={notImplemented}
setActive={setActiveID} setActive={setActiveID}
resetActive={() => setActiveID(null)} resetActive={() => setActiveID(null)}
/> />

View File

@ -6,7 +6,6 @@ import { infoMsg } from '@/utils/labels';
import { import {
type ICheckConstituentaDTO, type ICheckConstituentaDTO,
type IConstituentaBasicsDTO,
type IConstituentaCreatedResponse, type IConstituentaCreatedResponse,
type IConstituentaList, type IConstituentaList,
type ICreateConstituentaDTO, type ICreateConstituentaDTO,
@ -14,12 +13,10 @@ import {
type IInlineSynthesisDTO, type IInlineSynthesisDTO,
type IMoveConstituentsDTO, type IMoveConstituentsDTO,
type IProduceStructureResponse, type IProduceStructureResponse,
type IRenameConstituentaDTO,
type IRSFormDTO, type IRSFormDTO,
type IRSFormUploadDTO, type IRSFormUploadDTO,
type ISubstitutionsDTO, type ISubstitutionsDTO,
type IUpdateConstituentaDTO, type IUpdateConstituentaDTO,
schemaConstituentaBasics,
schemaConstituentaCreatedResponse, schemaConstituentaCreatedResponse,
schemaExpressionParse, schemaExpressionParse,
schemaProduceStructureResponse, schemaProduceStructureResponse,
@ -74,8 +71,8 @@ export const rsformsApi = {
} }
}), }),
updateConstituenta: ({ itemID, data }: { itemID: number; data: IUpdateConstituentaDTO }) => updateConstituenta: ({ itemID, data }: { itemID: number; data: IUpdateConstituentaDTO }) =>
axiosPatch<IUpdateConstituentaDTO, IConstituentaBasicsDTO>({ axiosPatch<IUpdateConstituentaDTO, IRSFormDTO>({
schema: schemaConstituentaBasics, schema: schemaRSForm,
endpoint: `/api/rsforms/${itemID}/update-cst`, endpoint: `/api/rsforms/${itemID}/update-cst`,
request: { request: {
data: data, data: data,
@ -91,15 +88,6 @@ export const rsformsApi = {
successMessage: infoMsg.constituentsDestroyed(data.items.length) successMessage: infoMsg.constituentsDestroyed(data.items.length)
} }
}), }),
renameConstituenta: ({ itemID, data }: { itemID: number; data: IRenameConstituentaDTO }) =>
axiosPatch<IRenameConstituentaDTO, IConstituentaCreatedResponse>({
schema: schemaConstituentaCreatedResponse,
endpoint: `/api/rsforms/${itemID}/rename-cst`,
request: {
data: data,
successMessage: infoMsg.changesSaved
}
}),
substituteConstituents: ({ itemID, data }: { itemID: number; data: ISubstitutionsDTO }) => substituteConstituents: ({ itemID, data }: { itemID: number; data: ISubstitutionsDTO }) =>
axiosPatch<ISubstitutionsDTO, IRSFormDTO>({ axiosPatch<ISubstitutionsDTO, IRSFormDTO>({
schema: schemaRSForm, schema: schemaRSForm,

View File

@ -64,9 +64,6 @@ export type IConstituentaCreatedResponse = z.infer<typeof schemaConstituentaCrea
/** Represents data, used in updating persistent attributes in {@link IConstituenta}. */ /** Represents data, used in updating persistent attributes in {@link IConstituenta}. */
export type IUpdateConstituentaDTO = z.infer<typeof schemaUpdateConstituenta>; export type IUpdateConstituentaDTO = z.infer<typeof schemaUpdateConstituenta>;
/** Represents data, used in renaming {@link IConstituenta}. */
export type IRenameConstituentaDTO = z.infer<typeof schemaRenameConstituenta>;
/** Represents data, used in ordering a list of {@link IConstituenta}. */ /** Represents data, used in ordering a list of {@link IConstituenta}. */
export interface IMoveConstituentsDTO { export interface IMoveConstituentsDTO {
items: number[]; items: number[];
@ -344,6 +341,8 @@ export const schemaConstituentaCreatedResponse = z.strictObject({
export const schemaUpdateConstituenta = z.strictObject({ export const schemaUpdateConstituenta = z.strictObject({
target: z.number(), target: z.number(),
item_data: z.strictObject({ item_data: z.strictObject({
alias: z.string().optional(),
cst_type: schemaCstType.optional(),
convention: z.string().optional(), convention: z.string().optional(),
definition_formal: z.string().optional(), definition_formal: z.string().optional(),
definition_raw: z.string().optional(), definition_raw: z.string().optional(),
@ -352,12 +351,6 @@ export const schemaUpdateConstituenta = z.strictObject({
}) })
}); });
export const schemaRenameConstituenta = z.strictObject({
target: z.number(),
alias: z.string(),
cst_type: schemaCstType
});
export const schemaProduceStructureResponse = z.strictObject({ export const schemaProduceStructureResponse = z.strictObject({
cst_list: z.array(z.number()), cst_list: z.array(z.number()),
schema: schemaRSForm schema: schemaRSForm

View File

@ -1,33 +0,0 @@
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 IRenameConstituentaDTO } from './types';
export const useRenameConstituenta = () => {
const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'rename-constituenta'],
mutationFn: rsformsApi.renameConstituenta,
onSuccess: async data => {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);
updateTimestamp(data.schema.id);
await Promise.allSettled([
client.invalidateQueries({ queryKey: [KEYS.oss] }),
client.invalidateQueries({
queryKey: [rsformsApi.baseKey],
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== String(data.schema.id)
})
]);
},
onError: () => client.invalidateQueries()
});
return {
renameConstituenta: (data: { itemID: number; data: IRenameConstituentaDTO }) => mutation.mutateAsync(data)
};
};

View File

@ -13,12 +13,16 @@ export const useUpdateConstituenta = () => {
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'update-constituenta'], mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'update-constituenta'],
mutationFn: rsformsApi.updateConstituenta, mutationFn: rsformsApi.updateConstituenta,
onSuccess: async (_, variables) => { onSuccess: async (data, _) => {
updateTimestamp(variables.itemID); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
updateTimestamp(data.id);
await Promise.allSettled([ await Promise.allSettled([
client.invalidateQueries({ queryKey: [KEYS.oss] }), client.invalidateQueries({ queryKey: [KEYS.oss] }),
client.invalidateQueries({ queryKey: [rsformsApi.baseKey] }) client.invalidateQueries({
queryKey: [rsformsApi.baseKey],
predicate: query => query.queryKey.length > 2 && query.queryKey[2] !== String(data.id)
})
]); ]);
}, },
onError: () => client.invalidateQueries() onError: () => client.invalidateQueries()

View File

@ -9,8 +9,8 @@ import { TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal'; import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { type CstType, type IRenameConstituentaDTO, schemaRenameConstituenta } from '../backend/types'; import { type CstType, type IUpdateConstituentaDTO, schemaUpdateConstituenta } from '../backend/types';
import { useRenameConstituenta } from '../backend/use-rename-constituenta'; import { useUpdateConstituenta } from '../backend/use-update-constituenta';
import { SelectCstType } from '../components/select-cst-type'; import { SelectCstType } from '../components/select-cst-type';
import { type IConstituenta, type IRSForm } from '../models/rsform'; import { type IConstituenta, type IRSForm } from '../models/rsform';
import { generateAlias, validateNewAlias } from '../models/rsform-api'; import { generateAlias, validateNewAlias } from '../models/rsform-api';
@ -22,27 +22,29 @@ export interface DlgRenameCstProps {
export function DlgRenameCst() { export function DlgRenameCst() {
const { schema, target } = useDialogsStore(state => state.props as DlgRenameCstProps); const { schema, target } = useDialogsStore(state => state.props as DlgRenameCstProps);
const { renameConstituenta: cstRename } = useRenameConstituenta(); const { updateConstituenta: cstUpdate } = useUpdateConstituenta();
const { register, setValue, handleSubmit, control } = useForm<IRenameConstituentaDTO>({ const { register, setValue, handleSubmit, control } = useForm<IUpdateConstituentaDTO>({
resolver: zodResolver(schemaRenameConstituenta), resolver: zodResolver(schemaUpdateConstituenta),
defaultValues: { defaultValues: {
target: target.id, target: target.id,
alias: target.alias, item_data: {
cst_type: target.cst_type alias: target.alias,
cst_type: target.cst_type
}
} }
}); });
const alias = useWatch({ control, name: 'alias' }); const alias = useWatch({ control, name: 'item_data.alias' })!;
const cst_type = useWatch({ control, name: 'cst_type' }); const cst_type = useWatch({ control, name: 'item_data.cst_type' })!;
const isValid = alias !== target.alias && validateNewAlias(alias, cst_type, schema); const isValid = alias !== target.alias && validateNewAlias(alias, cst_type, schema);
function onSubmit(data: IRenameConstituentaDTO) { function onSubmit(data: IUpdateConstituentaDTO) {
return cstRename({ itemID: schema.id, data: data }); return cstUpdate({ itemID: schema.id, data: data });
} }
function handleChangeType(newType: CstType) { function handleChangeType(newType: CstType) {
setValue('cst_type', newType); setValue('item_data.cst_type', newType);
setValue('alias', generateAlias(newType, schema), { shouldValidate: true }); setValue('item_data.alias', generateAlias(newType, schema), { shouldValidate: true });
} }
return ( return (
@ -63,7 +65,7 @@ export function DlgRenameCst() {
/> />
<TextInput <TextInput
id='dlg_cst_alias' // id='dlg_cst_alias' //
{...register('alias')} {...register('item_data.alias')}
dense dense
label='Имя' label='Имя'
className='w-28' className='w-28'