@@ -14,9 +14,36 @@ const MAX_RETRIES = 3;
1414
1515export interface UndoEntry { endpoint : string ; apiName : any ; id : string }
1616
17+ // Endpoints the Api's audit registry marks batch-capable. Only these accept X-Batch-Id;
18+ // any other route 400s when the header is present, so the batch header is scoped to just these.
19+ const BATCH_CAPABLE = [
20+ "/campuses" ,
21+ "/people" ,
22+ "/groups" ,
23+ "/groupmembers" ,
24+ "/forms" ,
25+ "/donations" ,
26+ "/households" ,
27+ "/questions" ,
28+ "/services" ,
29+ "/servicetimes" ,
30+ "/groupservicetimes" ,
31+ "/funds" ,
32+ "/donationbatches" ,
33+ "/funddonations"
34+ ] ;
35+
1736// ponytail: module-level log of every row this run inserted; reversed = FK-safe delete order
1837let undoLog : UndoEntry [ ] = [ ] ;
1938
39+ // Set only while an import is running; the request hook stamps the header on batch-capable calls.
40+ let activeBatchId : string | undefined ;
41+ const batchRequestHook = ( url : string , requestOptions : any ) => {
42+ if ( activeBatchId && BATCH_CAPABLE . some ( e => url . endsWith ( e ) ) ) {
43+ requestOptions . headers = { ...( requestOptions . headers || { } ) , "X-Batch-Id" : activeBatchId } ;
44+ }
45+ } ;
46+
2047const postInBatches = async < T extends { id ?: string } > (
2148 endpoint : string ,
2249 data : T [ ] ,
@@ -57,7 +84,9 @@ const postInBatches = async <T extends { id?: string }>(
5784 return results ;
5885} ;
5986
60- const exportToB1Db = async ( exportData : ImportDataInterface , updateProgress : ( name : string , status : string ) => void ) : Promise < UndoEntry [ ] > => {
87+ export interface ImportResult { undoLog : UndoEntry [ ] ; batchId ?: string }
88+
89+ const exportToB1Db = async ( exportData : ImportDataInterface , updateProgress : ( name : string , status : string ) => void , importSourceName ?: string ) : Promise < ImportResult > => {
6190
6291 undoLog = [ ] ;
6392 const sleep = ( milliseconds : number ) => new Promise ( resolve => setTimeout ( resolve , milliseconds ) ) ;
@@ -78,13 +107,43 @@ const exportToB1Db = async (exportData: ImportDataInterface, updateProgress: (na
78107 }
79108 } ;
80109
81- const campusResult = await exportCampuses ( exportData , runImport ) ;
82- const tmpPeople = await exportPeople ( exportData , runImport , updateProgress ) ;
83- const tmpGroups = await exportGroups ( exportData , tmpPeople , campusResult . serviceTimes , runImport , updateProgress ) ;
84- await exportAttendance ( exportData , tmpPeople , tmpGroups , campusResult . services , campusResult . serviceTimes , runImport , updateProgress ) ;
85- await exportDonations ( exportData , tmpPeople , runImport , updateProgress ) ;
86- await exportForms ( exportData , tmpPeople , tmpGroups , runImport ) ;
87- return undoLog ;
110+ let batchId : string | undefined ;
111+ try {
112+ const label = `Import from ${ importSourceName || "file" } ${ new Date ( ) . toLocaleDateString ( ) } ` ;
113+ const batch = await ApiHelper . post ( "/batches" , { label, source : "import" } , "MembershipApi" ) ;
114+ batchId = batch ?. id ;
115+ } catch ( e ) {
116+ // Batch tracking is best-effort; a failure here must not block the import itself.
117+ console . error ( "Could not create undo batch; import will not be undoable from B1Admin" , e ) ;
118+ }
119+
120+ const prevOnRequest = ApiHelper . onRequest ;
121+ if ( batchId ) {
122+ activeBatchId = batchId ;
123+ ApiHelper . onRequest = batchRequestHook ;
124+ }
125+
126+ try {
127+ const campusResult = await exportCampuses ( exportData , runImport ) ;
128+ const tmpPeople = await exportPeople ( exportData , runImport , updateProgress ) ;
129+ const tmpGroups = await exportGroups ( exportData , tmpPeople , campusResult . serviceTimes , runImport , updateProgress ) ;
130+ await exportAttendance ( exportData , tmpPeople , tmpGroups , campusResult . services , campusResult . serviceTimes , runImport , updateProgress ) ;
131+ await exportDonations ( exportData , tmpPeople , runImport , updateProgress ) ;
132+ await exportForms ( exportData , tmpPeople , tmpGroups , runImport ) ;
133+ } finally {
134+ activeBatchId = undefined ;
135+ ApiHelper . onRequest = prevOnRequest ;
136+ if ( batchId ) {
137+ try {
138+ await ApiHelper . post ( `/batches/${ batchId } /complete` , { } , "MembershipApi" ) ;
139+ console . log ( `Import recorded as batch ${ batchId } . It can be undone from B1Admin Settings → Batches.` ) ;
140+ } catch ( e ) {
141+ console . error ( "Could not close undo batch" , batchId , e ) ;
142+ }
143+ }
144+ }
145+
146+ return { undoLog, batchId } ;
88147} ;
89148
90149export const undoB1DbImport = async ( log : UndoEntry [ ] , onProgress ?: ( done : number , total : number ) => void ) => {
0 commit comments