feat: subscriptions have personalized notification times (#334)

This commit is contained in:
Miguel Ribeiro 2024-05-20 13:07:40 +02:00 committed by GitHub
parent f1815d7335
commit c7146dfd08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 105 additions and 28 deletions

View File

@ -134,15 +134,21 @@
$notify = []; $i = 0;
$currentDate = new DateTime('now');
while ($rowSubscription = $resultSubscriptions->fetchArray(SQLITE3_ASSOC)) {
if ($rowSubscription['notify_days_before'] !== 0) {
$daysToCompare = $rowSubscription['notify_days_before'];
} else {
$daysToCompare = $days;
}
$nextPaymentDate = new DateTime($rowSubscription['next_payment']);
$difference = $currentDate->diff($nextPaymentDate)->days + 1;
if ($difference === $days) {
if ($difference === $daysToCompare) {
$notify[$rowSubscription['payer_user_id']][$i]['name'] = $rowSubscription['name'];
$notify[$rowSubscription['payer_user_id']][$i]['price'] = $rowSubscription['price'] . $currencies[$rowSubscription['currency_id']]['symbol'];
$notify[$rowSubscription['payer_user_id']][$i]['currency'] = $currencies[$rowSubscription['currency_id']]['name'];
$notify[$rowSubscription['payer_user_id']][$i]['category'] = $categories[$rowSubscription['category_id']]['name'];
$notify[$rowSubscription['payer_user_id']][$i]['payer'] = $household[$rowSubscription['payer_user_id']]['name'];
$notify[$rowSubscription['payer_user_id']][$i]['date'] = $rowSubscription['next_payment'];
$notify[$rowSubscription['payer_user_id']][$i]['days'] = $daysToCompare;
$i++;
}
}
@ -163,11 +169,11 @@
$defaultName = $defaultUser['username'];
foreach ($notify as $userId => $perUser) {
$dayText = $days == 1 ? "tomorrow" : "in " . $days . " days";
$message = "The following subscriptions are up for renewal " . $dayText . ":\n";
$message = "The following subscriptions are up for renewal:\n";
foreach ($perUser as $subscription) {
$message .= $subscription['name'] . " for " . $subscription['price'] . "\n";
$dayText = $subscription['days'] == 1 ? "Tomorrow" : "In " . $subscription['days'] . " days";
$message .= $subscription['name'] . " for " . $subscription['price'] . " (" . $dayText . ")\n";
}
$mail = new PHPMailer(true);
@ -214,15 +220,15 @@
$title = translate('wallos_notification', $i18n);
$dayText = $days == 1 ? "tomorrow" : "in " . $days . " days";
if ($user['name']) {
$message = $user['name'] . ", the following subscriptions are up for renewal " . $dayText . ":\n";
$message = $user['name'] . ", the following subscriptions are up for renewal:\n";
} else {
$message = "The following subscriptions are up for renewal " . $dayText . ":\n";
$message = "The following subscriptions are up for renewal:\n";
}
foreach ($perUser as $subscription) {
$message .= $subscription['name'] . " for " . $subscription['price'] . "\n";
$dayText = $subscription['days'] == 1 ? "Tomorrow" : "In " . $subscription['days'] . " days";
$message .= $subscription['name'] . " for " . $subscription['price'] . " (" . $dayText . ")\n";
}
$postfields = [
@ -267,15 +273,15 @@
$result = $stmt->execute();
$user = $result->fetchArray(SQLITE3_ASSOC);
$dayText = $days == 1 ? "tomorrow" : "in " . $days . " days";
if ($user['name']) {
$message = $user['name'] . ", the following subscriptions are up for renewal " . $dayText . ":\n";
$message = $user['name'] . ", the following subscriptions are up for renewal:\n";
} else {
$message = "The following subscriptions are up for renewal " . $dayText . ":\n";
$message = "The following subscriptions are up for renewal:\n";
}
foreach ($perUser as $subscription) {
$message .= $subscription['name'] . " for " . $subscription['price'] . "\n";
$dayText = $subscription['days'] == 1 ? "Tomorrow" : "In " . $subscription['days'] . " days";
$message .= $subscription['name'] . " for " . $subscription['price'] . " (" . $dayText . ")\n";
}
$data = array(
@ -312,15 +318,15 @@
$result = $stmt->execute();
$user = $result->fetchArray(SQLITE3_ASSOC);
$dayText = $days == 1 ? "tomorrow" : "in " . $days . " days";
if ($user['name']) {
$message = $user['name'] . ", the following subscriptions are up for renewal " . $dayText . ":\n";
$message = $user['name'] . ", the following subscriptions are up for renewal:\n";
} else {
$message = "The following subscriptions are up for renewal " . $dayText . ":\n";
$message = "The following subscriptions are up for renewal:\n";
}
foreach ($perUser as $subscription) {
$message .= $subscription['name'] . " for " . $subscription['price'] . "\n";
$dayText = $subscription['days'] == 1 ? "Tomorrow" : "In " . $subscription['days'] . " days";
$message .= $subscription['name'] . " for " . $subscription['price'] . " (" . $dayText . ")\n";
}
$data = array(
@ -357,15 +363,15 @@
$result = $stmt->execute();
$user = $result->fetchArray(SQLITE3_ASSOC);
$dayText = $days == 1 ? "tomorrow" : "in " . $days . " days";
if ($user['name']) {
$message = $user['name'] . ", the following subscriptions are up for renewal " . $dayText . ":\n";
$message = $user['name'] . ", the following subscriptions are up for renewal:\n";
} else {
$message = "The following subscriptions are up for renewal " . $dayText . ":\n";
$message = "The following subscriptions are up for renewal:\n";
}
foreach ($perUser as $subscription) {
$message .= $subscription['name'] . " for " . $subscription['price'] . "\n";
$dayText = $subscription['days'] == 1 ? "Tomorrow" : "In " . $subscription['days'] . " days";
$message .= $subscription['name'] . " for " . $subscription['price'] . " (" . $dayText . ")\n";
}
$ch = curl_init();
@ -394,7 +400,7 @@
if ($webhookNotificationsEnabled) {
// Get webhook payload and turn it into a json object
$payload = str_replace("{{days_until}}", $days, $webhook['payload']);
$payload = str_replace("{{days_until}}", $days, $webhook['payload']); // The default value for all subscriptions
$payload_json = json_decode($payload, true);
$subscription_template = $payload_json["{{subscriptions}}"];
@ -422,6 +428,7 @@
$temp_subscription[$key] = str_replace("{{subscription_category}}", $subscription['category'], $temp_subscription[$key]);
$temp_subscription[$key] = str_replace("{{subscription_payer}}", $subscription['payer'], $temp_subscription[$key]);
$temp_subscription[$key] = str_replace("{{subscription_date}}", $subscription['date'], $temp_subscription[$key]);
$temp_subscription[$key] = str_replace("{{subscription_days_until_payment}}", $subscription['days'], $temp_subscription[$key]); // The de facto value for this subscription
}
}
$subscriptions[] = $temp_subscription;

View File

@ -166,6 +166,7 @@
$logoUrl = validate($_POST['logo-url']);
$logo = "";
$notify = isset($_POST['notifications']) ? true : false;
$notifyDaysBefore = $_POST['notify_days_before'];
$inactive = isset($_POST['inactive']) ? true : false;
if($logoUrl !== "") {
@ -183,15 +184,20 @@
if (!$isEdit) {
$sql = "INSERT INTO subscriptions (name, logo, price, currency_id, next_payment, cycle, frequency, notes,
payment_method_id, payer_user_id, category_id, notify, inactive, url)
payment_method_id, payer_user_id, category_id, notify, inactive, url, notify_days_before)
VALUES (:name, :logo, :price, :currencyId, :nextPayment, :cycle, :frequency, :notes,
:paymentMethodId, :payerUserId, :categoryId, :notify, :inactive, :url)";
:paymentMethodId, :payerUserId, :categoryId, :notify, :inactive, :url, :notifyDaysBefore)";
} else {
$id = $_POST['id'];
if ($logo != "") {
$sql = "UPDATE subscriptions SET name = :name, logo = :logo, price = :price, currency_id = :currencyId, next_payment = :nextPayment, cycle = :cycle, frequency = :frequency, notes = :notes, payment_method_id = :paymentMethodId, payer_user_id = :payerUserId, category_id = :categoryId, notify = :notify, inactive = :inactive, url = :url WHERE id = :id";
$sql = "UPDATE subscriptions SET name = :name, logo = :logo, price = :price, currency_id = :currencyId,
next_payment = :nextPayment, cycle = :cycle, frequency = :frequency, notes = :notes, payment_method_id = :paymentMethodId,
payer_user_id = :payerUserId, category_id = :categoryId, notify = :notify, inactive = :inactive,
url = :url, notify_days_before = :notifyDaysBefore WHERE id = :id";
} else {
$sql = "UPDATE subscriptions SET name = :name, price = :price, currency_id = :currencyId, next_payment = :nextPayment, cycle = :cycle, frequency = :frequency, notes = :notes, payment_method_id = :paymentMethodId, payer_user_id = :payerUserId, category_id = :categoryId, notify = :notify, inactive = :inactive, url = :url WHERE id = :id";
$sql = "UPDATE subscriptions SET name = :name, price = :price, currency_id = :currencyId, next_payment = :nextPayment,
cycle = :cycle, frequency = :frequency, notes = :notes, payment_method_id = :paymentMethodId, payer_user_id = :payerUserId,
category_id = :categoryId, notify = :notify, inactive = :inactive, url = :url,notify_days_before = :notifyDaysBefore WHERE id = :id";
}
}
@ -215,6 +221,7 @@
$stmt->bindParam(':notify', $notify, SQLITE3_INTEGER);
$stmt->bindParam(':inactive', $inactive, SQLITE3_INTEGER);
$stmt->bindParam(':url', $url, SQLITE3_TEXT);
$stmt->bindParam(':notifyDaysBefore', $notifyDaysBefore, SQLITE3_INTEGER);
if ($stmt->execute()) {
$success['status'] = "Success";

View File

@ -27,6 +27,7 @@
$subscriptionData['notify'] = $row['notify'];
$subscriptionData['inactive'] = $row['inactive'];
$subscriptionData['url'] = htmlspecialchars_decode($row['url']);
$subscriptionData['notify_days_before'] = $row['notify_days_before'];
$subscriptionJson = json_encode($subscriptionData);
header('Content-Type: application/json');

View File

@ -74,6 +74,7 @@ $i18n = [
"url" => "URL",
"notes" => "Notizen",
"enable_notifications" => "Benachrichtigungen für dieses Abonnement aktivieren",
"default_value_from_settings" => "Standardwert aus den Einstellungen",
"delete" => "Löschen",
"cancel" => "Abbrechen",
"upload_logo" => "Logo hochladen",

View File

@ -74,6 +74,7 @@ $i18n = [
"url" => "URL",
"notes" => "Σημειώσεις",
"enable_notifications" => "Ενεργοποίηση ειδοποιήσεων για αυτή τη συνδρομή",
"default_value_from_settings" => "Προεπιλεγμένη τιμή από τις ρυθμίσεις",
"delete" => "Διαγραφή",
"cancel" => "Ακύρωση",
"upload_logo" => "Φόρτωση λογότυπου",

View File

@ -74,6 +74,7 @@ $i18n = [
"url" => "URL",
"notes" => "Notes",
"enable_notifications" => "Enable Notifications for this subscription",
"default_value_from_settings" => "Default value from settings",
"delete" => "Delete",
"cancel" => "Cancel",
"upload_logo" => "Upload Logo",

View File

@ -74,6 +74,7 @@ $i18n = [
"url" => "URL",
"notes" => "Notas",
"enable_notifications" => "Habilitar notificaciones para esta suscripción",
"default_value_from_settings" => "Valor predeterminado de la configuración",
"delete" => "Eliminar",
"cancel" => "Cancelar",
"upload_logo" => "Cargar Logotipo",

View File

@ -74,6 +74,7 @@ $i18n = [
"url" => "URL",
"notes" => "Notes",
"enable_notifications" => "Activer les notifications pour cet abonnement",
"default_value_from_settings" => "Valeur par défaut des paramètres",
"delete" => "Supprimer",
"cancel" => "Annuler",
"upload_logo" => "Télécharger le logo",

View File

@ -76,6 +76,7 @@ $i18n = [
'url' => 'URL',
'notes' => 'Note',
'enable_notifications' => 'Abilita notifiche per questo abbonamento',
'default_value_from_settings' => 'Valore predefinito dalle impostazioni',
'delete' => 'Cancella',
'cancel' => 'Annulla',
'upload_logo' => 'Carica logo',

View File

@ -74,6 +74,7 @@ $i18n = [
"url" => "URL",
"notes" => "注釈",
"enable_notifications" => "この定期購入の通知を有効にする",
"default_value_from_settings" => "設定からデフォルト値を使用",
"delete" => "削除",
"cancel" => "キャンセル",
"upload_logo" => "ロゴのアップロード",

View File

@ -74,6 +74,7 @@ $i18n = [
"url" => "URL",
"notes" => "메모",
"enable_notifications" => "이 구독에 대한 알림을 활성화합니다.",
"default_value_from_settings" => "설정에서 기본값 사용",
"delete" => "삭제",
"cancel" => "취소",
"upload_logo" => "로고 업로드",

View File

@ -74,6 +74,7 @@ $i18n = [
"url" => "URL",
"notes" => "Notatki",
"enable_notifications" => "Włącz powiadomienia dla tej subskrypcji",
"default_value_from_settings" => "Wartość domyślna z ustawień",
"delete" => "Usuń",
"cancel" => "Anuluj",
"upload_logo" => "Prześlij logo",

View File

@ -74,6 +74,7 @@ $i18n = [
"url" => "URL",
"notes" => "Notas",
"enable_notifications" => "Activar notificações para esta subscrição",
"default_value_from_settings" => "Valor por defeito das definições",
"delete" => "Remover",
"cancel" => "Cancelar",
"upload_logo" => "Enviar Logo",

View File

@ -72,6 +72,7 @@ $i18n = [
"url" => "URL",
"notes" => "Anotações",
"enable_notifications" => "Ativar notificações para essa assinatura",
"default_value_from_settings" => "Valor padrão das configurações",
"delete" => "Excluir",
"cancel" => "Cancelar",
"upload_logo" => "Enviar Logo",

View File

@ -74,6 +74,7 @@ $i18n = [
"url" => "URL",
"notes" => "Примечания",
"enable_notifications" => "Включить уведомления для этой подписки",
"default_value_from_settings" => "Значение по умолчанию из настроек",
"delete" => "Удалить",
"cancel" => "Отмена",
"upload_logo" => "Загрузить логотип",

View File

@ -74,6 +74,7 @@ $i18n = [
"url" => "URL",
"notes" => "Beleške",
"enable_notifications" => "Omogući obaveštenja za ovu pretplatu",
"default_value_from_settings" => "Podrazumevana vrednost iz podešavanja",
"delete" => "Izbriši",
"cancel" => "Otkaži",
"upload_logo" => "Učitaj logo",

View File

@ -74,6 +74,7 @@ $i18n = [
"url" => "URL",
"notes" => "Notlar",
"enable_notifications" => "Bu abonelik için bildirimleri etkinleştir",
"default_value_from_settings" => "Ayarlar'dan varsayılan değeri al",
"delete" => "Sil",
"cancel" => "İptal",
"upload_logo" => "Logo Yükle",

View File

@ -78,6 +78,7 @@ $i18n = [
"url" => "链接",
"notes" => "备注",
"enable_notifications" => "为此订阅启用通知",
"default_value_from_settings" => "默认值从设置中获取",
"delete" => "删除",
"cancel" => "取消",
"upload_logo" => "上传 Logo",

View File

@ -74,6 +74,7 @@ $i18n = [
"url" => "連結",
"notes" => "備註",
"enable_notifications" => "為該訂閱開啟通知",
"default_value_from_settings" => "從設定中取得預設值",
"delete" => "刪除",
"cancel" => "取消",
"upload_logo" => "上傳圖示",

View File

@ -1,3 +1,3 @@
<?php
$version = "v1.28.0";
$version = "v1.29.0";
?>

View File

@ -276,10 +276,25 @@
</div>
<div class="form-group-inline">
<input type="checkbox" id="notifications" name="notifications">
<input type="checkbox" id="notifications" name="notifications" onchange="toggleNotificationDays()">
<label for="notifications"><?= translate('enable_notifications', $i18n) ?></label>
</div>
<div class="form-group">
<label for="notify_days_before"><?= translate('notify_me', $i18n) ?></label>
<select id="notify_days_before" name="notify_days_before" disabled>
<option value="0"><?= translate('default_value_from_settings', $i18n) ?></option>
<option value="1">1 <?= translate('day_before', $i18n) ?></option>
<?php
for ($i = 2; $i <= 90; $i++) {
?>
<option value="<?= $i ?>"><?= $i ?> <?= translate('days_before', $i18n) ?></option>
<?php
}
?>
</select>
</div>
<div class="form-group">
<label for="payment_method"><?= translate('payment_method', $i18n) ?></label>
<select id="payment_method" name="payment_method_id">

15
migrations/000019.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/*
This migration adds a column to the subscriptuons table to store individual choice for how many days before the subscription is up for payment to notify the user
The default value of 0 means global settings will be used
*/
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('subscriptions') where name='notify_days_before'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;
if ($columnRequired) {
$db->exec('ALTER TABLE subscriptions ADD COLUMN notify_days_before INTEGER DEFAULT 0');
}
?>

View File

@ -11,6 +11,12 @@ function toggleSortOptions() {
isSortOptionsOpen = !isSortOptionsOpen;
}
function toggleNotificationDays() {
const notifyCheckbox = document.querySelector("#notifications");
const notifyDaysBefore = document.querySelector("#notify_days_before");
notifyDaysBefore.disabled = !notifyCheckbox.checked;
}
function resetForm() {
const id = document.querySelector("#id");
id.value = "";
@ -25,6 +31,8 @@ function resetForm() {
logoSearchButton.classList.add("disabled");
const submitButton = document.querySelector("#save-button");
submitButton.disabled = false;
const notifyDaysBefore = document.querySelector("#notify_days_before");
notifyDaysBefore.disabled = true;
const form = document.querySelector("#subs-form");
form.reset();
closeLogoSearch();
@ -77,6 +85,12 @@ function fillEditFormFields(subscription) {
notifications.checked = subscription.notify;
}
const notifyDaysBefore = document.querySelector("#notify_days_before");
notifyDaysBefore.value = subscription.notify_days_before;
if (subscription.notify === 1) {
notifyDaysBefore.disabled = false;
}
const deleteButton = document.querySelector("#deletesub");
deleteButton.style = 'display: block';
deleteButton.setAttribute("onClick", `deleteSubscription(${subscription.id})`);

View File

@ -327,6 +327,7 @@
"category": "{{subscription_category}}",
"date": "{{subscription_date}}",
"payer": "{{subscription_payer}}"
"dyas": "{{subscription_days_until_payment}}"
}
]
@ -555,7 +556,7 @@
</div>
<div class="settings-notes">
<p>
<i class="fa-solid fa-circle-info"></i> <?= translate('variables_available', $i18n) ?>: {{days_until}}, {{subscription_name}}, {{subscription_price}}, {{subscription_currency}}, {{subscription_category}}, {{subscription_date}}, {{subscription_payer}}</p>
<i class="fa-solid fa-circle-info"></i> <?= translate('variables_available', $i18n) ?>: {{days_until}}, {{subscription_name}}, {{subscription_price}}, {{subscription_currency}}, {{subscription_category}}, {{subscription_date}}, {{subscription_payer}}, {{subscription_days_until_payment}}</p>
<p>
</div>
<div class="buttons">

View File

@ -1375,6 +1375,7 @@ textarea.thin {
transform: translateX(calc(100% + 30px));
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.35);
box-sizing: border-box;
z-index: 5;
}
@media (max-width: 768px) {