@@ -120,6 +120,8 @@ const IMPORT_DEFAULT_TRANSLATIONS: Record<string, string> = {
120120 'grid.import.needMatchFields' : 'Select at least one field to match on.' ,
121121 'grid.import.optCreateOptions' : 'Keep unknown option values' ,
122122 'grid.import.optRunAutomations' : 'Run automations & triggers' ,
123+ 'grid.import.optTreatHistorical' : 'Import as historical data' ,
124+ 'grid.import.optTreatHistoricalHint' : '(skip state-machine checks so already-completed records import as-is)' ,
123125 'grid.import.optSkipBlankKey' : 'Skip rows with a blank match value' ,
124126 'grid.import.optBackground' : 'Import in the background' ,
125127 'grid.import.optBackgroundHint' : '(runs as an undoable job)' ,
@@ -516,6 +518,7 @@ function assembleImportRequest(
516518 matchFields : string [ ] ;
517519 createMissingOptions : boolean ;
518520 runAutomations : boolean ;
521+ treatAsHistorical ?: boolean ;
519522 skipBlankMatchKey : boolean ;
520523 dryRun ?: boolean ;
521524 /** When set, the server resolves this registered mapping and owns the
@@ -532,6 +535,7 @@ function assembleImportRequest(
532535 rows,
533536 mappingName : opts . mappingName ,
534537 runAutomations : opts . runAutomations ,
538+ ...( opts . treatAsHistorical ? { treatAsHistorical : true } : { } ) ,
535539 ...( opts . dryRun ? { dryRun : true } : { } ) ,
536540 } ;
537541 }
@@ -542,6 +546,7 @@ function assembleImportRequest(
542546 ...( opts . writeMode !== 'insert' ? { matchFields : opts . matchFields } : { } ) ,
543547 createMissingOptions : opts . createMissingOptions ,
544548 runAutomations : opts . runAutomations ,
549+ ...( opts . treatAsHistorical ? { treatAsHistorical : true } : { } ) ,
545550 skipBlankMatchKey : opts . skipBlankMatchKey ,
546551 ...( opts . dryRun ? { dryRun : true } : { } ) ,
547552 } ;
@@ -1228,6 +1233,8 @@ const ImportOptions: React.FC<{
12281233 onCreateMissingOptions : ( v : boolean ) => void ;
12291234 runAutomations : boolean ;
12301235 onRunAutomations : ( v : boolean ) => void ;
1236+ treatAsHistorical : boolean ;
1237+ onTreatAsHistorical : ( v : boolean ) => void ;
12311238 skipBlankMatchKey : boolean ;
12321239 onSkipBlankMatchKey : ( v : boolean ) => void ;
12331240 showBackground : boolean ;
@@ -1236,6 +1243,7 @@ const ImportOptions: React.FC<{
12361243} > = ( {
12371244 fields, mapping, writeMode, onWriteMode, matchFields, onToggleMatchField,
12381245 createMissingOptions, onCreateMissingOptions, runAutomations, onRunAutomations,
1246+ treatAsHistorical, onTreatAsHistorical,
12391247 skipBlankMatchKey, onSkipBlankMatchKey,
12401248 showBackground, backgroundImport, onBackgroundImport,
12411249} ) => {
@@ -1301,6 +1309,13 @@ const ImportOptions: React.FC<{
13011309 < Checkbox checked = { runAutomations } onCheckedChange = { ( v ) => onRunAutomations ( v === true ) } />
13021310 { t ( 'grid.import.optRunAutomations' ) }
13031311 </ label >
1312+ < label className = "flex items-center gap-2 text-xs" data-testid = "import-opt-treat-historical" >
1313+ < Checkbox checked = { treatAsHistorical } onCheckedChange = { ( v ) => onTreatAsHistorical ( v === true ) } />
1314+ < span >
1315+ { t ( 'grid.import.optTreatHistorical' ) }
1316+ < span className = "ml-1 text-[11px] text-muted-foreground" > { t ( 'grid.import.optTreatHistoricalHint' ) } </ span >
1317+ </ span >
1318+ </ label >
13041319 { showBackground && (
13051320 < label className = "flex items-center gap-2 text-xs" data-testid = "import-opt-background" >
13061321 < Checkbox checked = { backgroundImport } onCheckedChange = { ( v ) => onBackgroundImport ( v === true ) } />
@@ -1513,6 +1528,10 @@ export const ImportWizard: React.FC<ImportWizardProps> = ({
15131528 // Default ON: automations always ran on import before framework#2922 wired
15141529 // the flag up server-side, so preserving behavior means opt-out, not opt-in.
15151530 const [ runAutomations , setRunAutomations ] = useState ( true ) ;
1531+ // Default OFF (opt-in): a normal import must still walk the state machine —
1532+ // only an explicit "historical" import skips it so mid-lifecycle rows aren't
1533+ // rejected by initialStates (framework #3479). The strict behavior is the default.
1534+ const [ treatAsHistorical , setTreatAsHistorical ] = useState ( false ) ;
15161535 const [ skipBlankMatchKey , setSkipBlankMatchKey ] = useState ( false ) ;
15171536 // Opt-in: route this import through a background job even when the row count
15181537 // is under the async threshold. This is the only way to obtain an undoable
@@ -1876,11 +1895,11 @@ export const ImportWizard: React.FC<ImportWizardProps> = ({
18761895 // hand-mapped, field-keyed rows are for the manual path only.
18771896 mappingName ? buildSourceRows ( headers , rows , corrections ) : buildRawRows ( ) ,
18781897 {
1879- writeMode, matchFields, createMissingOptions, runAutomations, skipBlankMatchKey, dryRun,
1898+ writeMode, matchFields, createMissingOptions, runAutomations, treatAsHistorical , skipBlankMatchKey, dryRun,
18801899 ...( mappingName ? { mappingName } : { } ) ,
18811900 } ,
18821901 ) ,
1883- [ buildRawRows , mappingName , headers , rows , corrections , writeMode , matchFields , createMissingOptions , runAutomations , skipBlankMatchKey ] ) ;
1902+ [ buildRawRows , mappingName , headers , rows , corrections , writeMode , matchFields , createMissingOptions , runAutomations , treatAsHistorical , skipBlankMatchKey ] ) ;
18841903
18851904 const handleImport = useCallback ( async ( ) => {
18861905 setImporting ( true ) ; setProgress ( 0 ) ;
@@ -2146,6 +2165,8 @@ export const ImportWizard: React.FC<ImportWizardProps> = ({
21462165 onCreateMissingOptions = { setCreateMissingOptions }
21472166 runAutomations = { runAutomations }
21482167 onRunAutomations = { setRunAutomations }
2168+ treatAsHistorical = { treatAsHistorical }
2169+ onTreatAsHistorical = { setTreatAsHistorical }
21492170 skipBlankMatchKey = { skipBlankMatchKey }
21502171 onSkipBlankMatchKey = { setSkipBlankMatchKey }
21512172 showBackground = { supportsImportJob && rows . length <= ASYNC_IMPORT_THRESHOLD }
0 commit comments