88
99class SendLicenseExpiryWarnings extends Command
1010{
11- protected $ signature = 'licenses:send-expiry-warnings ' ;
11+ protected $ signature = 'licenses:send-expiry-warnings {--catch-up : Send missed warnings for licenses within warning windows} ' ;
1212
1313 protected $ description = 'Send expiry warning emails for licenses that are expiring soon ' ;
1414
1515 public function handle (): int
1616 {
1717 $ warningDays = [30 , 7 , 1 ];
1818 $ totalSent = 0 ;
19+ $ catchUp = $ this ->option ('catch-up ' );
20+
21+ if ($ catchUp ) {
22+ $ this ->info ('Running in catch-up mode - sending missed warnings... ' );
23+ }
1924
2025 foreach ($ warningDays as $ days ) {
21- $ sent = $ this ->sendWarningsForDays ($ days );
26+ $ sent = $ this ->sendWarningsForDays ($ days, $ catchUp );
2227 $ totalSent += $ sent ;
2328
2429 $ this ->info ("Sent {$ sent } warning emails for licenses expiring in {$ days } day(s) " );
@@ -29,24 +34,38 @@ public function handle(): int
2934 return Command::SUCCESS ;
3035 }
3136
32- private function sendWarningsForDays (int $ days ): int
37+ private function sendWarningsForDays (int $ days, bool $ catchUp = false ): int
3338 {
34- $ targetDate = now ()->addDays ($ days )->startOfDay ();
3539 $ sent = 0 ;
3640
37- // Find licenses that:
38- // 1. Expire on the target date
39- // 2. Don't have an active subscription (legacy licenses)
40- // 3. Haven't been sent a warning for this specific day count recently
41- $ licenses = License::query ()
42- ->whereDate ('expires_at ' , $ targetDate )
41+ $ query = License::query ()
4342 ->whereNull ('subscription_item_id ' ) // Legacy licenses without subscriptions
44- ->whereDoesntHave ('expiryWarnings ' , function ($ query ) use ($ days ) {
45- $ query ->where ('warning_days ' , $ days )
46- ->where ('sent_at ' , '>= ' , now ()->subHours (23 )); // Prevent duplicate emails within 23 hours
47- })
48- ->with ('user ' )
49- ->get ();
43+ ->with ('user ' );
44+
45+ if ($ catchUp ) {
46+ // Catch-up mode: find licenses that are within the warning window but haven't received this warning yet
47+ // For 30-day: expires within 30 days (but more than 7 days to avoid overlap)
48+ // For 7-day: expires within 7 days (but more than 1 day)
49+ // For 1-day: expires within 1 day (but hasn't expired yet)
50+ $ warningThresholds = [30 => 7 , 7 => 1 , 1 => 0 ];
51+ $ lowerBound = $ warningThresholds [$ days ] ?? 0 ;
52+
53+ $ query ->where ('expires_at ' , '> ' , now ()->addDays ($ lowerBound )->startOfDay ())
54+ ->where ('expires_at ' , '<= ' , now ()->addDays ($ days )->endOfDay ())
55+ ->whereDoesntHave ('expiryWarnings ' , function ($ q ) use ($ days ) {
56+ $ q ->where ('warning_days ' , $ days );
57+ });
58+ } else {
59+ // Normal mode: only licenses expiring on the exact target date
60+ $ targetDate = now ()->addDays ($ days )->startOfDay ();
61+ $ query ->whereDate ('expires_at ' , $ targetDate )
62+ ->whereDoesntHave ('expiryWarnings ' , function ($ q ) use ($ days ) {
63+ $ q ->where ('warning_days ' , $ days )
64+ ->where ('sent_at ' , '>= ' , now ()->subHours (23 )); // Prevent duplicate emails within 23 hours
65+ });
66+ }
67+
68+ $ licenses = $ query ->get ();
5069
5170 foreach ($ licenses as $ license ) {
5271 if ($ license ->user ) {
0 commit comments