diff --git a/specifyweb/backend/businessrules/rules/collectionobject_rules.py b/specifyweb/backend/businessrules/rules/collectionobject_rules.py index 40ffec39a0c..3d84088b567 100644 --- a/specifyweb/backend/businessrules/rules/collectionobject_rules.py +++ b/specifyweb/backend/businessrules/rules/collectionobject_rules.py @@ -2,6 +2,7 @@ from specifyweb.backend.businessrules.exceptions import BusinessRuleException from specifyweb.backend.businessrules.utils import get_unique_catnum_across_comp_co_coll_pref +from specifyweb.specify.api.utils import get_or_create_default_collection_object_type from specifyweb.specify.models import Component @@ -10,8 +11,15 @@ def collectionobject_pre_save(co): if co.collectionmemberid is None: co.collectionmemberid = co.collection_id - if co.collectionobjecttype is None: - co.collectionobjecttype = co.collection.collectionobjecttype + if co.collectionobjecttype is None: + if co.collection.collectionobjecttype is not None: + co.collectionobjecttype = co.collection.collectionobjecttype + elif co.pk is not None: + co.collectionobjecttype = ( + get_or_create_default_collection_object_type( + co.collection, using=co._state.db or 'default' + ) + ) agent = co.createdbyagent if agent is not None and agent.specifyuser is not None: @@ -25,4 +33,4 @@ def collectionobject_pre_save(co): if contains_component_duplicates: raise BusinessRuleException( - 'Catalog Number is already in use for another Component in this collection.') \ No newline at end of file + 'Catalog Number is already in use for another Component in this collection.') 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 26226f396f9..1ef80c1e81d 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 @@ -194,6 +194,64 @@ describe('Collection Object business rules', () => { expect(result.current[0]).toStrictEqual([]); }); + test('CollectionObject -> determinations: Save is not blocked when collection object type is missing', async () => { + const collectionObject = getBaseCollectionObject(); + collectionObject.set('collectionObjectType', null as never, { + silent: true, + }); + + const determination = + collectionObject.getDependentResource('determinations')?.models[0]; + + const { result } = renderHook(() => + useSaveBlockers(determination, tables.Determination.getField('Taxon')) + ); + + await act(async () => { + await collectionObject?.businessRuleManager?.checkField( + 'collectionObjectType' + ); + }); + + expect(result.current[0]).toStrictEqual([]); + }); + + test('CollectionObject -> determinations: Missing collection object type clears invalid determination blockers', async () => { + const collectionObject = getBaseCollectionObject(); + collectionObject.set( + 'collectionObjectType', + getResourceApiUrl('CollectionObjectType', 1) + ); + + const determination = + collectionObject.getDependentResource('determinations')?.models[0]; + + const { result } = renderHook(() => + useSaveBlockers(determination, tables.Determination.getField('Taxon')) + ); + + await act(async () => { + await collectionObject?.businessRuleManager?.checkField( + 'collectionObjectType' + ); + }); + expect(result.current[0]).toStrictEqual([ + resourcesText.invalidDeterminationTaxon(), + ]); + + collectionObject.set('collectionObjectType', null as never, { + silent: true, + }); + + await act(async () => { + await collectionObject?.businessRuleManager?.checkField( + 'collectionObjectType' + ); + }); + + expect(result.current[0]).toStrictEqual([]); + }); + test('Newly added determinations are current by default', async () => { const collectionObject = getBaseCollectionObject(); const determinations = diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts index 5b0fe4fe7d0..d6542338da6 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts @@ -181,6 +181,19 @@ export const businessRuleDefs: MappedBusinessRuleDefs = { determinations.models.map(async (det) => det.rgetPromise('taxon')) ); const coType = await resource.rgetPromise('collectionObjectType'); + + if (coType === null) { + determinations.models.forEach((determination) => { + setSaveBlockers( + determination, + determination.specifyTable.field.taxon, + [], + DETERMINATION_TAXON_KEY + ); + }); + return; + } + const coTypeTreeDef = coType.get('taxonTreeDef'); // Block save when a Determination -> Taxon does not belong to the COType's tree definition diff --git a/specifyweb/specify/api/utils.py b/specifyweb/specify/api/utils.py index 1b53ff42bd7..75fdde50871 100644 --- a/specifyweb/specify/api/utils.py +++ b/specifyweb/specify/api/utils.py @@ -29,6 +29,32 @@ def log_sqlalchemy_query(query): # Run in the storred_queries.execute file, in the execute function, right before the return statement, line 546 # from specifyweb.specify.utils import log_sqlalchemy_query; log_sqlalchemy_query(query) +def get_or_create_default_collection_object_type(collection: spmodels.Collection, using: str = "default"): + db = using or "default" + + if collection.collectionobjecttype is not None: + return collection.collectionobjecttype + + discipline_name = collection.discipline.name + taxon_tree_def_id = collection.discipline.taxontreedef_id + + if discipline_name is None or taxon_tree_def_id is None: + return None + + default_type, _ = spmodels.Collectionobjecttype.objects.using(db).get_or_create( + name=discipline_name, + collection=collection, + taxontreedef_id=taxon_tree_def_id, + ) + + type(collection).objects.using(db).filter( + pk=collection.pk, + collectionobjecttype__isnull=True, + ).update(collectionobjecttype=default_type) + collection.collectionobjecttype = default_type + + return default_type + def create_default_collection_types(apps, using="default"): db = using or "default" @@ -90,4 +116,4 @@ def get_picklists(collection: spmodels.Collection, tablename: str, fieldname: st if len(collection_picklists) > 0: picklists = collection_picklists - return picklists, schemaitem \ No newline at end of file + return picklists, schemaitem