@@ -43,6 +43,12 @@ export interface CancelLeavePayload {
4343 dates : string [ ] ; // YYYY-MM-DD
4444}
4545
46+ interface LeaveProcessingFailure {
47+ date : string ;
48+ stage : "calendar" | "harvest" | "cancel" | "validation" ;
49+ message : string ;
50+ }
51+
4652// ─── Config ─────────────────────────────────────────────
4753
4854export interface WorkerConfig {
@@ -179,10 +185,18 @@ export class BackgroundWorker {
179185 if ( ! user ) {
180186 console . error ( `[worker] user ${ action . user_id } not found for action ${ action . id } ` ) ;
181187 updatePendingActionStatus ( this . db , action . id , "failed" ) ;
188+ await this . notifyFailed ( action , [
189+ {
190+ date : payload . dates . join ( ", " ) ,
191+ stage : "validation" ,
192+ message : `User ${ action . user_id } no longer exists in Jadoo's database.` ,
193+ } ,
194+ ] ) ;
182195 return ;
183196 }
184197
185198 let allSucceeded = true ;
199+ const failures : LeaveProcessingFailure [ ] = [ ] ;
186200
187201 for ( const date of payload . dates ) {
188202 // Upsert a leave record in 'confirmed' state
@@ -196,6 +210,7 @@ export class BackgroundWorker {
196210 status : "confirmed" ,
197211 } ) ;
198212
213+ let stage : LeaveProcessingFailure [ "stage" ] = "calendar" ;
199214 try {
200215 // Sync to Calendar
201216 const start = new Date ( `${ date } T00:00:00` ) ;
@@ -210,6 +225,7 @@ export class BackgroundWorker {
210225 // Sync to Harvest (only if user has a Harvest mapping)
211226 let harvestEntryId : number | null = null ;
212227 if ( user . harvest_user_id ) {
228+ stage = "harvest" ;
213229 harvestEntryId = await this . harvest . createTimeEntry ( {
214230 harvestUserId : user . harvest_user_id ,
215231 date,
@@ -229,6 +245,8 @@ export class BackgroundWorker {
229245 const msg = err instanceof Error ? err . message : String ( err ) ;
230246 const retryCount = incrementLeaveRecordRetry ( this . db , record . id , msg ) ;
231247
248+ failures . push ( { date, stage, message : msg } ) ;
249+
232250 if ( retryCount >= this . maxRetries ) {
233251 updateLeaveRecordStatus ( this . db , record . id , {
234252 status : "failed" ,
@@ -263,7 +281,7 @@ export class BackgroundWorker {
263281 } else {
264282 // All dates either completed or failed
265283 updatePendingActionStatus ( this . db , action . id , "failed" ) ;
266- await this . notifyFailed ( action ) ;
284+ await this . notifyFailed ( action , failures , payload ) ;
267285 }
268286 }
269287 }
@@ -274,6 +292,13 @@ export class BackgroundWorker {
274292 if ( ! user ) {
275293 console . error ( `[worker] user ${ action . user_id } not found for action ${ action . id } ` ) ;
276294 updatePendingActionStatus ( this . db , action . id , "failed" ) ;
295+ await this . notifyFailed ( action , [
296+ {
297+ date : payload . dates . join ( ", " ) ,
298+ stage : "validation" ,
299+ message : `User ${ action . user_id } no longer exists in Jadoo's database.` ,
300+ } ,
301+ ] ) ;
277302 return ;
278303 }
279304
@@ -289,6 +314,21 @@ export class BackgroundWorker {
289314 )
290315 . all ( user . id , ...payload . dates ) ;
291316
317+ if ( records . length === 0 ) {
318+ updatePendingActionStatus ( this . db , action . id , "failed" ) ;
319+ await this . notifyFailed (
320+ action ,
321+ payload . dates . map ( ( date ) => ( {
322+ date,
323+ stage : "validation" ,
324+ message : "No matching leave record was found to cancel." ,
325+ } ) ) ,
326+ ) ;
327+ return ;
328+ }
329+
330+ const failures : LeaveProcessingFailure [ ] = [ ] ;
331+
292332 for ( const record of records ) {
293333 try {
294334 if ( record . calendar_event_id ) {
@@ -301,13 +341,20 @@ export class BackgroundWorker {
301341 } catch ( err ) {
302342 const msg = err instanceof Error ? err . message : String ( err ) ;
303343 console . error ( `[worker] failed to cancel leave record ${ record . id } : ${ msg } ` ) ;
344+ failures . push ( { date : record . date , stage : "cancel" , message : msg } ) ;
304345 updateLeaveRecordStatus ( this . db , record . id , {
305346 status : "failed" ,
306347 errorMessage : msg ,
307348 } ) ;
308349 }
309350 }
310351
352+ if ( failures . length > 0 ) {
353+ updatePendingActionStatus ( this . db , action . id , "failed" ) ;
354+ await this . notifyFailed ( action , failures ) ;
355+ return ;
356+ }
357+
311358 updatePendingActionStatus ( this . db , action . id , "completed" ) ;
312359 await this . notifyCancelled ( action , payload ) ;
313360 }
@@ -338,20 +385,47 @@ export class BackgroundWorker {
338385 }
339386 }
340387
341- private async notifyFailed ( action : DbPendingAction ) : Promise < void > {
388+ private async notifyFailed (
389+ action : DbPendingAction ,
390+ failures : LeaveProcessingFailure [ ] ,
391+ payload ?: CreateLeavePayload ,
392+ ) : Promise < void > {
342393 const channel = action . slack_channel_id ;
343394 const ts = action . slack_bot_message_ts ;
344395 if ( ! channel || ! ts ) return ;
345396
397+ const totalDates = payload ?. dates . length ;
398+ const uniqueFailures = failures . slice ( 0 , 5 ) . map ( ( failure ) => {
399+ const stageLabel =
400+ failure . stage === "calendar"
401+ ? "Calendar"
402+ : failure . stage === "harvest"
403+ ? "Harvest"
404+ : failure . stage === "cancel"
405+ ? "Cancellation"
406+ : "Validation" ;
407+ return `• ${ failure . date } : ${ stageLabel } — ${ failure . message } ` ;
408+ } ) ;
409+ const summary =
410+ totalDates && totalDates > failures . length
411+ ? `Some dates may have succeeded, but ${ failures . length } date(s) failed.`
412+ : "The request could not be completed." ;
413+ const message = [
414+ "❌ Leave processing failed." ,
415+ summary ,
416+ "Please try again or contact an admin." ,
417+ ...uniqueFailures ,
418+ ] . join ( "\n" ) ;
419+
346420 try {
347421 await this . slack . updateMessage ( channel , ts , {
348- text : "❌ Leave sync failed after retries. Please contact an admin." ,
422+ text : message ,
349423 blocks : [
350424 {
351425 type : "section" ,
352426 text : {
353427 type : "mrkdwn" ,
354- text : " ❌ *Leave sync failed* after retries. Please contact an admin." ,
428+ text : ` ❌ *Leave processing failed*\n ${ summary } \nPlease try again or contact an admin.${ uniqueFailures . length ? `\n\n ${ uniqueFailures . join ( "\n" ) } ` : "" } ` ,
355429 } ,
356430 } ,
357431 ] ,
0 commit comments