diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/businessRules.test.ts b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/businessRules.test.ts index 2441c7431cb..a3a04754ab0 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/businessRules.test.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/businessRules.test.ts @@ -118,25 +118,6 @@ describe('Collection Object business rules', () => { expect(collectionObject.get('collectingEvent')).toBeDefined(); }); - test('Save blocked when CollectionObjectType of a CollectionObject does not have same tree definition as its associated Determination -> taxon', async () => { - const collectionObject = getBaseCollectionObject(); - - const determination = - collectionObject.getDependentResource('determinations')?.models[0]; - - const { result } = renderHook(() => - useSaveBlockers(determination, tables.Determination.getField('taxon')) - ); - - await act(async () => { - await determination?.businessRuleManager?.checkField('taxon'); - }); - - expect(result.current[0]).toStrictEqual([ - 'Taxon does not belong to the same tree as this Object Type', - ]); - }); - const otherCollectionObjectTypeUrl = getResourceApiUrl( 'CollectionObjectType', 2 diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts index 8e98a9ec793..328c12df255 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts @@ -1,4 +1,3 @@ -import { formsText } from '../../localization/forms'; import { resourcesText } from '../../localization/resources'; import { f } from '../../utils/functools'; import type { BusinessRuleResult } from './businessRules'; @@ -50,7 +49,6 @@ type MappedBusinessRuleDefs = { }; const CURRENT_DETERMINATION_KEY = 'determination-isCurrent'; -const DETERMINATION_TAXON_KEY = 'determination-taxon'; const hasNoCurrentDetermination = (collection: Collection) => collection.models.length > 0 && @@ -185,14 +183,8 @@ export const businessRuleDefs: MappedBusinessRuleDefs = { const taxonTreeDefinition = fetchedTaxon.definition; const COTypeTreeDefinition = fetchedCOType.taxonTreeDef; - return taxonTreeDefinition === COTypeTreeDefinition - ? void setSaveBlockers( - currentDetermination as SpecifyResource, - currentDetermination.specifyTable.field.taxon, - [], - DETERMINATION_TAXON_KEY - ) - : resource.set('determinations', []); + if (taxonTreeDefinition !== COTypeTreeDefinition) + resource.set('determinations', []); }) .catch((error) => { console.error('Error fetching resources:', error); @@ -219,57 +211,6 @@ export const businessRuleDefs: MappedBusinessRuleDefs = { accepted === null ? taxon : getLastAccepted(accepted) ); - const related = determination.collection?.related; - if ( - related !== undefined && - related.specifyTable.name === 'CollectionObject' - ) { - const collectionObject = - related as SpecifyResource; - void f - .all({ - defaultType: - collectionObject.get('collectionObjectType') === undefined - ? collectionObject - .rgetPromise('collection') - .then(async (collection) => - collection.rgetPromise('collectionObjectType') - ) - .then((coType) => coType ?? undefined) - : undefined, - coType: collectionObject.rgetPromise( - 'collectionObjectType', - true - ), - }) - .then(({ defaultType, coType }) => { - const resolvedCoType = coType ?? defaultType; - /* - * Have to set save blockers directly here to get this working. - * Since following code has to wait for above rgetPromise to resolve, returning a Promise for validation here is too slow and - * does not get captured by business rules. - */ - if ( - resolvedCoType?.get('taxonTreeDef') === - (taxon?.get('definition') ?? '') - ) { - setSaveBlockers( - determination as SpecifyResource, - determination.specifyTable.field.taxon, - [], - DETERMINATION_TAXON_KEY - ); - } else { - setSaveBlockers( - determination as SpecifyResource, - determination.specifyTable.field.taxon, - [formsText.invalidTree()], - DETERMINATION_TAXON_KEY - ); - } - }); - } - return taxon === null ? { isValid: true, diff --git a/specifyweb/frontend/js_src/lib/components/QueryComboBox/helpers.ts b/specifyweb/frontend/js_src/lib/components/QueryComboBox/helpers.ts index 0a3651d8e8f..3fb5bb0a27a 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryComboBox/helpers.ts +++ b/specifyweb/frontend/js_src/lib/components/QueryComboBox/helpers.ts @@ -2,7 +2,7 @@ import type { RA, WritableArray } from '../../utils/types'; import { toTable, toTreeTable } from '../DataModel/helpers'; import type { AnySchema } from '../DataModel/helperTypes'; import type { SpecifyResource } from '../DataModel/legacyTypes'; -import { idFromUrl } from '../DataModel/resource'; +import { idFromUrl, strictIdFromUrl } from '../DataModel/resource'; import type { Relationship } from '../DataModel/specifyField'; import type { SpecifyTable } from '../DataModel/specifyTable'; import { tables } from '../DataModel/tables'; @@ -73,6 +73,7 @@ export function getQueryComboBoxConditions({ treeData, subViewRelationship, relatedTable, + treeDefinition, }: { readonly resource: SpecifyResource; readonly fieldName: string; @@ -80,6 +81,7 @@ export function getQueryComboBoxConditions({ readonly collectionRelationships: CollectionRelationships | undefined; readonly relatedTable: SpecifyTable; readonly subViewRelationship: Relationship | undefined; + readonly treeDefinition: string | undefined; }): RA> { const fields: WritableArray> = []; const treeResource = toTreeTable(resource); @@ -141,6 +143,20 @@ export function getQueryComboBoxConditions({ } } + if ( + resource.specifyTable === tables.Determination && + fieldName === 'fullName' && + treeDefinition !== undefined + ) { + fields.push( + QueryFieldSpec.fromPath(tables.Taxon.name, ['definition', 'id']) + .toSpQueryField() + .set('isDisplay', false) + .set('startValue', strictIdFromUrl(treeDefinition).toString()) + .set('operStart', queryFieldFilters.equal.id) + ); + } + if ( typeof collectionRelationships === 'object' && fieldName === 'collectionRelType' diff --git a/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx b/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx index 98605a30a2c..8d8f7969c1d 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx @@ -23,6 +23,11 @@ import { import { serializeResource } from '../DataModel/serializers'; import type { Relationship } from '../DataModel/specifyField'; import type { SpecifyTable } from '../DataModel/specifyTable'; +import { tables } from '../DataModel/tables'; +import type { + CollectionObject, + CollectionObjectType, +} from '../DataModel/types'; import { format, naiveFormatter } from '../Formatters/formatters'; import type { FormType } from '../FormParse'; import { ResourceView, RESTRICT_ADDING } from '../Forms/ResourceView'; @@ -247,6 +252,26 @@ export function QueryComboBox({ (typeof typeSearch === 'object' ? typeSearch?.table : undefined) ?? field.relatedTable; + const [treeDefinition] = useAsyncState( + React.useCallback( + async () => + resource?.specifyTable === tables.Determination && + resource.collection?.related?.specifyTable === tables.CollectionObject + ? (resource.collection?.related as SpecifyResource) + .rgetPromise('collectionObjectType') + .then( + ( + collectionObjectType: + | SpecifyResource + | undefined + ) => collectionObjectType?.get('taxonTreeDef') + ) + : undefined, + [resource, resource?.collection?.related?.get('collectionObjectType')] + ), + false + ); + // FEATURE: use main table field if type search is not defined const fetchSource = React.useCallback( async (value: string): Promise>> => @@ -274,6 +299,7 @@ export function QueryComboBox({ typeof treeData === 'object' ? treeData : undefined, relatedTable, subViewRelationship, + treeDefinition, }), }) ) @@ -322,6 +348,7 @@ export function QueryComboBox({ relatedCollectionId, resource, treeData, + treeDefinition, ] ); @@ -473,6 +500,7 @@ export function QueryComboBox({ : undefined, relatedTable, subViewRelationship, + treeDefinition, }) .map(serializeResource) .map(({ fieldName, startValue }) => diff --git a/specifyweb/frontend/js_src/lib/localization/forms.ts b/specifyweb/frontend/js_src/lib/localization/forms.ts index 74100c1982f..9134ba47354 100644 --- a/specifyweb/frontend/js_src/lib/localization/forms.ts +++ b/specifyweb/frontend/js_src/lib/localization/forms.ts @@ -1159,7 +1159,4 @@ export const formsText = createDictionary({ 'ru-ru': 'Номер по каталогу Числовой', 'uk-ua': 'Каталожний номер Числовий', }, - invalidTree: { - 'en-us': 'Taxon does not belong to the same tree as this Object Type', - }, } as const);