1818use Workflow \V2 \Models \WorkflowHistoryEvent ;
1919use Workflow \V2 \Models \WorkflowRun ;
2020use Workflow \V2 \Models \WorkflowSchedule ;
21+ use Workflow \V2 \Models \WorkflowScheduleHistoryEvent ;
2122
2223/**
2324 * Single source of truth for workflow schedule lifecycle.
@@ -137,6 +138,13 @@ public static function createFromSpec(
137138 $ schedule ->next_fire_at = $ schedule ->computeNextFireAtWithJitter ();
138139 $ schedule ->save ();
139140
141+ self ::recordScheduleEvent ($ schedule , HistoryEventType::ScheduleCreated, [
142+ 'spec ' => is_array ($ schedule ->spec ) ? $ schedule ->spec : [],
143+ 'action ' => is_array ($ schedule ->action ) ? $ schedule ->action : [],
144+ 'overlap_policy ' => $ schedule ->overlap_policy ,
145+ 'next_fire_at ' => $ schedule ->next_fire_at ?->toIso8601String(),
146+ ]);
147+
140148 return $ schedule ;
141149 }
142150
@@ -157,6 +165,11 @@ public static function pause(WorkflowSchedule $schedule, ?string $reason = null)
157165
158166 $ schedule ->forceFill ($ updates )->save ();
159167
168+ self ::recordScheduleEvent ($ schedule , HistoryEventType::SchedulePaused, array_filter ([
169+ 'reason ' => $ reason ,
170+ 'paused_at ' => $ schedule ->paused_at ?->toIso8601String(),
171+ ], static fn (mixed $ value ): bool => $ value !== null ));
172+
160173 return $ schedule ;
161174 }
162175
@@ -174,6 +187,10 @@ public static function resume(WorkflowSchedule $schedule): WorkflowSchedule
174187 $ schedule ->next_fire_at = $ schedule ->computeNextFireAtWithJitter ();
175188 $ schedule ->save ();
176189
190+ self ::recordScheduleEvent ($ schedule , HistoryEventType::ScheduleResumed, [
191+ 'next_fire_at ' => $ schedule ->next_fire_at ?->toIso8601String(),
192+ ]);
193+
177194 return $ schedule ;
178195 }
179196
@@ -248,11 +265,21 @@ public static function update(
248265 : (int ) $ schedule ->remaining_actions ;
249266 }
250267
268+ $ changedFields = array_values (array_keys ($ updates ));
269+
251270 $ schedule ->forceFill ($ updates )->save ();
252271
253272 $ schedule ->next_fire_at = $ schedule ->computeNextFireAtWithJitter ();
254273 $ schedule ->save ();
255274
275+ self ::recordScheduleEvent ($ schedule , HistoryEventType::ScheduleUpdated, [
276+ 'changed_fields ' => $ changedFields ,
277+ 'spec ' => is_array ($ schedule ->spec ) ? $ schedule ->spec : [],
278+ 'action ' => is_array ($ schedule ->action ) ? $ schedule ->action : [],
279+ 'overlap_policy ' => $ schedule ->overlap_policy ,
280+ 'next_fire_at ' => $ schedule ->next_fire_at ?->toIso8601String(),
281+ ]);
282+
256283 return $ schedule ;
257284 }
258285
@@ -268,6 +295,11 @@ public static function delete(WorkflowSchedule $schedule): WorkflowSchedule
268295 'next_fire_at ' => null ,
269296 ])->save ();
270297
298+ self ::recordScheduleEvent ($ schedule , HistoryEventType::ScheduleDeleted, [
299+ 'reason ' => 'deleted ' ,
300+ 'deleted_at ' => $ schedule ->deleted_at ?->toIso8601String(),
301+ ]);
302+
271303 return $ schedule ;
272304 }
273305
@@ -523,14 +555,20 @@ private static function triggerForBackfill(
523555 $ schedule = WorkflowSchedule::query ()->lockForUpdate ()->findOrFail ($ schedule ->id );
524556
525557 if (! $ schedule ->status ->allowsTrigger ()) {
558+ self ::recordSkip ($ schedule , 'status_not_triggerable ' );
559+
526560 return null ;
527561 }
528562
529563 if ($ schedule ->remaining_actions !== null && $ schedule ->remaining_actions <= 0 ) {
564+ self ::recordSkip ($ schedule , 'remaining_actions_exhausted ' );
565+
530566 return null ;
531567 }
532568
533569 if (! self ::overlapAllowed ($ schedule , $ effectivePolicy )) {
570+ self ::recordSkip ($ schedule , 'overlap_policy_ ' . $ effectivePolicy ->value );
571+
534572 return null ;
535573 }
536574
@@ -564,6 +602,14 @@ private static function startRun(
564602 }
565603
566604 self ::recordScheduleTriggered ($ schedule , $ result ->runId , $ occurrenceTime );
605+ self ::recordScheduleEvent ($ schedule , HistoryEventType::ScheduleTriggered, array_filter ([
606+ 'workflow_instance_id ' => $ result ->instanceId ,
607+ 'workflow_run_id ' => $ result ->runId ,
608+ 'outcome ' => $ outcome ,
609+ 'effective_overlap_policy ' => $ effectiveOverlapPolicy ?? $ schedule ->overlap_policy ,
610+ 'trigger_number ' => (int ) $ schedule ->fires_count + 1 ,
611+ 'occurrence_time ' => $ occurrenceTime ?->format('Y-m-d\TH:i:s.uP ' ),
612+ ], static fn (mixed $ value ): bool => $ value !== null ));
567613
568614 $ schedule ->recordFire ($ result ->instanceId , $ result ->runId , $ outcome );
569615 $ schedule ->forceFill ([
@@ -583,6 +629,10 @@ private static function startRun(
583629 'deleted_at ' => now (),
584630 'next_fire_at ' => null ,
585631 ])->save ();
632+ self ::recordScheduleEvent ($ schedule , HistoryEventType::ScheduleDeleted, [
633+ 'reason ' => 'max_runs_exhausted ' ,
634+ 'deleted_at ' => $ schedule ->deleted_at ?->toIso8601String(),
635+ ]);
586636 }
587637
588638 return $ result ;
@@ -627,6 +677,23 @@ private static function recordSkip(WorkflowSchedule $schedule, string $reason):
627677 'last_skipped_at ' => now (),
628678 'skipped_trigger_count ' => ($ schedule ->skipped_trigger_count ?? 0 ) + 1 ,
629679 ])->save ();
680+
681+ self ::recordScheduleEvent ($ schedule , HistoryEventType::ScheduleTriggerSkipped, [
682+ 'reason ' => $ reason ,
683+ 'skipped_trigger_count ' => (int ) $ schedule ->skipped_trigger_count ,
684+ 'last_skipped_at ' => $ schedule ->last_skipped_at ?->toIso8601String(),
685+ ]);
686+ }
687+
688+ /**
689+ * @param array<string, mixed> $payload
690+ */
691+ private static function recordScheduleEvent (
692+ WorkflowSchedule $ schedule ,
693+ HistoryEventType $ eventType ,
694+ array $ payload = [],
695+ ): void {
696+ WorkflowScheduleHistoryEvent::record ($ schedule , $ eventType , $ payload );
630697 }
631698
632699 /**
0 commit comments