@@ -122,12 +122,27 @@ export async function startTimeTracking(
122122 operatorId : string ,
123123 tenantId : string
124124) {
125- // Check for existing active time entries for this operator
125+ // Check for existing active time entries for this operation specifically
126+ const { data : existingForOperation } = await supabase
127+ . from ( "time_entries" )
128+ . select ( "id" )
129+ . eq ( "operation_id" , operationId )
130+ . eq ( "operator_id" , operatorId )
131+ . is ( "end_time" , null ) ;
132+
133+ // Prevent duplicate entries for same operation (race condition protection)
134+ if ( existingForOperation && existingForOperation . length > 0 ) {
135+ console . log ( "Time entry already exists for this operation, skipping duplicate" ) ;
136+ return ; // Silently succeed - entry already exists
137+ }
138+
139+ // Check for existing active time entries for this operator on OTHER operations
126140 const { data : activeEntries } = await supabase
127141 . from ( "time_entries" )
128142 . select ( "id, operation_id, operations(operation_name)" )
129143 . eq ( "operator_id" , operatorId )
130144 . eq ( "tenant_id" , tenantId )
145+ . neq ( "operation_id" , operationId ) // Exclude current operation
131146 . is ( "end_time" , null ) ;
132147
133148 if ( activeEntries && activeEntries . length > 0 ) {
@@ -169,7 +184,19 @@ export async function startTimeTracking(
169184 const startedAt = new Date ( ) . toISOString ( ) ;
170185 const isNewStart = operation . status === "not_started" ;
171186
172- // Create time entry
187+ // Create time entry - use a lock by checking again right before insert
188+ const { data : doubleCheck } = await supabase
189+ . from ( "time_entries" )
190+ . select ( "id" )
191+ . eq ( "operation_id" , operationId )
192+ . eq ( "operator_id" , operatorId )
193+ . is ( "end_time" , null ) ;
194+
195+ if ( doubleCheck && doubleCheck . length > 0 ) {
196+ console . log ( "Time entry created by concurrent request, skipping" ) ;
197+ return ;
198+ }
199+
173200 const { error : timeError } = await supabase . from ( "time_entries" ) . insert ( {
174201 operation_id : operationId ,
175202 operator_id : operatorId ,
@@ -274,16 +301,34 @@ export async function startTimeTracking(
274301}
275302
276303export async function stopTimeTracking ( operationId : string , operatorId : string ) {
277- // Find active time entry
278- const { data : entry } = await supabase
304+ // Find active time entries (may be multiple due to race conditions)
305+ const { data : entries } = await supabase
279306 . from ( "time_entries" )
280307 . select ( "id, start_time, is_paused" )
281308 . eq ( "operation_id" , operationId )
282309 . eq ( "operator_id" , operatorId )
283310 . is ( "end_time" , null )
284- . single ( ) ;
311+ . order ( "start_time" , { ascending : false } ) ;
285312
286- if ( ! entry ) throw new Error ( "No active time entry found" ) ;
313+ if ( ! entries || entries . length === 0 ) throw new Error ( "No active time entry found" ) ;
314+
315+ // Use the most recent entry
316+ const entry = entries [ 0 ] ;
317+
318+ // If there are duplicates, close them all
319+ if ( entries . length > 1 ) {
320+ console . log ( `Found ${ entries . length } duplicate time entries, closing all` ) ;
321+ const now = new Date ( ) ;
322+ for ( let i = 1 ; i < entries . length ; i ++ ) {
323+ const dupEntry = entries [ i ] ;
324+ const startTime = new Date ( dupEntry . start_time ) ;
325+ const duration = Math . round ( ( now . getTime ( ) - startTime . getTime ( ) ) / 1000 ) ;
326+ await supabase
327+ . from ( "time_entries" )
328+ . update ( { end_time : now . toISOString ( ) , duration } )
329+ . eq ( "id" , dupEntry . id ) ;
330+ }
331+ }
287332
288333 // If paused, close the current pause
289334 if ( entry . is_paused ) {
0 commit comments