Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
396 lines
17 KiB
JavaScript
396 lines
17 KiB
JavaScript
/**
|
|
* Migration: Add Appointments category to internal notifications
|
|
*
|
|
* Creates:
|
|
* - Appointments notification category
|
|
* - Subtypes for appointment events
|
|
* - Notification templates in English (with placeholders for other languages)
|
|
*/
|
|
|
|
exports.up = async function(knex) {
|
|
console.log('Adding appointments internal notification category...');
|
|
|
|
// 1. Insert Appointments category
|
|
const [appointmentsCategory] = await knex('internal_notification_categories')
|
|
.insert([
|
|
{
|
|
name: 'appointments',
|
|
description: 'Appointment request and scheduling notifications',
|
|
is_enabled: true,
|
|
is_default_enabled: true,
|
|
available_for_client_portal: true // Available for both MSP and client portal
|
|
}
|
|
])
|
|
.onConflict('name')
|
|
.merge({
|
|
description: knex.raw('excluded.description'),
|
|
available_for_client_portal: knex.raw('excluded.available_for_client_portal')
|
|
})
|
|
.returning('*');
|
|
|
|
const categoryId = appointmentsCategory.internal_notification_category_id;
|
|
|
|
// 2. Insert subtypes
|
|
const subtypes = await knex('internal_notification_subtypes')
|
|
.insert([
|
|
{
|
|
internal_category_id: categoryId,
|
|
name: 'appointment-request-created',
|
|
description: 'New appointment request submitted',
|
|
is_enabled: true,
|
|
is_default_enabled: true,
|
|
available_for_client_portal: true
|
|
},
|
|
{
|
|
internal_category_id: categoryId,
|
|
name: 'appointment-request-approved',
|
|
description: 'Appointment request approved',
|
|
is_enabled: true,
|
|
is_default_enabled: true,
|
|
available_for_client_portal: true
|
|
},
|
|
{
|
|
internal_category_id: categoryId,
|
|
name: 'appointment-request-declined',
|
|
description: 'Appointment request declined',
|
|
is_enabled: true,
|
|
is_default_enabled: true,
|
|
available_for_client_portal: true
|
|
},
|
|
{
|
|
internal_category_id: categoryId,
|
|
name: 'appointment-request-cancelled',
|
|
description: 'Appointment request cancelled',
|
|
is_enabled: true,
|
|
is_default_enabled: true,
|
|
available_for_client_portal: true
|
|
}
|
|
])
|
|
.onConflict(['internal_category_id', 'name'])
|
|
.merge({
|
|
description: knex.raw('excluded.description'),
|
|
available_for_client_portal: knex.raw('excluded.available_for_client_portal')
|
|
})
|
|
.returning('*');
|
|
|
|
// Get subtype IDs
|
|
const requestCreatedSubtype = subtypes.find(s => s.name === 'appointment-request-created');
|
|
const requestApprovedSubtype = subtypes.find(s => s.name === 'appointment-request-approved');
|
|
const requestDeclinedSubtype = subtypes.find(s => s.name === 'appointment-request-declined');
|
|
const requestCancelledSubtype = subtypes.find(s => s.name === 'appointment-request-cancelled');
|
|
|
|
// 3. Insert templates for all supported languages
|
|
await knex('internal_notification_templates')
|
|
.insert([
|
|
// English (en)
|
|
{
|
|
name: 'appointment-request-created-client',
|
|
language_code: 'en',
|
|
subtype_id: requestCreatedSubtype.internal_notification_subtype_id,
|
|
title: 'Appointment Request Submitted',
|
|
message: 'Your appointment request for {{serviceName}} on {{requestedDate}} has been submitted and is pending approval.'
|
|
},
|
|
{
|
|
name: 'appointment-request-created-staff',
|
|
language_code: 'en',
|
|
subtype_id: requestCreatedSubtype.internal_notification_subtype_id,
|
|
title: 'New Appointment Request from {{clientName}}',
|
|
message: '{{requesterName}} has requested an appointment for {{serviceName}} on {{requestedDate}} at {{requestedTime}}.'
|
|
},
|
|
{
|
|
name: 'appointment-request-approved',
|
|
language_code: 'en',
|
|
subtype_id: requestApprovedSubtype.internal_notification_subtype_id,
|
|
title: 'Appointment Confirmed!',
|
|
message: 'Your appointment for {{serviceName}} on {{appointmentDate}} at {{appointmentTime}} has been confirmed. Assigned technician: {{technicianName}}.'
|
|
},
|
|
{
|
|
name: 'appointment-request-declined',
|
|
language_code: 'en',
|
|
subtype_id: requestDeclinedSubtype.internal_notification_subtype_id,
|
|
title: 'Appointment Request Update',
|
|
message: 'Your appointment request for {{serviceName}} could not be accommodated. {{declineReason}}'
|
|
},
|
|
{
|
|
name: 'appointment-request-cancelled-client',
|
|
language_code: 'en',
|
|
subtype_id: requestCancelledSubtype.internal_notification_subtype_id,
|
|
title: 'Appointment Request Cancelled',
|
|
message: 'Your appointment request for {{serviceName}} on {{requestedDate}} has been cancelled successfully.'
|
|
},
|
|
{
|
|
name: 'appointment-request-cancelled-staff',
|
|
language_code: 'en',
|
|
subtype_id: requestCancelledSubtype.internal_notification_subtype_id,
|
|
title: 'Appointment Request Cancelled',
|
|
message: '{{requesterName}} has cancelled their appointment request for {{serviceName}} on {{requestedDate}}.'
|
|
},
|
|
|
|
// German (de)
|
|
{
|
|
name: 'appointment-request-created-client',
|
|
language_code: 'de',
|
|
subtype_id: requestCreatedSubtype.internal_notification_subtype_id,
|
|
title: 'Terminanfrage eingereicht',
|
|
message: 'Ihre Terminanfrage für {{serviceName}} am {{requestedDate}} wurde eingereicht und wartet auf Genehmigung.'
|
|
},
|
|
{
|
|
name: 'appointment-request-created-staff',
|
|
language_code: 'de',
|
|
subtype_id: requestCreatedSubtype.internal_notification_subtype_id,
|
|
title: 'Neue Terminanfrage von {{clientName}}',
|
|
message: '{{requesterName}} hat einen Termin für {{serviceName}} am {{requestedDate}} um {{requestedTime}} angefragt.'
|
|
},
|
|
{
|
|
name: 'appointment-request-approved',
|
|
language_code: 'de',
|
|
subtype_id: requestApprovedSubtype.internal_notification_subtype_id,
|
|
title: 'Termin bestätigt!',
|
|
message: 'Ihr Termin für {{serviceName}} am {{appointmentDate}} um {{appointmentTime}} wurde bestätigt. Zugewiesener Techniker: {{technicianName}}.'
|
|
},
|
|
{
|
|
name: 'appointment-request-declined',
|
|
language_code: 'de',
|
|
subtype_id: requestDeclinedSubtype.internal_notification_subtype_id,
|
|
title: 'Terminanfrage Aktualisierung',
|
|
message: 'Ihre Terminanfrage für {{serviceName}} konnte nicht berücksichtigt werden. {{declineReason}}'
|
|
},
|
|
{
|
|
name: 'appointment-request-cancelled-client',
|
|
language_code: 'de',
|
|
subtype_id: requestCancelledSubtype.internal_notification_subtype_id,
|
|
title: 'Terminanfrage storniert',
|
|
message: 'Ihre Terminanfrage für {{serviceName}} am {{requestedDate}} wurde erfolgreich storniert.'
|
|
},
|
|
{
|
|
name: 'appointment-request-cancelled-staff',
|
|
language_code: 'de',
|
|
subtype_id: requestCancelledSubtype.internal_notification_subtype_id,
|
|
title: 'Terminanfrage storniert',
|
|
message: '{{requesterName}} hat die Terminanfrage für {{serviceName}} am {{requestedDate}} storniert.'
|
|
},
|
|
|
|
// Spanish (es)
|
|
{
|
|
name: 'appointment-request-created-client',
|
|
language_code: 'es',
|
|
subtype_id: requestCreatedSubtype.internal_notification_subtype_id,
|
|
title: 'Solicitud de cita enviada',
|
|
message: 'Su solicitud de cita para {{serviceName}} el {{requestedDate}} ha sido enviada y está pendiente de aprobación.'
|
|
},
|
|
{
|
|
name: 'appointment-request-created-staff',
|
|
language_code: 'es',
|
|
subtype_id: requestCreatedSubtype.internal_notification_subtype_id,
|
|
title: 'Nueva solicitud de cita de {{clientName}}',
|
|
message: '{{requesterName}} ha solicitado una cita para {{serviceName}} el {{requestedDate}} a las {{requestedTime}}.'
|
|
},
|
|
{
|
|
name: 'appointment-request-approved',
|
|
language_code: 'es',
|
|
subtype_id: requestApprovedSubtype.internal_notification_subtype_id,
|
|
title: '¡Cita confirmada!',
|
|
message: 'Su cita para {{serviceName}} el {{appointmentDate}} a las {{appointmentTime}} ha sido confirmada. Técnico asignado: {{technicianName}}.'
|
|
},
|
|
{
|
|
name: 'appointment-request-declined',
|
|
language_code: 'es',
|
|
subtype_id: requestDeclinedSubtype.internal_notification_subtype_id,
|
|
title: 'Actualización de solicitud de cita',
|
|
message: 'No se pudo acomodar su solicitud de cita para {{serviceName}}. {{declineReason}}'
|
|
},
|
|
{
|
|
name: 'appointment-request-cancelled-client',
|
|
language_code: 'es',
|
|
subtype_id: requestCancelledSubtype.internal_notification_subtype_id,
|
|
title: 'Solicitud de cita cancelada',
|
|
message: 'Su solicitud de cita para {{serviceName}} el {{requestedDate}} ha sido cancelada exitosamente.'
|
|
},
|
|
{
|
|
name: 'appointment-request-cancelled-staff',
|
|
language_code: 'es',
|
|
subtype_id: requestCancelledSubtype.internal_notification_subtype_id,
|
|
title: 'Solicitud de cita cancelada',
|
|
message: '{{requesterName}} ha cancelado su solicitud de cita para {{serviceName}} el {{requestedDate}}.'
|
|
},
|
|
|
|
// French (fr)
|
|
{
|
|
name: 'appointment-request-created-client',
|
|
language_code: 'fr',
|
|
subtype_id: requestCreatedSubtype.internal_notification_subtype_id,
|
|
title: 'Demande de rendez-vous soumise',
|
|
message: 'Votre demande de rendez-vous pour {{serviceName}} le {{requestedDate}} a été soumise et est en attente d\'approbation.'
|
|
},
|
|
{
|
|
name: 'appointment-request-created-staff',
|
|
language_code: 'fr',
|
|
subtype_id: requestCreatedSubtype.internal_notification_subtype_id,
|
|
title: 'Nouvelle demande de rendez-vous de {{clientName}}',
|
|
message: '{{requesterName}} a demandé un rendez-vous pour {{serviceName}} le {{requestedDate}} à {{requestedTime}}.'
|
|
},
|
|
{
|
|
name: 'appointment-request-approved',
|
|
language_code: 'fr',
|
|
subtype_id: requestApprovedSubtype.internal_notification_subtype_id,
|
|
title: 'Rendez-vous confirmé !',
|
|
message: 'Votre rendez-vous pour {{serviceName}} le {{appointmentDate}} à {{appointmentTime}} a été confirmé. Technicien assigné : {{technicianName}}.'
|
|
},
|
|
{
|
|
name: 'appointment-request-declined',
|
|
language_code: 'fr',
|
|
subtype_id: requestDeclinedSubtype.internal_notification_subtype_id,
|
|
title: 'Mise à jour de la demande de rendez-vous',
|
|
message: 'Votre demande de rendez-vous pour {{serviceName}} n\'a pas pu être acceptée. {{declineReason}}'
|
|
},
|
|
{
|
|
name: 'appointment-request-cancelled-client',
|
|
language_code: 'fr',
|
|
subtype_id: requestCancelledSubtype.internal_notification_subtype_id,
|
|
title: 'Demande de rendez-vous annulée',
|
|
message: 'Votre demande de rendez-vous pour {{serviceName}} le {{requestedDate}} a été annulée avec succès.'
|
|
},
|
|
{
|
|
name: 'appointment-request-cancelled-staff',
|
|
language_code: 'fr',
|
|
subtype_id: requestCancelledSubtype.internal_notification_subtype_id,
|
|
title: 'Demande de rendez-vous annulée',
|
|
message: '{{requesterName}} a annulé sa demande de rendez-vous pour {{serviceName}} le {{requestedDate}}.'
|
|
},
|
|
|
|
// Italian (it)
|
|
{
|
|
name: 'appointment-request-created-client',
|
|
language_code: 'it',
|
|
subtype_id: requestCreatedSubtype.internal_notification_subtype_id,
|
|
title: 'Richiesta di appuntamento inviata',
|
|
message: 'La tua richiesta di appuntamento per {{serviceName}} il {{requestedDate}} è stata inviata ed è in attesa di approvazione.'
|
|
},
|
|
{
|
|
name: 'appointment-request-created-staff',
|
|
language_code: 'it',
|
|
subtype_id: requestCreatedSubtype.internal_notification_subtype_id,
|
|
title: 'Nuova richiesta di appuntamento da {{clientName}}',
|
|
message: '{{requesterName}} ha richiesto un appuntamento per {{serviceName}} il {{requestedDate}} alle {{requestedTime}}.'
|
|
},
|
|
{
|
|
name: 'appointment-request-approved',
|
|
language_code: 'it',
|
|
subtype_id: requestApprovedSubtype.internal_notification_subtype_id,
|
|
title: 'Appuntamento confermato!',
|
|
message: 'Il tuo appuntamento per {{serviceName}} il {{appointmentDate}} alle {{appointmentTime}} è stato confermato. Tecnico assegnato: {{technicianName}}.'
|
|
},
|
|
{
|
|
name: 'appointment-request-declined',
|
|
language_code: 'it',
|
|
subtype_id: requestDeclinedSubtype.internal_notification_subtype_id,
|
|
title: 'Aggiornamento richiesta di appuntamento',
|
|
message: 'La tua richiesta di appuntamento per {{serviceName}} non ha potuto essere accolta. {{declineReason}}'
|
|
},
|
|
{
|
|
name: 'appointment-request-cancelled-client',
|
|
language_code: 'it',
|
|
subtype_id: requestCancelledSubtype.internal_notification_subtype_id,
|
|
title: 'Richiesta di appuntamento cancellata',
|
|
message: 'La tua richiesta di appuntamento per {{serviceName}} il {{requestedDate}} è stata cancellata con successo.'
|
|
},
|
|
{
|
|
name: 'appointment-request-cancelled-staff',
|
|
language_code: 'it',
|
|
subtype_id: requestCancelledSubtype.internal_notification_subtype_id,
|
|
title: 'Richiesta di appuntamento cancellata',
|
|
message: '{{requesterName}} ha cancellato la sua richiesta di appuntamento per {{serviceName}} il {{requestedDate}}.'
|
|
},
|
|
|
|
// Dutch (nl)
|
|
{
|
|
name: 'appointment-request-created-client',
|
|
language_code: 'nl',
|
|
subtype_id: requestCreatedSubtype.internal_notification_subtype_id,
|
|
title: 'Afspraakverzoek ingediend',
|
|
message: 'Uw afspraakverzoek voor {{serviceName}} op {{requestedDate}} is ingediend en wacht op goedkeuring.'
|
|
},
|
|
{
|
|
name: 'appointment-request-created-staff',
|
|
language_code: 'nl',
|
|
subtype_id: requestCreatedSubtype.internal_notification_subtype_id,
|
|
title: 'Nieuw afspraakverzoek van {{clientName}}',
|
|
message: '{{requesterName}} heeft een afspraak aangevraagd voor {{serviceName}} op {{requestedDate}} om {{requestedTime}}.'
|
|
},
|
|
{
|
|
name: 'appointment-request-approved',
|
|
language_code: 'nl',
|
|
subtype_id: requestApprovedSubtype.internal_notification_subtype_id,
|
|
title: 'Afspraak bevestigd!',
|
|
message: 'Uw afspraak voor {{serviceName}} op {{appointmentDate}} om {{appointmentTime}} is bevestigd. Toegewezen technicus: {{technicianName}}.'
|
|
},
|
|
{
|
|
name: 'appointment-request-declined',
|
|
language_code: 'nl',
|
|
subtype_id: requestDeclinedSubtype.internal_notification_subtype_id,
|
|
title: 'Update afspraakverzoek',
|
|
message: 'Uw afspraakverzoek voor {{serviceName}} kon niet worden geaccepteerd. {{declineReason}}'
|
|
},
|
|
{
|
|
name: 'appointment-request-cancelled-client',
|
|
language_code: 'nl',
|
|
subtype_id: requestCancelledSubtype.internal_notification_subtype_id,
|
|
title: 'Afspraakverzoek geannuleerd',
|
|
message: 'Uw afspraakverzoek voor {{serviceName}} op {{requestedDate}} is succesvol geannuleerd.'
|
|
},
|
|
{
|
|
name: 'appointment-request-cancelled-staff',
|
|
language_code: 'nl',
|
|
subtype_id: requestCancelledSubtype.internal_notification_subtype_id,
|
|
title: 'Afspraakverzoek geannuleerd',
|
|
message: '{{requesterName}} heeft zijn/haar afspraakverzoek voor {{serviceName}} op {{requestedDate}} geannuleerd.'
|
|
}
|
|
])
|
|
.onConflict(['name', 'language_code'])
|
|
.ignore();
|
|
|
|
console.log('Appointments internal notification category created successfully');
|
|
};
|
|
|
|
exports.down = async function(knex) {
|
|
console.log('Removing appointments internal notification category...');
|
|
|
|
// Get the category ID
|
|
const category = await knex('internal_notification_categories')
|
|
.where({ name: 'appointments' })
|
|
.first();
|
|
|
|
if (!category) {
|
|
console.log('Appointments category not found, nothing to remove');
|
|
return;
|
|
}
|
|
|
|
const categoryId = category.internal_notification_category_id;
|
|
|
|
// Get subtypes
|
|
const subtypes = await knex('internal_notification_subtypes')
|
|
.where({ internal_category_id: categoryId })
|
|
.select('internal_notification_subtype_id');
|
|
|
|
const subtypeIds = subtypes.map(s => s.internal_notification_subtype_id);
|
|
|
|
if (subtypeIds.length > 0) {
|
|
// Delete templates associated with these subtypes
|
|
await knex('internal_notification_templates')
|
|
.whereIn('subtype_id', subtypeIds)
|
|
.delete();
|
|
|
|
// Delete subtypes
|
|
await knex('internal_notification_subtypes')
|
|
.whereIn('internal_notification_subtype_id', subtypeIds)
|
|
.delete();
|
|
}
|
|
|
|
// Delete category
|
|
await knex('internal_notification_categories')
|
|
.where({ internal_notification_category_id: categoryId })
|
|
.delete();
|
|
|
|
console.log('Appointments internal notification category removed successfully');
|
|
};
|