diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/resource.test.ts b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/resource.test.ts index 0b51c70f85d..beec6f4d451 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/resource.test.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/resource.test.ts @@ -231,18 +231,18 @@ theories(parseJavaClassName, [ ]); describe('getCarryOverPreference', () => { test('default carry over fields', () => - expect(getCarryOverPreference(tables.SpQuery, true)).toEqual( + expect(getCarryOverPreference(tables.SpQuery, true, false)).toEqual( getFieldsToClone(tables.SpQuery) )); test('customize carry over fields', () => { userPreferences.set('form', 'preferences', 'carryForward', { Locality: ['localityName', 'text1'], }); - expect(getCarryOverPreference(tables.Locality, false)).toEqual([ + expect(getCarryOverPreference(tables.Locality, false, false)).toEqual([ 'localityName', 'text1', ]); - expect(getCarryOverPreference(tables.SpQuery, true)).toEqual( + expect(getCarryOverPreference(tables.SpQuery, true, false)).toEqual( getFieldsToClone(tables.SpQuery) ); }); @@ -289,7 +289,7 @@ test('getFieldsToNotClone', () => { (name) => name !== 'text1' ) as RA>, }); - expect(getFieldsToNotClone(tables.CollectionObject, true)).toEqual([ + expect(getFieldsToNotClone(tables.CollectionObject, true, false)).toEqual([ 'actualTotalCountAmt', 'catalogNumber', 'timestampModified', @@ -302,7 +302,7 @@ test('getFieldsToNotClone', () => { 'currentDetermination', 'projects', ]); - expect(getFieldsToNotClone(tables.CollectionObject, false)).toEqual([ + expect(getFieldsToNotClone(tables.CollectionObject, false, false)).toEqual([ 'actualTotalCountAmt', 'catalogNumber', 'timestampModified', diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/legacyTypes.ts b/specifyweb/frontend/js_src/lib/components/DataModel/legacyTypes.ts index cfd05c50528..bf0d9ee2289 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/legacyTypes.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/legacyTypes.ts @@ -177,7 +177,10 @@ export type SpecifyResource = { viewUrl(): string; isNew(): boolean; isBeingInitialized(): boolean; - clone(cloneAll: boolean): Promise>; + clone( + cloneAll: boolean, + isBulkCarry?: boolean + ): Promise>; // eslint-disable-next-line @typescript-eslint/naming-convention toJSON(): SerializedRecord; getRelatedObjectCount( diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/resource.ts b/specifyweb/frontend/js_src/lib/components/DataModel/resource.ts index d66501430cf..581e2876960 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/resource.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/resource.ts @@ -233,9 +233,10 @@ export const parseJavaClassName = (className: string): string => export function getFieldsToNotClone( table: SpecifyTable, - cloneAll: boolean + cloneAll: boolean, + isBulkCarry: boolean ): RA { - const fieldsToClone = getCarryOverPreference(table, cloneAll); + const fieldsToClone = getCarryOverPreference(table, cloneAll, isBulkCarry); const uniqueFields = getUniqueFields(table); return table.fields .map(({ name }) => name) @@ -247,11 +248,16 @@ export function getFieldsToNotClone( function getCarryOverPreference( table: SpecifyTable, - cloneAll: boolean + cloneAll: boolean, + isBulkCarry: boolean ): RA { const config: Partial>> = cloneAll ? {} - : userPreferences.get('form', 'preferences', 'carryForward'); + : userPreferences.get( + 'form', + 'preferences', + isBulkCarry ? 'bulkCarryForward' : 'carryForward' + ); return config?.[table.name] ?? getFieldsToClone(table); } diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts b/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts index 67b1c07982b..5fb629d22ed 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts @@ -140,12 +140,14 @@ export const ResourceBase = Backbone.Model.extend({ handleChanged() { this.needsSaved = true; }, - async clone(cloneAll = false) { + async clone(cloneAll = false, isBulkCarry = false) { const self = this; - const exemptFields = getFieldsToNotClone(this.specifyTable, cloneAll).map( - (fieldName) => fieldName.toLowerCase() - ); + const exemptFields = getFieldsToNotClone( + this.specifyTable, + cloneAll, + isBulkCarry + ).map((fieldName) => fieldName.toLowerCase()); const newResource = new this.constructor( removeKey(this.attributes, ...specialFields, ...exemptFields), diff --git a/specifyweb/frontend/js_src/lib/components/FormMeta/CarryForward.tsx b/specifyweb/frontend/js_src/lib/components/FormMeta/CarryForward.tsx index d9578a9f250..3b7e3d5dcac 100644 --- a/specifyweb/frontend/js_src/lib/components/FormMeta/CarryForward.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormMeta/CarryForward.tsx @@ -88,10 +88,12 @@ export function CarryForwardConfig({ table, parentTable, type, + isBulkConfig, }: { readonly table: SpecifyTable; readonly parentTable: SpecifyTable | undefined; readonly type: 'button' | 'cog'; + readonly isBulkConfig?: boolean; }): JSX.Element | null { const [isOpen, handleOpen, handleClose] = useBooleanState(); const [globalEnabled, setGlobalEnabled] = userPreferences.use( @@ -128,9 +130,12 @@ export function CarryForwardConfig({ onClick={handleOpen} /> )} - {isCarryForwardEnabled ? : null} + {isCarryForwardEnabled ? ( + + ) : null} {isOpen && ( ): RA => */ function BulkCloneConfig({ table, + parentTable, }: { readonly table: SpecifyTable; + readonly parentTable: SpecifyTable | undefined; }): JSX.Element | null { const [globalBulkEnabled, setGlobalBulkEnabled] = userPreferences.use( 'form', @@ -160,16 +167,35 @@ function BulkCloneConfig({ const isBulkCarryEnabled = globalBulkEnabled.includes(table.name); + const [isOpen, handleOpen, handleClose] = useBooleanState(); + return tableValidForBulkClone(table) ? ( - - - setGlobalBulkEnabled(toggleItem(globalBulkEnabled, table.name)) - } - /> - {formsText.bulkCarryForwardEnabled()} - + <> + + + setGlobalBulkEnabled(toggleItem(globalBulkEnabled, table.name)) + } + /> + {formsText.bulkCarryForwardEnabled()} + + {icons.cog} + + + {isOpen && ( + + )} + ) : null; } @@ -189,21 +215,25 @@ function CarryForwardConfigDialog({ table, parentTable, onClose: handleClose, + isBulkConfig, }: { readonly table: SpecifyTable; readonly parentTable: SpecifyTable | undefined; readonly onClose: () => void; + readonly isBulkConfig?: boolean; }): JSX.Element { const [showHiddenFields, setShowHiddenFields] = userPreferences.use( 'form', 'preferences', - 'carryForwardShowHidden' + isBulkConfig === true + ? 'bulkCarryForwardShowHidden' + : 'carryForwardShowHidden' ); const [globalConfig, setGlobalConfig] = userPreferences.use( 'form', 'preferences', - 'carryForward' + isBulkConfig === true ? 'bulkCarryForward' : 'carryForward' ); const uniqueFields = getUniqueFields(table); @@ -300,9 +330,15 @@ function CarryForwardConfigDialog({ } - header={formsText.carryForwardTableSettingsDescription({ - tableName: table.label, - })} + header={ + isBulkConfig === true + ? formsText.bulkCarryForwardTableSettingsDescription({ + tableName: table.label, + }) + : formsText.carryForwardTableSettingsDescription({ + tableName: table.label, + }) + } onClose={handleClose} >
@@ -311,6 +347,7 @@ function CarryForwardConfigDialog({ carryForward={config} fields={literalFields} header={schemaText.fields()} + isBulkConfig={isBulkConfig} table={table} uniqueFields={uniqueFields} onChange={handleChange} @@ -319,6 +356,7 @@ function CarryForwardConfigDialog({ carryForward={config} fields={relationships} header={schemaText.relationships()} + isBulkConfig={isBulkConfig} table={table} uniqueFields={uniqueFields} onChange={handleChange} @@ -343,6 +381,7 @@ function CarryForwardCategory({ uniqueFields, carryForward, onChange: handleChange, + isBulkConfig, }: { readonly header: string; readonly table: SpecifyTable; @@ -350,6 +389,7 @@ function CarryForwardCategory({ readonly uniqueFields: RA; readonly carryForward: RA; readonly onChange: (carryForward: RA) => void; + readonly isBulkConfig?: boolean; }): JSX.Element | null { return fields.length > 0 ? ( <> @@ -357,6 +397,9 @@ function CarryForwardCategory({
    {fields.map((field) => { const isUnique = uniqueFields.includes(field.name); + const isRequired = + isBulkConfig === true && + (field.localization.isrequired || field.overrides.isRequired); return (
  • { const dependents = filterArray( Object.entries(dependentFields()) @@ -389,6 +432,7 @@ function CarryForwardCategory({ {field.isRelationship && field.isDependent() && !isUnique ? ( ({ const clonePromises = Array.from( { length: carryForwardAmount }, async () => { - const clonedResource = await resource.clone(false); + const clonedResource = await resource.clone( + false, + true + ); clonedResource.set('catalogNumber', wildCard as never); return clonedResource; } diff --git a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx index 307861ab5c7..00d2f28acd7 100644 --- a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx +++ b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx @@ -1170,6 +1170,18 @@ export const userPreferenceDefinitions = { renderer: f.never, container: 'div', }), + bulkCarryForward: definePref<{ + readonly [TABLE_NAME in keyof Tables]?: RA< + TableFields + >; + }>({ + title: localized('_bulkCarryForward'), + requiresReload: false, + visible: false, + defaultValue: {}, + renderer: f.never, + container: 'div', + }), enableCarryForward: definePref>({ title: localized('_enableCarryForward'), requiresReload: false, @@ -1242,6 +1254,14 @@ export const userPreferenceDefinitions = { type: 'java.lang.Boolean', container: 'div', }), + bulkCarryForwardShowHidden: definePref({ + title: localized('_bulkCarryForwardShowHidden'), + requiresReload: false, + visible: false, + defaultValue: false, + type: 'java.lang.Boolean', + container: 'div', + }), }, }, }, diff --git a/specifyweb/frontend/js_src/lib/localization/forms.ts b/specifyweb/frontend/js_src/lib/localization/forms.ts index c036fd0747d..9134ba47354 100644 --- a/specifyweb/frontend/js_src/lib/localization/forms.ts +++ b/specifyweb/frontend/js_src/lib/localization/forms.ts @@ -860,6 +860,9 @@ export const formsText = createDictionary({ 'uk-ua': 'Налаштуйте поля для перенесення', 'de-ch': 'Konfigurieren der zu übertragenden Felder', }, + bulkCarryForwardSettingsDescription: { + 'en-us': 'Configure fields to bulk carry forward', + }, carryForwardTableSettingsDescription: { 'en-us': 'Configure fields to carry forward ({tableName:string})', 'ru-ru': 'Настройте поля для переноса ({tableName:string})', @@ -869,6 +872,9 @@ export const formsText = createDictionary({ 'de-ch': 'Konfigurieren Sie die zu übertragenden Felder ({tableName:string})', }, + bulkCarryForwardTableSettingsDescription: { + 'en-us': 'Configure fields to bulk carry forward ({tableName:string})', + }, carryForwardUniqueField: { 'en-us': 'This field must be unique. It can not be carried over', 'ru-ru': 'Это поле должно быть уникальным. Его нельзя переносить',