nhance/app/Config/PtRenewalReminderConfig.php

388 lines
12 KiB
PHP

<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
use Config\Database;
class PtRenewalReminderConfig extends BaseConfig
{
public bool $enabled = false;
public bool $daily = false;
/** @var string[] Lowercase three-letter day abbreviations, e.g. mon, tue */
public array $days = [];
/** @var int[] Client type ids: 1=Group, 2=Individual */
public array $clientTypes = [];
/** 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 bool $includeClientEmail = false;
/** When true, also include policies whose dateField is already past */
public bool $includeOverdue = false;
/** @var string[] */
public array $toEmails = [];
/** @var string[] */
public array $ccEmails = [];
/** @var string[] */
public array $bccEmails = [];
public string $fromMail = '';
/** Loaded from notifications.subject when present */
public string $mailSubject = '';
/** Loaded from notifications.mail_content when present */
public string $mailContent = '';
public ?int $notificationId = null;
private const ALLOWED_DATE_FIELDS = ['renewal_date', 'policy_end_date'];
public const TEMPLATE_NAME = 'retail_reminder_mail';
public function __construct()
{
parent::__construct();
// Env defaults (seed / fallback)
$defaults = [
'enabled' => env('pt.renewalReminder.enabled', 'true'),
'daily' => env('pt.renewalReminder.daily', 'true'),
'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'),
'includeClientEmail' => env('pt.renewalReminder.includeClientEmail', 'true'),
'includeOverdue' => env('pt.renewalReminder.includeOverdue', 'false'),
'toEmails' => env('pt.renewalReminder.toEmails', ''),
'ccEmails' => env('pt.renewalReminder.ccEmails', ''),
'bccEmails' => env('pt.renewalReminder.bccEmails', ''),
'fromMail' => env('pt.renewalReminder.fromMail', ''),
];
$dbConfig = $this->loadConfigFromNotification();
$merged = array_merge($defaults, $dbConfig);
$this->applyMergedConfig($merged);
}
/**
* @return array<string, mixed>
*/
private function loadConfigFromNotification(): array
{
try {
$db = Database::connect();
$row = $db->table('notifications')
->where('template_name', self::TEMPLATE_NAME)
->groupStart()
->where('client_id', null)
->orWhere('client_id', 0)
->orWhere('client_id', '')
->groupEnd()
->orderBy('id', 'DESC')
->get()
->getRowArray();
if (empty($row)) {
log_message('error', '[PtRenewalReminderConfig] No notifications row for retail_reminder_mail; using env defaults');
return [];
}
$this->notificationId = isset($row['id']) ? (int) $row['id'] : null;
$this->mailSubject = trim((string) ($row['subject'] ?? ''));
$this->mailContent = (string) ($row['mail_content'] ?? '');
$json = trim((string) ($row['config_json'] ?? ''));
if ($json === '') {
log_message('error', '[PtRenewalReminderConfig] config_json empty for notification_id=' . ($this->notificationId ?? 'n/a'));
return [];
}
$decoded = json_decode($json, true);
if (! is_array($decoded)) {
log_message('error', '[PtRenewalReminderConfig] Invalid config_json for notification_id=' . ($this->notificationId ?? 'n/a'));
return [];
}
return $decoded;
} catch (\Throwable $e) {
log_message(
'error',
'[PtRenewalReminderConfig] Failed loading notification config: ' . $e->getMessage()
);
return [];
}
}
/**
* @param array<string, mixed> $merged
*/
private function applyMergedConfig(array $merged): void
{
$this->enabled = $this->toBool($merged['enabled'] ?? false);
$this->daily = $this->toBool($merged['daily'] ?? false);
$daysRaw = $merged['days'] ?? 'mon,tue,wed,thu,fri';
if (is_array($daysRaw)) {
$daysRaw = implode(',', $daysRaw);
}
$this->days = array_values(array_filter(array_map(
static fn (string $day): string => strtolower(substr(trim($day), 0, 3)),
explode(',', (string) $daysRaw)
)));
$clientTypesRaw = $merged['clientTypes'] ?? '2';
if (is_array($clientTypesRaw)) {
$this->clientTypes = array_values(array_unique(array_filter(array_map(
static fn ($type): int => (int) $type,
$clientTypesRaw
), static fn (int $type): bool => $type > 0)));
} else {
$this->clientTypes = array_values(array_unique(array_filter(array_map(
static fn (string $type): int => (int) trim($type),
explode(',', (string) $clientTypesRaw)
), static fn (int $type): bool => $type > 0)));
}
$dateField = strtolower(trim((string) ($merged['dateField'] ?? 'renewal_date')));
if (! in_array($dateField, self::ALLOWED_DATE_FIELDS, true)) {
log_message(
'error',
'[PtRenewalReminderConfig] Invalid dateField "' . $dateField . '", falling back to renewal_date'
);
$dateField = 'renewal_date';
}
$this->dateField = $dateField;
$this->daysBeforeList = self::parseDaysBeforeList($merged['daysBefore'] ?? 30);
$this->includeClientEmail = $this->toBool($merged['includeClientEmail'] ?? true);
$this->includeOverdue = $this->toBool($merged['includeOverdue'] ?? false);
$this->toEmails = $this->parseEmailList($merged['toEmails'] ?? '');
$this->ccEmails = $this->parseEmailList($merged['ccEmails'] ?? '');
$this->bccEmails = $this->parseEmailList($merged['bccEmails'] ?? '');
$this->fromMail = trim((string) ($merged['fromMail'] ?? ''));
}
public function shouldRunToday(): bool
{
if (! $this->enabled) {
return false;
}
if ($this->daily) {
return true;
}
$today = strtolower(date('D'));
return in_array($today, $this->days, true);
}
public function getDateField(): string
{
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 int[]
*/
public function getClientTypes(): array
{
return $this->clientTypes;
}
/**
* @return string[]
*/
public function getToEmails(): array
{
return $this->toEmails;
}
/**
* @return string[]
*/
public function getCcEmails(): array
{
return $this->ccEmails;
}
/**
* @return string[]
*/
public function getBccEmails(): array
{
return $this->bccEmails;
}
public function getFromMail(): string
{
return $this->fromMail;
}
public function includeClientEmail(): bool
{
return $this->includeClientEmail;
}
public function includeOverdue(): bool
{
return $this->includeOverdue;
}
public function getMailSubject(): string
{
return $this->mailSubject;
}
public function getMailContent(): string
{
return $this->mailContent;
}
/**
* Flatten config for UI / API response.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'enabled' => $this->enabled,
'daily' => $this->daily,
'days' => implode(',', $this->days),
'clientTypes' => $this->clientTypes,
'dateField' => $this->dateField,
'daysBefore' => $this->getDaysBeforeDisplay(),
'includeClientEmail' => $this->includeClientEmail,
'includeOverdue' => $this->includeOverdue,
'toEmails' => implode(',', $this->toEmails),
'ccEmails' => implode(',', $this->ccEmails),
'bccEmails' => implode(',', $this->bccEmails),
'fromMail' => $this->fromMail,
];
}
/**
* @return string[]
*/
private function parseEmailList(mixed $value): array
{
if ($value === null || $value === '') {
return [];
}
if (is_array($value)) {
$emails = array_map('trim', $value);
} else {
$emails = array_map('trim', explode(',', (string) $value));
}
$unique = [];
foreach ($emails as $email) {
if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
continue;
}
$key = strtolower($email);
if (! isset($unique[$key])) {
$unique[$key] = $email;
}
}
return array_values($unique);
}
private function toBool(mixed $value): bool
{
if (is_bool($value)) {
return $value;
}
return in_array(strtolower((string) $value), ['1', 'true', 'yes', 'on'], true);
}
}