Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 129 additions & 4 deletions specifyweb/frontend/js_src/lib/components/Forms/BulkCarryForward.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,30 @@ import {
formatterToParser,
getValidationAttributes,
} from '../../utils/parser/definitions';
import type { RA } from '../../utils/types';
import type { RA, WritableArray } from '../../utils/types';
import { keysToLowerCase } from '../../utils/utils';
import { Button } from '../Atoms/Button';
import { Input, Label } from '../Atoms/Form';
import { cogTypes } from '../DataModel/helpers';
import type { AnySchema, SerializedRecord } from '../DataModel/helperTypes';
import type { SpecifyResource } from '../DataModel/legacyTypes';
import {
deserializeResource,
serializeResource,
} from '../DataModel/serializers';
import type { LiteralField } from '../DataModel/specifyField';
import type { LiteralField, Relationship } from '../DataModel/specifyField';
import type { Collection } from '../DataModel/specifyTable';
import { tables } from '../DataModel/tables';
import type { CollectionObject } from '../DataModel/types';
import { tableValidForBulkClone } from '../FormMeta/CarryForward';
import { Dialog } from '../Molecules/Dialog';

export type BulkCarryRangeError =
| boolean
| 'ExistingNumbers'
| 'InvalidRange'
| 'LimitExceeded';
| 'LimitExceeded'
| 'UnsupportedRelationships';

const bulkCarryLimit = 500;

