Compare commits
203 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d028087d23 | ||
|
|
b168c93b9f | ||
|
|
1e60c99c49 | ||
|
|
3fe52985fc | ||
|
|
8da8becb58 | ||
|
|
0051a86b60 | ||
|
|
f5cd3248cd | ||
|
|
9599619962 | ||
|
|
7c272b29f9 | ||
|
|
50d17c837c | ||
|
|
efe78a258c | ||
|
|
31869e3d27 | ||
|
|
dda2e21629 | ||
|
|
abd4ccb739 | ||
|
|
03ed295623 | ||
|
|
fca939f2dd | ||
|
|
b1a2eb94c1 | ||
|
|
005cef7faa | ||
|
|
cc146f5fb6 | ||
|
|
3a57ccf180 | ||
|
|
28aee541f0 | ||
|
|
32e45e16d4 | ||
|
|
6a4e65b0c3 | ||
|
|
262d7616cd | ||
|
|
647f7470bc | ||
|
|
6a39c0e79d | ||
|
|
15dff5e87b | ||
|
|
e1ef840aee | ||
|
|
19afd681b1 | ||
|
|
d0b173396c | ||
|
|
9f1adfe8cc | ||
|
|
261dc5e024 | ||
|
|
efa5bee9d4 | ||
|
|
b07a95780f | ||
|
|
19fe1bf3b1 | ||
|
|
4e905437e4 | ||
|
|
3e723f96e9 | ||
|
|
f2572f3807 | ||
|
|
389d95a10b | ||
|
|
d255fca519 | ||
|
|
93f41ef221 | ||
|
|
da84bdf0b7 | ||
|
|
25870c8f92 | ||
|
|
8174ccfcfa | ||
|
|
78f150ca81 | ||
|
|
29cb318a5d | ||
|
|
55e2d41044 | ||
|
|
b122af9c5a | ||
|
|
a2d89b96cf | ||
|
|
a3e5e8ffad | ||
|
|
8f5127e249 | ||
|
|
30468b50e2 | ||
|
|
1981c558eb | ||
|
|
725e12fc00 | ||
|
|
6bb25c5227 | ||
|
|
bacc5735b3 | ||
|
|
04f164d0e4 | ||
|
|
fcc8a3479f | ||
|
|
641c62ea6f | ||
|
|
d950435408 | ||
|
|
572224846e | ||
|
|
b06b3a0761 | ||
|
|
d87b74491b | ||
|
|
13603c3f3a | ||
|
|
2f4d8f3a6e | ||
|
|
005ff63f19 | ||
|
|
0fce8ee236 | ||
|
|
d3ae90cf99 | ||
|
|
153daf8524 | ||
|
|
e530d41f91 | ||
|
|
09bd3a37ea | ||
|
|
8d16a02eb4 | ||
|
|
06f5844fd6 | ||
|
|
dfe90153e9 | ||
|
|
0253a89125 | ||
|
|
a86bbc4613 | ||
|
|
40fe8d8d03 | ||
|
|
d619899374 | ||
|
|
fbd6c31885 | ||
|
|
52c8096b5f | ||
|
|
b963224404 | ||
|
|
aff74592d2 | ||
|
|
169ff8c1ca | ||
|
|
a4ba5c77c4 | ||
|
|
2c0ebe8c30 | ||
|
|
2cb5fa8a2d | ||
|
|
3ea898ab56 | ||
|
|
02ec112b24 | ||
|
|
e1759263f0 | ||
|
|
7d0d1b073f | ||
|
|
9b0dca1ee7 | ||
|
|
836436c73d | ||
|
|
7107ecb750 | ||
|
|
b83d8d97c7 | ||
|
|
8740f2ec0d | ||
|
|
0164fea8af | ||
|
|
38564d381e | ||
|
|
d6d914783a | ||
|
|
dfbcd95a4f | ||
|
|
72c2824975 | ||
|
|
a64cc2916e | ||
|
|
06cd2c9614 | ||
|
|
e96396ca70 | ||
|
|
cd51a8c0f1 | ||
|
|
fe35d51a31 | ||
|
|
ad761b3d27 | ||
|
|
5fb7f05e8c | ||
|
|
dc7dafeb1e | ||
|
|
c579c6f797 | ||
|
|
0f4e768852 | ||
|
|
6b20ddb354 | ||
|
|
ce58510613 | ||
|
|
b9c7d7cfb8 | ||
|
|
d7f9ce89f4 | ||
|
|
58cdfb76b8 | ||
|
|
74c1970c38 | ||
|
|
09f69b1fb5 | ||
|
|
f6fc7c2b22 | ||
|
|
fc544c3239 | ||
|
|
848bd7ce0c | ||
|
|
2e8ce70712 | ||
|
|
5f5f8cc601 | ||
|
|
1ff48c9bee | ||
|
|
e5726d221b | ||
|
|
4f0878bade | ||
|
|
23a61e9769 | ||
|
|
7b25330b4c | ||
|
|
b8804472b8 | ||
|
|
6577afc1b1 | ||
|
|
750a102e3c | ||
|
|
fdc3678022 | ||
|
|
0dd8f9fec7 | ||
|
|
e2aac9d2bb | ||
|
|
dda33a00ea | ||
|
|
946e616d14 | ||
|
|
bc7c431605 | ||
|
|
51c3e1eaf7 | ||
|
|
24475c4e89 | ||
|
|
2277546932 | ||
|
|
ac0e159f5e | ||
|
|
2a6ecc0469 | ||
|
|
ce60cb0420 | ||
|
|
a0279b5985 | ||
|
|
efb3be7b8c | ||
|
|
bc8a11aae5 | ||
|
|
a8d7ee3800 | ||
|
|
985f25628f | ||
|
|
7896477820 | ||
|
|
87d1b75ccb | ||
|
|
accb72bfc4 | ||
|
|
0e3688df22 | ||
|
|
a54e15196f | ||
|
|
80e85f5a31 | ||
|
|
e5d48a7ee3 | ||
|
|
370736acae | ||
|
|
7e49baad76 | ||
|
|
361806a473 | ||
|
|
6ca9b7fe61 | ||
|
|
ff3ab2d4b5 | ||
|
|
75f0049cf8 | ||
|
|
b7c11ddaee | ||
|
|
7941d34310 | ||
|
|
63c12f195f | ||
|
|
70159861e2 | ||
|
|
55ea88dac8 | ||
|
|
4d0ca068b8 | ||
|
|
45565d8b40 | ||
|
|
2b165749a0 | ||
|
|
51f081c9dd | ||
|
|
42f4666907 | ||
|
|
199b3e830e | ||
|
|
ada700e1a8 | ||
|
|
c027be2259 | ||
|
|
fe88ed5c8d | ||
|
|
f01148df11 | ||
|
|
48a8f4eef3 | ||
|
|
1a3ff04f85 | ||
|
|
6a64e6a26f | ||
|
|
4a5a68c95d | ||
|
|
38124040cc | ||
|
|
b00bf440e9 | ||
|
|
2f5ef9a31d | ||
|
|
139e0470e6 | ||
|
|
ae607e65be | ||
|
|
40e32b5da9 | ||
|
|
375ca023d2 | ||
|
|
fb7bbea3ce | ||
|
|
a1cfca14e2 | ||
|
|
74f49f8d0a | ||
|
|
a261fed9ed | ||
|
|
a687e8a3bc | ||
|
|
69ff7bc194 | ||
|
|
419afb0d55 | ||
|
|
d040c96e25 | ||
|
|
09fa2f47e3 | ||
|
|
3d35fc4dfd | ||
|
|
0f7ebb61cc | ||
|
|
b1255e30e8 | ||
|
|
e412673c45 | ||
|
|
fc4dda3cc6 | ||
|
|
207bdd9aba | ||
|
|
8e5a7cc285 | ||
|
|
80e03fb478 |
@ -188,7 +188,7 @@ pt.renewalReminder.days = mon,tue,wed,thu,fri
|
||||
pt.renewalReminder.clientTypes = 1,2
|
||||
# renewal_date | policy_end_date
|
||||
pt.renewalReminder.dateField = renewal_date
|
||||
pt.renewalReminder.daysBefore = 30, 15, 7, 1, -2, -5, -10
|
||||
pt.renewalReminder.daysBefore = 30
|
||||
pt.renewalReminder.includeClientEmail = true
|
||||
# When true, also send for policies whose dateField is already past (overdue)
|
||||
pt.renewalReminder.includeOverdue = false
|
||||
|
||||
@ -20,8 +20,7 @@ class PtRenewalReminderConfig extends BaseConfig
|
||||
/** Whitelisted: renewal_date | policy_end_date */
|
||||
public string $dateField = 'renewal_date';
|
||||
|
||||
/** @var int[] Reminder offsets: positive = days before due, negative = days after due */
|
||||
public array $daysBeforeList = [30];
|
||||
public int $daysBefore = 30;
|
||||
|
||||
public bool $includeClientEmail = false;
|
||||
|
||||
@ -62,7 +61,7 @@ class PtRenewalReminderConfig extends BaseConfig
|
||||
'days' => env('pt.renewalReminder.days', 'mon,tue,wed,thu,fri'),
|
||||
'clientTypes' => env('pt.renewalReminder.clientTypes', '2'),
|
||||
'dateField' => env('pt.renewalReminder.dateField', 'renewal_date'),
|
||||
'daysBefore' => env('pt.renewalReminder.daysBefore', '30'),
|
||||
'daysBefore' => env('pt.renewalReminder.daysBefore', 30),
|
||||
'includeClientEmail' => env('pt.renewalReminder.includeClientEmail', 'true'),
|
||||
'includeOverdue' => env('pt.renewalReminder.includeOverdue', 'false'),
|
||||
'toEmails' => env('pt.renewalReminder.toEmails', ''),
|
||||
@ -166,7 +165,7 @@ class PtRenewalReminderConfig extends BaseConfig
|
||||
}
|
||||
$this->dateField = $dateField;
|
||||
|
||||
$this->daysBeforeList = self::parseDaysBeforeList($merged['daysBefore'] ?? 30);
|
||||
$this->daysBefore = max(0, (int) ($merged['daysBefore'] ?? 30));
|
||||
|
||||
$this->includeClientEmail = $this->toBool($merged['includeClientEmail'] ?? true);
|
||||
$this->includeOverdue = $this->toBool($merged['includeOverdue'] ?? false);
|
||||
@ -197,74 +196,9 @@ class PtRenewalReminderConfig extends BaseConfig
|
||||
return $this->dateField;
|
||||
}
|
||||
|
||||
/**
|
||||
* First / primary offset (legacy callers).
|
||||
*/
|
||||
public function getDaysBefore(): int
|
||||
{
|
||||
return $this->daysBeforeList[0] ?? 30;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function getDaysBeforeList(): array
|
||||
{
|
||||
return $this->daysBeforeList;
|
||||
}
|
||||
|
||||
public function getDaysBeforeDisplay(): string
|
||||
{
|
||||
return implode(', ', $this->daysBeforeList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map each offset to the calendar date it matches (today + offset).
|
||||
*
|
||||
* @return array<int, string> offset => Y-m-d
|
||||
*/
|
||||
public function getTargetDates(?string $today = null): array
|
||||
{
|
||||
$today = $today ?: date('Y-m-d');
|
||||
$map = [];
|
||||
foreach ($this->daysBeforeList as $offset) {
|
||||
$map[(int) $offset] = date('Y-m-d', strtotime($today . ' ' . sprintf('%+d days', (int) $offset)));
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse "30, 15, 7, 1, -2, -5, -10" (or a single int / array) into unique offsets.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public static function parseDaysBeforeList(mixed $value): array
|
||||
{
|
||||
if (is_int($value) || is_float($value)) {
|
||||
$raw = [(int) $value];
|
||||
} elseif (is_array($value)) {
|
||||
$raw = $value;
|
||||
} else {
|
||||
$raw = preg_split('/[,\s]+/', trim((string) $value), -1, PREG_SPLIT_NO_EMPTY) ?: [];
|
||||
}
|
||||
|
||||
$out = [];
|
||||
$seen = [];
|
||||
foreach ($raw as $item) {
|
||||
$item = trim((string) $item);
|
||||
if ($item === '' || ! is_numeric($item)) {
|
||||
continue;
|
||||
}
|
||||
$n = (int) $item;
|
||||
if ($n > 365 || $n < -365 || isset($seen[$n])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$n] = true;
|
||||
$out[] = $n;
|
||||
}
|
||||
|
||||
return $out !== [] ? $out : [30];
|
||||
return $this->daysBefore;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -337,7 +271,7 @@ class PtRenewalReminderConfig extends BaseConfig
|
||||
'days' => implode(',', $this->days),
|
||||
'clientTypes' => $this->clientTypes,
|
||||
'dateField' => $this->dateField,
|
||||
'daysBefore' => $this->getDaysBeforeDisplay(),
|
||||
'daysBefore' => $this->daysBefore,
|
||||
'includeClientEmail' => $this->includeClientEmail,
|
||||
'includeOverdue' => $this->includeOverdue,
|
||||
'toEmails' => implode(',', $this->toEmails),
|
||||
|
||||
@ -6014,7 +6014,7 @@ class PolicyTransactionController extends BaseController
|
||||
$this->logRenewalReminderStep(
|
||||
$logPrefix . ' Fetching candidates for dateField='
|
||||
. $config->getDateField()
|
||||
. ', daysBefore=' . $config->getDaysBeforeDisplay()
|
||||
. ', daysBefore=' . $config->getDaysBefore()
|
||||
. ', includeOverdue=' . ($config->includeOverdue() ? 'true' : 'false')
|
||||
. ', clientTypes=' . json_encode($config->getClientTypes())
|
||||
);
|
||||
@ -6023,7 +6023,7 @@ class PolicyTransactionController extends BaseController
|
||||
try {
|
||||
$candidates = $this->policyTransactionModel->getRenewalReminderCandidates(
|
||||
$config->getDateField(),
|
||||
$config->getDaysBeforeList(),
|
||||
$config->getDaysBefore(),
|
||||
$config->getClientTypes(),
|
||||
$config->includeOverdue()
|
||||
);
|
||||
@ -6066,7 +6066,7 @@ class PolicyTransactionController extends BaseController
|
||||
$this->logRenewalReminderStep(
|
||||
$logPrefix . ' ' . $message
|
||||
. ' | dateField=' . $config->getDateField()
|
||||
. ' | daysBefore=' . $config->getDaysBeforeDisplay()
|
||||
. ' | daysBefore=' . $config->getDaysBefore()
|
||||
. ' | includeOverdue=' . ($config->includeOverdue() ? 'true' : 'false')
|
||||
);
|
||||
if ($isCli) {
|
||||
@ -6081,7 +6081,7 @@ class PolicyTransactionController extends BaseController
|
||||
try {
|
||||
$diagnostics = $this->policyTransactionModel->diagnoseRenewalReminderCandidates(
|
||||
$config->getDateField(),
|
||||
$config->getDaysBeforeList(),
|
||||
$config->getDaysBefore(),
|
||||
$config->getClientTypes(),
|
||||
$config->includeOverdue()
|
||||
);
|
||||
@ -6119,9 +6119,6 @@ class PolicyTransactionController extends BaseController
|
||||
$emptyPayload['config'] = $configSnapshot;
|
||||
$emptyPayload['detailed_summary'] = $detailedSummary;
|
||||
$emptyPayload['why_empty'] = $diagnostics;
|
||||
if (! empty($detailedSummary['client_mail'])) {
|
||||
$emptyPayload['summary']['client_mail'] = $detailedSummary['client_mail'];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->renewalReminderResponse($emptyPayload, $isCli);
|
||||
@ -6228,8 +6225,6 @@ class PolicyTransactionController extends BaseController
|
||||
'policy_start_date' => $row['policy_start_date'] ?? null,
|
||||
'policy_end_date' => $row['policy_end_date'] ?? null,
|
||||
'reminder_date' => ! empty($reminderRaw) ? date('Y-m-d', strtotime((string) $reminderRaw)) : null,
|
||||
'days_before' => isset($row['reminder_offset']) ? (int) $row['reminder_offset'] : null,
|
||||
'reminder_offset' => isset($row['reminder_offset']) ? (int) $row['reminder_offset'] : null,
|
||||
'is_overdue' => $isOverdue,
|
||||
'status' => 'dry_run',
|
||||
'message' => 'Dry run — mail not sent, status not updated',
|
||||
@ -6290,10 +6285,9 @@ class PolicyTransactionController extends BaseController
|
||||
if (isset($res->status) && $res->status === 'success') {
|
||||
$successCount++;
|
||||
try {
|
||||
$this->policyTransactionModel->markRenewalReminderSent(
|
||||
(int) $ptId,
|
||||
isset($row['reminder_offset']) ? (int) $row['reminder_offset'] : null
|
||||
);
|
||||
$this->policyTransactionModel->update((int) $ptId, [
|
||||
'renewal_status' => 'renewal_mail_send',
|
||||
]);
|
||||
$this->logRenewalReminderStep(
|
||||
$logPrefix . " PT {$ptId}: renewal_status set to renewal_mail_send"
|
||||
);
|
||||
@ -6413,9 +6407,6 @@ class PolicyTransactionController extends BaseController
|
||||
$responsePayload['config'] = $configSnapshot;
|
||||
$responsePayload['detailed_summary'] = $detailedSummary;
|
||||
$responsePayload['message'] = $detailedSummary['headline'] ?? $summaryMessage;
|
||||
if (! empty($detailedSummary['client_mail'])) {
|
||||
$responsePayload['summary']['client_mail'] = $detailedSummary['client_mail'];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->renewalReminderResponse($responsePayload, $isCli);
|
||||
@ -6504,8 +6495,6 @@ class PolicyTransactionController extends BaseController
|
||||
$policiesWithoutPolicyNo = 0;
|
||||
$policiesWithClientEmailInTo = 0;
|
||||
$policiesMissingClientEmail = 0;
|
||||
$policiesWithClientEmailOnRecord = 0;
|
||||
$policiesConfiguredToOnly = 0;
|
||||
$clientEmailsInTo = [];
|
||||
|
||||
foreach ($results as $item) {
|
||||
@ -6537,11 +6526,6 @@ class PolicyTransactionController extends BaseController
|
||||
$policiesWithoutPolicyNo++;
|
||||
}
|
||||
|
||||
$clientEmailRaw = trim((string) ($item['client_email'] ?? ''));
|
||||
if ($clientEmailRaw !== '') {
|
||||
$policiesWithClientEmailOnRecord++;
|
||||
}
|
||||
|
||||
$clientEmailIncluded = ! empty($item['client_email_included']);
|
||||
if ($clientEmailIncluded) {
|
||||
$policiesWithClientEmailInTo++;
|
||||
@ -6555,20 +6539,15 @@ class PolicyTransactionController extends BaseController
|
||||
$policiesMissingClientEmail++;
|
||||
}
|
||||
|
||||
if ($status === 'dry_run' && ! $clientEmailIncluded && (int) ($item['to_count'] ?? count($item['to'] ?? [])) > 0) {
|
||||
$policiesConfiguredToOnly++;
|
||||
}
|
||||
|
||||
$compact = [
|
||||
'pt_id' => $item['pt_id'] ?? null,
|
||||
'policy_no' => $policyNo !== '' ? $policyNo : 'Not Assigned',
|
||||
'client_name' => $item['client_name'] ?? null,
|
||||
'client_email' => $clientEmailRaw !== '' ? $clientEmailRaw : null,
|
||||
'client_email' => $item['client_email'] ?? null,
|
||||
'client_email_included' => $clientEmailIncluded,
|
||||
'include_client_email_enabled' => ! empty($item['include_client_email_enabled']),
|
||||
'policy_type' => $item['policy_type'] ?? null,
|
||||
'reminder_date' => $item['reminder_date'] ?? ($item['renewal_date'] ?? $item['policy_end_date'] ?? null),
|
||||
'days_before' => $item['days_before'] ?? ($item['reminder_offset'] ?? null),
|
||||
'is_overdue' => $isOverdue,
|
||||
'to' => $item['to'] ?? [],
|
||||
'to_count' => (int) ($item['to_count'] ?? count($item['to'] ?? [])),
|
||||
@ -6611,8 +6590,7 @@ class PolicyTransactionController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
$targetDates = $config->getTargetDates();
|
||||
$targetDate = implode(', ', array_values($targetDates));
|
||||
$targetDate = date('Y-m-d', strtotime('+' . max(0, (int) $config->getDaysBefore()) . ' days'));
|
||||
|
||||
$headline = sprintf(
|
||||
'DRY RUN — %d policy(ies) pending for renewal reminder: %d ready to mail, %d skipped (no email), %d failed | client email in TO: %d, missing client email: %d',
|
||||
@ -6633,7 +6611,7 @@ class PolicyTransactionController extends BaseController
|
||||
'Policies with client email in TO: ' . $policiesWithClientEmailInTo,
|
||||
'Policies missing/invalid client email: ' . $policiesMissingClientEmail,
|
||||
'Unique client emails in TO: ' . count($clientEmailsInTo),
|
||||
'Due on target date(s) (' . $targetDate . '): ' . $dueCount,
|
||||
'Due on target date (' . $targetDate . '): ' . $dueCount,
|
||||
'Overdue policies included: ' . $overdueCount,
|
||||
'Unique TO recipients: ' . count($uniqueTo),
|
||||
'Unique CC recipients: ' . count($uniqueCc),
|
||||
@ -6641,24 +6619,13 @@ class PolicyTransactionController extends BaseController
|
||||
'Total TO deliveries (per mail): ' . $totalToHits,
|
||||
'Policies without policy no: ' . $policiesWithoutPolicyNo,
|
||||
'Date field: ' . $config->getDateField(),
|
||||
'Days before: ' . $config->getDaysBeforeDisplay(),
|
||||
'Days before: ' . $config->getDaysBefore(),
|
||||
'Include overdue: ' . ($config->includeOverdue() ? 'yes' : 'no'),
|
||||
];
|
||||
|
||||
$clientMailSummary = [
|
||||
'enabled' => $config->includeClientEmail(),
|
||||
'policies_with_email_on_record' => $policiesWithClientEmailOnRecord,
|
||||
'policies_included_in_to' => $policiesWithClientEmailInTo,
|
||||
'policies_missing_or_invalid' => $policiesMissingClientEmail,
|
||||
'policies_configured_to_only' => $policiesConfiguredToOnly,
|
||||
'unique_client_emails_in_to' => count($clientEmailsInTo),
|
||||
'client_emails_in_to' => array_keys($clientEmailsInTo),
|
||||
];
|
||||
|
||||
return [
|
||||
'headline' => $headline,
|
||||
'lines' => $lines,
|
||||
'client_mail' => $clientMailSummary,
|
||||
'counts' => [
|
||||
'policies_pending_renewal' => $candidateCount,
|
||||
'policies_ready_to_mail' => $wouldSendCount,
|
||||
@ -6669,10 +6636,8 @@ class PolicyTransactionController extends BaseController
|
||||
'policies_without_policy_no' => $policiesWithoutPolicyNo,
|
||||
'policies_without_email' => $policiesWithoutEmail,
|
||||
'include_client_email_enabled' => $config->includeClientEmail(),
|
||||
'policies_with_client_email_on_record' => $policiesWithClientEmailOnRecord,
|
||||
'policies_with_client_email_in_to' => $policiesWithClientEmailInTo,
|
||||
'policies_missing_client_email' => $policiesMissingClientEmail,
|
||||
'policies_configured_to_only' => $policiesConfiguredToOnly,
|
||||
'unique_client_emails_in_to' => count($clientEmailsInTo),
|
||||
'unique_to_recipients' => count($uniqueTo),
|
||||
'unique_cc_recipients' => count($uniqueCc),
|
||||
@ -6684,9 +6649,8 @@ class PolicyTransactionController extends BaseController
|
||||
],
|
||||
'filters' => [
|
||||
'date_field' => $config->getDateField(),
|
||||
'days_before' => $config->getDaysBeforeList(),
|
||||
'target_date' => $targetDates[$config->getDaysBefore()] ?? reset($targetDates),
|
||||
'target_dates' => $targetDates,
|
||||
'days_before' => $config->getDaysBefore(),
|
||||
'target_date' => $targetDate,
|
||||
'include_overdue' => $config->includeOverdue(),
|
||||
'include_client_email' => $config->includeClientEmail(),
|
||||
'client_types' => $config->getClientTypes(),
|
||||
@ -6758,9 +6722,7 @@ class PolicyTransactionController extends BaseController
|
||||
$dateField = $config->getDateField();
|
||||
$reminderRaw = $data[$dateField] ?? ($data['renewal_date'] ?? $data['policy_end_date'] ?? null);
|
||||
$reminderDate = ! empty($reminderRaw) ? date('d-m-Y', strtotime((string) $reminderRaw)) : '';
|
||||
$daysBefore = isset($data['reminder_offset']) && $data['reminder_offset'] !== '' && $data['reminder_offset'] !== null
|
||||
? (int) $data['reminder_offset']
|
||||
: $config->getDaysBefore();
|
||||
$daysBefore = $config->getDaysBefore();
|
||||
$isOverdue = ! empty($data['is_overdue'])
|
||||
|| (! empty($reminderRaw) && strtotime((string) $reminderRaw) < strtotime(date('Y-m-d')));
|
||||
$policyLabel = $policyNo !== '' ? "Policy #{$policyNo}" : 'Policy Pending';
|
||||
@ -7115,7 +7077,7 @@ class PolicyTransactionController extends BaseController
|
||||
'days' => trim((string) $this->request->getPost('days')),
|
||||
'clientTypes' => [2],
|
||||
'dateField' => trim((string) $this->request->getPost('dateField')),
|
||||
'daysBefore' => PtRenewalReminderConfig::parseDaysBeforeList($this->request->getPost('daysBefore')),
|
||||
'daysBefore' => (int) $this->request->getPost('daysBefore'),
|
||||
'includeClientEmail' => $this->request->getPost('includeClientEmail'),
|
||||
'includeOverdue' => $this->request->getPost('includeOverdue'),
|
||||
'toEmails' => trim((string) $this->request->getPost('toEmails')),
|
||||
@ -7132,7 +7094,7 @@ class PolicyTransactionController extends BaseController
|
||||
if (! in_array($payload['dateField'], ['renewal_date', 'policy_end_date'], true)) {
|
||||
$payload['dateField'] = 'renewal_date';
|
||||
}
|
||||
$payload['daysBefore'] = implode(', ', $payload['daysBefore']);
|
||||
$payload['daysBefore'] = max(0, $payload['daysBefore']);
|
||||
|
||||
$updated = $this->notificationModel->update((int) $row['id'], [
|
||||
'config_json' => json_encode($payload),
|
||||
@ -7164,12 +7126,6 @@ class PolicyTransactionController extends BaseController
|
||||
$config['days'] = implode(',', $config['days']);
|
||||
}
|
||||
|
||||
if (is_array($config['daysBefore'] ?? null)) {
|
||||
$config['daysBefore'] = implode(', ', $config['daysBefore']);
|
||||
} elseif (isset($config['daysBefore'])) {
|
||||
$config['daysBefore'] = implode(', ', PtRenewalReminderConfig::parseDaysBeforeList($config['daysBefore']));
|
||||
}
|
||||
|
||||
foreach (['toEmails', 'ccEmails', 'bccEmails'] as $field) {
|
||||
if (is_array($config[$field] ?? null)) {
|
||||
$config[$field] = implode(',', $config[$field]);
|
||||
@ -7401,7 +7357,7 @@ class PolicyTransactionController extends BaseController
|
||||
'days' => 'mon,tue,wed,thu,fri',
|
||||
'clientTypes' => [2],
|
||||
'dateField' => 'renewal_date',
|
||||
'daysBefore' => '30',
|
||||
'daysBefore' => 30,
|
||||
'includeClientEmail' => true,
|
||||
'includeOverdue' => false,
|
||||
'toEmails' => '',
|
||||
|
||||
@ -63,7 +63,6 @@
|
||||
'edate',
|
||||
'renewal_date',
|
||||
'renewal_status',
|
||||
'renewal_last_reminder_offset',
|
||||
'rollover_date',
|
||||
'policy_holder_name',
|
||||
'same_as_proposer',
|
||||
@ -2132,15 +2131,15 @@
|
||||
/**
|
||||
* Candidates for policy_transaction renewal reminder cron.
|
||||
*
|
||||
* @param string $dateField Whitelisted column: renewal_date | policy_end_date
|
||||
* @param int|string|int[] $daysBefore Comma-separated or list of offsets (negative = days after due)
|
||||
* @param int[] $clientTypes Client type ids (1=Group, 2=Individual)
|
||||
* @param bool $includeOverdue When true, also include rows where dateField < today
|
||||
* @param string $dateField Whitelisted column: renewal_date | policy_end_date
|
||||
* @param int $daysBefore Days ahead of today to match
|
||||
* @param int[] $clientTypes Client type ids (1=Group, 2=Individual)
|
||||
* @param bool $includeOverdue When true, also include rows where dateField < today
|
||||
* @return array
|
||||
*/
|
||||
public function getRenewalReminderCandidates(
|
||||
string $dateField,
|
||||
$daysBefore,
|
||||
int $daysBefore,
|
||||
array $clientTypes,
|
||||
bool $includeOverdue = false
|
||||
): array {
|
||||
@ -2157,10 +2156,10 @@
|
||||
return [];
|
||||
}
|
||||
|
||||
$offsets = $this->normalizeReminderOffsets($daysBefore);
|
||||
$today = date('Y-m-d');
|
||||
$targetDates = $this->reminderTargetDates($offsets, $today);
|
||||
$column = 'pt.' . $dateField;
|
||||
$daysBefore = max(0, (int) $daysBefore);
|
||||
$targetDate = date('Y-m-d', strtotime('+' . $daysBefore . ' days'));
|
||||
$today = date('Y-m-d');
|
||||
$column = 'pt.' . $dateField;
|
||||
|
||||
$query = $this->db->table('policy_transaction pt')
|
||||
->select("
|
||||
@ -2178,7 +2177,6 @@
|
||||
c.common_mails,
|
||||
policy_type.policy_type,
|
||||
i.name AS insurer_name,
|
||||
DATEDIFF({$column}, CURDATE()) AS reminder_offset,
|
||||
CASE WHEN {$column} < '{$today}' THEN 1 ELSE 0 END AS is_overdue
|
||||
")
|
||||
->join('clients c', 'pt.client_id = c.id', 'left')
|
||||
@ -2187,9 +2185,18 @@
|
||||
->join('policy_type', 'pt.policy_type_id = policy_type.id', 'left')
|
||||
->where('pt.is_active', 1)
|
||||
->where('pt.action_type', 'inception')
|
||||
->whereIn('c.client_type', $clientTypes);
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->where("NOT EXISTS (SELECT 1 FROM policy_transaction p2 WHERE p2.source_client_policy_id = pt.client_policy_id AND p2.is_active = 1)");
|
||||
|
||||
$this->applyRenewalReminderCandidateFilters($query, $column, $targetDates, $today, $includeOverdue);
|
||||
if ($includeOverdue) {
|
||||
$query->groupStart()
|
||||
->where($column, $targetDate)
|
||||
->orWhere("{$column} <", $today)
|
||||
->groupEnd();
|
||||
} else {
|
||||
$query->where($column, $targetDate);
|
||||
}
|
||||
|
||||
$query->groupBy('pt.id')
|
||||
->orderBy('pt.id', 'DESC');
|
||||
@ -2200,13 +2207,12 @@
|
||||
/**
|
||||
* Explain why renewal reminder candidates may be empty (for dry-run diagnostics).
|
||||
*
|
||||
* @param int|string|int[] $daysBefore
|
||||
* @param int[] $clientTypes
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function diagnoseRenewalReminderCandidates(
|
||||
string $dateField,
|
||||
$daysBefore,
|
||||
int $daysBefore,
|
||||
array $clientTypes,
|
||||
bool $includeOverdue = false
|
||||
): array {
|
||||
@ -2219,11 +2225,10 @@
|
||||
return $type > 0;
|
||||
})));
|
||||
|
||||
$offsets = $this->normalizeReminderOffsets($daysBefore);
|
||||
$today = date('Y-m-d');
|
||||
$targetDates = $this->reminderTargetDates($offsets, $today);
|
||||
$column = 'pt.' . $dateField;
|
||||
$targetLabel = implode(', ', $targetDates);
|
||||
$daysBefore = max(0, (int) $daysBefore);
|
||||
$targetDate = date('Y-m-d', strtotime('+' . $daysBefore . ' days'));
|
||||
$today = date('Y-m-d');
|
||||
$column = 'pt.' . $dateField;
|
||||
|
||||
$base = function () {
|
||||
return $this->db->table('policy_transaction pt')
|
||||
@ -2250,25 +2255,25 @@
|
||||
|
||||
$countAfterStatus = (int) $base()
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') !=", 'renewed')
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->countAllResults();
|
||||
|
||||
$countAfterNotRenewedExists = (int) $base()
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') !=", 'renewed')
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->where("NOT EXISTS (SELECT 1 FROM policy_transaction p2 WHERE p2.source_client_policy_id = pt.client_policy_id AND p2.is_active = 1)")
|
||||
->countAllResults();
|
||||
|
||||
$countExactTargetDate = (int) $base()
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') !=", 'renewed')
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->where("NOT EXISTS (SELECT 1 FROM policy_transaction p2 WHERE p2.source_client_policy_id = pt.client_policy_id AND p2.is_active = 1)")
|
||||
->whereIn($column, $targetDates)
|
||||
->where($column, $targetDate)
|
||||
->countAllResults();
|
||||
|
||||
$countOverdue = (int) $base()
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') !=", 'renewed')
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->where("NOT EXISTS (SELECT 1 FROM policy_transaction p2 WHERE p2.source_client_policy_id = pt.client_policy_id AND p2.is_active = 1)")
|
||||
->where("{$column} <", $today)
|
||||
->where("{$column} IS NOT NULL", null, false)
|
||||
@ -2276,16 +2281,27 @@
|
||||
->countAllResults();
|
||||
|
||||
$finalBuilder = $base()
|
||||
->whereIn('c.client_type', $clientTypes);
|
||||
$this->applyRenewalReminderCandidateFilters($finalBuilder, $column, $targetDates, $today, $includeOverdue);
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->where("NOT EXISTS (SELECT 1 FROM policy_transaction p2 WHERE p2.source_client_policy_id = pt.client_policy_id AND p2.is_active = 1)");
|
||||
|
||||
if ($includeOverdue) {
|
||||
$finalBuilder->groupStart()
|
||||
->where($column, $targetDate)
|
||||
->orWhere("{$column} <", $today)
|
||||
->groupEnd();
|
||||
} else {
|
||||
$finalBuilder->where($column, $targetDate);
|
||||
}
|
||||
$countFinal = (int) $finalBuilder->countAllResults();
|
||||
|
||||
// Sample nearest upcoming renewals (next 60 days) for debugging
|
||||
$from = $today;
|
||||
$to = date('Y-m-d', strtotime('+60 days'));
|
||||
$nearestSample = $base()
|
||||
->select("pt.id, pt.policy_no, c.client_name, c.email AS client_email, pt.renewal_date, pt.policy_end_date, pt.renewal_status, {$column} AS match_date, DATEDIFF({$column}, CURDATE()) AS reminder_offset")
|
||||
->select("pt.id, pt.policy_no, c.client_name, c.email AS client_email, pt.renewal_date, pt.policy_end_date, pt.renewal_status, {$column} AS match_date")
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') !=", 'renewed')
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->where("NOT EXISTS (SELECT 1 FROM policy_transaction p2 WHERE p2.source_client_policy_id = pt.client_policy_id AND p2.is_active = 1)")
|
||||
->where("{$column} >=", $from)
|
||||
->where("{$column} <=", $to)
|
||||
@ -2302,16 +2318,16 @@
|
||||
if ($countAfterClientType === 0) {
|
||||
$reasons[] = 'No active inception policies found for configured client type(s).';
|
||||
} elseif ($countAfterStatus === 0) {
|
||||
$reasons[] = 'All matching policies already have renewal_status of renewed.';
|
||||
$reasons[] = 'All matching policies already have renewal_status of renewal_mail_send/renewed/reminded.';
|
||||
} elseif ($countAfterNotRenewedExists === 0) {
|
||||
$reasons[] = 'All remaining policies already have an active renewed policy linked via source_client_policy_id.';
|
||||
} elseif (! $includeOverdue && $countExactTargetDate === 0) {
|
||||
$reasons[] = "No policies have {$dateField} equal to any target date ({$targetLabel}) for offsets [" . implode(', ', $offsets) . '].';
|
||||
$reasons[] = "No policies have {$dateField} exactly equal to target date {$targetDate} (today + {$daysBefore} days). includeOverdue is false, so only exact date matches are selected.";
|
||||
if ($countOverdue > 0) {
|
||||
$reasons[] = "There are {$countOverdue} overdue policies that would be included if includeOverdue=true, or if a negative offset matches today.";
|
||||
$reasons[] = "There are {$countOverdue} overdue policies that would be included if includeOverdue=true.";
|
||||
}
|
||||
} elseif ($includeOverdue && $countFinal === 0) {
|
||||
$reasons[] = "No policies match target dates ({$targetLabel}) or overdue (< {$today}) on {$dateField}.";
|
||||
$reasons[] = "No policies match target date {$targetDate} or overdue (< {$today}) on {$dateField}.";
|
||||
}
|
||||
|
||||
if (empty($reasons) && $countFinal === 0) {
|
||||
@ -2320,15 +2336,14 @@
|
||||
|
||||
return [
|
||||
'today' => $today,
|
||||
'target_date' => $targetDates[0] ?? null,
|
||||
'target_dates' => $targetDates,
|
||||
'target_date' => $targetDate,
|
||||
'date_field' => $dateField,
|
||||
'days_before' => $offsets,
|
||||
'days_before' => $daysBefore,
|
||||
'include_overdue' => $includeOverdue,
|
||||
'client_types' => $clientTypes,
|
||||
'counts' => [
|
||||
'active_inception_for_client_types' => $countAfterClientType,
|
||||
'after_excluding_renewed' => $countAfterStatus,
|
||||
'after_excluding_sent_or_renewed' => $countAfterStatus,
|
||||
'after_excluding_already_renewed_policy' => $countAfterNotRenewedExists,
|
||||
'exact_target_date_matches' => $countExactTargetDate,
|
||||
'overdue_matches' => $countOverdue,
|
||||
@ -2337,106 +2352,11 @@
|
||||
'reasons' => $reasons,
|
||||
'nearest_upcoming_sample_60_days' => $nearestSample,
|
||||
'hint' => $includeOverdue
|
||||
? 'Candidates = any configured offset date OR overdue on selected date field.'
|
||||
: "Candidates require {$dateField} IN ({$targetLabel}). Negative offsets send after the due date. Change daysBefore in Mail Config.",
|
||||
? 'Candidates = exact target date OR overdue on selected date field.'
|
||||
: "Candidates require {$dateField} = {$targetDate} exactly. Enable includeOverdue to also include past dates, or change daysBefore / dateField in Mail Config.",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a policy as mailed for the current reminder offset.
|
||||
*/
|
||||
public function markRenewalReminderSent(int $ptId, ?int $offset = null): bool
|
||||
{
|
||||
$data = ['renewal_status' => 'renewal_mail_send'];
|
||||
if ($offset !== null && $this->hasRenewalLastReminderOffsetColumn()) {
|
||||
$data['renewal_last_reminder_offset'] = $offset;
|
||||
}
|
||||
|
||||
return (bool) $this->update($ptId, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string|int[] $daysBefore
|
||||
* @return int[]
|
||||
*/
|
||||
private function normalizeReminderOffsets($daysBefore): array
|
||||
{
|
||||
return \Config\PtRenewalReminderConfig::parseDaysBeforeList($daysBefore);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $offsets
|
||||
* @return string[]
|
||||
*/
|
||||
private function reminderTargetDates(array $offsets, string $today): array
|
||||
{
|
||||
$dates = [];
|
||||
foreach ($offsets as $offset) {
|
||||
$dates[] = date('Y-m-d', strtotime($today . ' ' . sprintf('%+d days', (int) $offset)));
|
||||
}
|
||||
|
||||
return array_values(array_unique($dates));
|
||||
}
|
||||
|
||||
private function hasRenewalLastReminderOffsetColumn(): bool
|
||||
{
|
||||
static $exists = null;
|
||||
if ($exists === null) {
|
||||
$exists = $this->db->fieldExists('renewal_last_reminder_offset', $this->table);
|
||||
}
|
||||
|
||||
return $exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $query Query builder
|
||||
* @param string[] $targetDates
|
||||
*/
|
||||
private function applyRenewalReminderCandidateFilters(
|
||||
$query,
|
||||
string $column,
|
||||
array $targetDates,
|
||||
string $today,
|
||||
bool $includeOverdue
|
||||
): void {
|
||||
$query->where("NOT EXISTS (SELECT 1 FROM policy_transaction p2 WHERE p2.source_client_policy_id = pt.client_policy_id AND p2.is_active = 1)");
|
||||
|
||||
if ($this->hasRenewalLastReminderOffsetColumn()) {
|
||||
$query->where("IFNULL(pt.renewal_status, '') !=", 'renewed');
|
||||
$offsetSkip = "(pt.renewal_last_reminder_offset IS NULL OR pt.renewal_last_reminder_offset != DATEDIFF({$column}, CURDATE()))";
|
||||
|
||||
if ($includeOverdue) {
|
||||
$query->groupStart()
|
||||
->groupStart()
|
||||
->whereIn($column, $targetDates)
|
||||
->where($offsetSkip, null, false)
|
||||
->groupEnd()
|
||||
->orGroupStart()
|
||||
->where("{$column} <", $today)
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'reminded')", null, false)
|
||||
->where('pt.renewal_last_reminder_offset', null)
|
||||
->groupEnd()
|
||||
->groupEnd();
|
||||
} else {
|
||||
$query->whereIn($column, $targetDates)
|
||||
->where($offsetSkip, null, false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$query->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false);
|
||||
|
||||
if ($includeOverdue) {
|
||||
$query->groupStart()
|
||||
->whereIn($column, $targetDates)
|
||||
->orWhere("{$column} <", $today)
|
||||
->groupEnd();
|
||||
} else {
|
||||
$query->whereIn($column, $targetDates);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Server-side DataTables list for Retail Policy (individual clients only).
|
||||
*
|
||||
|
||||
@ -261,14 +261,12 @@
|
||||
<label class="custom-control-label" for="cfg_includeClientEmail">Include Client Email</label>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="form-group col-md-3 col-sm-6">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="cfg_includeOverdue" name="includeOverdue" value="1">
|
||||
<label class="custom-control-label" for="cfg_includeOverdue">Include Overdue</label>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
@ -285,14 +283,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-8">
|
||||
<div class="form-group col-md-3">
|
||||
<label for="cfg_daysBefore">Days Before</label>
|
||||
<input type="text" class="form-control" id="cfg_daysBefore" name="daysBefore" value="30" placeholder="30, 15, 7, 1, -2, -5, -10">
|
||||
<small class="form-text text-muted">Comma-separated offsets. Positive = days before due, negative = days after due.</small>
|
||||
<input type="number" min="0" class="form-control" id="cfg_daysBefore" name="daysBefore" value="30">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-12">
|
||||
<div class="form-group col-md-5">
|
||||
<label for="cfg_days">Weekdays (used when daily is off)</label>
|
||||
<input type="text" class="form-control" id="cfg_days" name="days" placeholder="mon,tue,wed,thu,fri">
|
||||
</div>
|
||||
@ -483,7 +478,7 @@
|
||||
$('#cfg_daily').prop('checked', boolVal(data.daily));
|
||||
$('#cfg_includeClientEmail').prop('checked', boolVal(data.includeClientEmail));
|
||||
$('#cfg_includeOverdue').prop('checked', boolVal(data.includeOverdue));
|
||||
$('#cfg_daysBefore').val(listVal(data.daysBefore) || '30');
|
||||
$('#cfg_daysBefore').val(data.daysBefore != null ? data.daysBefore : 30);
|
||||
setRetailDateField(data.dateField || 'renewal_date');
|
||||
$('#cfg_days').val(listVal(data.days) || 'mon,tue,wed,thu,fri');
|
||||
$('#cfg_toEmails').val(listVal(data.toEmails));
|
||||
|
||||
@ -2,16 +2,10 @@
|
||||
-- Run manually against the application database.
|
||||
|
||||
-- 1) policy_transaction.renewal_status
|
||||
-- Skip this ALTER if the column already exists.
|
||||
ALTER TABLE `policy_transaction`
|
||||
ADD COLUMN `renewal_status` VARCHAR(50) NULL DEFAULT NULL
|
||||
AFTER `renewal_date`;
|
||||
|
||||
-- Required for multi-offset reminders (30, 15, 7, ...). Skip if the column already exists.
|
||||
ALTER TABLE `policy_transaction`
|
||||
ADD COLUMN `renewal_last_reminder_offset` INT NULL DEFAULT NULL
|
||||
AFTER `renewal_status`;
|
||||
|
||||
-- 2) notifications.config_json
|
||||
ALTER TABLE `notifications`
|
||||
ADD COLUMN `config_json` LONGTEXT NULL
|
||||
@ -32,7 +26,7 @@ SELECT
|
||||
'Policy Renewal Reminder',
|
||||
'',
|
||||
1,
|
||||
'{"enabled":true,"daily":true,"days":"mon,tue,wed,thu,fri","clientTypes":[2],"dateField":"renewal_date","daysBefore":"30, 15, 7, 1, -2, -5, -10","includeClientEmail":true,"includeOverdue":false,"toEmails":"","ccEmails":"","bccEmails":"","fromMail":""}'
|
||||
'{"enabled":true,"daily":true,"days":"mon,tue,wed,thu,fri","clientTypes":[2],"dateField":"renewal_date","daysBefore":30,"includeClientEmail":true,"includeOverdue":false,"toEmails":"","ccEmails":"","bccEmails":"","fromMail":""}'
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
|
||||
Loading…
Reference in New Issue
Block a user