Expand Down Expand Up @@ -87,10 +91,25 @@ function useBulkCarryForwardRange<SCHEMA extends AnySchema>(
React.useState<BulkCarryRangeError>(false);
const [bulkCarryRangeInvalidNumbers, setBulkCarryRangeInvalidNumbers] =
React.useState<RA<string> | undefined>(undefined);
const [
bulkCarryRangeUnsupportedRelationships,
setBulkCarryRangeUnsupportedRelationships,
] = React.useState<RA<string> | undefined>(undefined);

const handleBulkCarryForward =
typeof formatter === 'object'
? async (): Promise<RA<SpecifyResource<SCHEMA>> | undefined> => {
const unsupportedRelationships = [
...getUnsupportedRelationships(resource),
...(await isInConsolidatedCog(
resource as SpecifyResource<CollectionObject>
)),
];
if (unsupportedRelationships.length > 0) {
setBulkCarryRangeBlocked('UnsupportedRelationships');
setBulkCarryRangeUnsupportedRelationships(unsupportedRelationships);
return undefined;
}
const carryForwardRangeStart = resource.get(field.name);
if (
carryForwardRangeStart === null ||
Expand Down Expand Up @@ -194,9 +213,11 @@ function useBulkCarryForwardRange<SCHEMA extends AnySchema>(
error={bulkCarryRangeBlocked}
invalidNumbers={bulkCarryRangeInvalidNumbers}
numberField={field}
unsupportedRelationships={bulkCarryRangeUnsupportedRelationships}
onClose={(): void => {
setBulkCarryRangeBlocked(false);
setBulkCarryRangeInvalidNumbers(undefined);
setBulkCarryRangeUnsupportedRelationships(undefined);
}}
/>
);
Expand Down Expand Up @@ -269,11 +290,13 @@ function useBulkCarryForwardCount<SCHEMA extends AnySchema>(
export function BulkCarryRangeBlockedDialog({
error,
invalidNumbers,
unsupportedRelationships,
numberField,
onClose: handleClose,
}: {
readonly error: BulkCarryRangeError;
readonly invalidNumbers: RA<string> | undefined;
readonly unsupportedRelationships: RA<string> | undefined;
readonly numberField: LiteralField;
readonly onClose: () => void;
}): JSX.Element {
Expand All @@ -287,7 +310,7 @@ export function BulkCarryRangeBlockedDialog({
header={formsText.carryForward()}
onClose={undefined}
>
{error == 'ExistingNumbers' ? (
{error === 'ExistingNumbers' ? (
<>
{formsText.bulkCarryForwardRangeExistingRecords({
field: numberField.label,
Expand All @@ -301,6 +324,14 @@ export function BulkCarryRangeBlockedDialog({
limit: bulkCarryLimit,
})}
</>
) : error === 'UnsupportedRelationships' ? (
<>
{formsText.bulkCarryForwardRangeUnsupportedRelationships()}
{unsupportedRelationships &&
unsupportedRelationships.map((relationship, index) => (
<p key={index}>{relationship}</p>
))}
</>
) : (
<>
{formsText.bulkCarryForwardRangeErrorDescription({
Expand All @@ -311,3 +342,97 @@ export function BulkCarryRangeBlockedDialog({
</Dialog>
);
}

/*
* Temporary fix for https://github.com/specify/specify7/issues/5022.
* REFACTOR: Remove this once issue #5022 is fixed.
*/
function getUnsupportedRelationships<SCHEMA extends AnySchema>(
resource: SpecifyResource<SCHEMA>
): RA<string> {
const unsupported: WritableArray<string> = [];

const table = resource.specifyTable;
const unsupportedRelationships = new Set([
'determinations',
'determiners',
'fundingagents',
'collectors',
'addresses',
]);

function isCollection(object: unknown): object is Collection<SCHEMA> {
return (
Boolean(object) &&
typeof object === 'object' &&
object !== null &&
'models' in object &&
'length' in object
);
}

function recursiveRelationshipCheck(
related: SpecifyResource<SCHEMA>,
relationship: Relationship
): void {
const relatedUnsupported = getUnsupportedRelationships(related);
relatedUnsupported.forEach((relatedRelationship) => {
unsupported.push(`${relationship.label} > ${relatedRelationship}`);
});
}

table.relationships.forEach((relationship): void => {
if (relationship.isDependent()) {
const relatedTable = relationship.relatedTable;
const relatedHasUnsupportedRelationships =
relatedTable.relationships.some((relatedRelationship: Relationship) =>
unsupportedRelationships.has(relatedRelationship.name)
);
// Relationship contains unsupported relationships within, check recursively.
if (relatedHasUnsupportedRelationships) {
const related = resource.getDependentResource(relationship.name);
if (related !== null && related !== undefined) {
if (isCollection(related)) {
related.models.forEach((model) =>
recursiveRelationshipCheck(model, relationship)
);
} else {
recursiveRelationshipCheck(related, relationship);
}
}
}

// This unsupported relationship cannot have more than two related records
if (unsupportedRelationships.has(relationship.name)) {
const related = resource.getDependentResource(relationship.name);
if (
related !== undefined &&
isCollection(related) &&
related.length > 1
) {
unsupported.push(relationship.label);
}
}
}
});

return unsupported;
}
/*
* Consolidated COGs aren't supported.
* REFACTOR: Remove this once issue #5022 is fixed.
*/
async function isInConsolidatedCog(
resource: SpecifyResource<CollectionObject>
): Promise<RA<string>> {
const cojo = resource.getDependentResource('cojo');
if (cojo !== null && cojo !== undefined) {
const parentCog = await cojo.rgetPromise('parentCog');
const cogType = await parentCog.rgetPromise('cogType');
if (cogType.get('type') === cogTypes.CONSOLIDATED) {
// Return 'Consolidated Collection Object Group' as error.
return [`${cogTypes.CONSOLIDATED} ${parentCog.specifyTable.label}`];
}
}
return [];
}
3 changes: 3 additions & 0 deletions specifyweb/frontend/js_src/lib/localization/forms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,9 @@ export const formsText = createDictionary({
'ru-ru': 'Диапазон превышает предел записи {limit:number}.',
'uk-ua': 'Діапазон перевищує ліміт записів {limit:number}.',
},
bulkCarryForwardRangeUnsupportedRelationships: {
'en-us': 'Some relationships with more than one record are not currently supported by Bulk Carry Forward:'
},
bulkCarryForwardRangeExistingRecords: {
'en-us': 'The following numbers for {field:string} are already being used:',
'de-ch': 'Folgende Nummern für {field:string} werden bereits verwendet:',
Expand Down