PSA/server/seeds/dev/69_add_more_notification_setttings.cjs
Hermes 284313f908
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
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

162 lines
6.1 KiB
JavaScript

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*
* Non-destructive seed for notification categories and subtypes.
* Uses upserts to preserve existing data while ensuring required entries exist.
*/
exports.seed = async function(knex) {
// Get the first tenant from the tenants table
const tenant = await knex('tenants').first('tenant');
if (!tenant) {
throw new Error('No tenant found in tenants table');
}
console.log('Seeding notification categories and subtypes (non-destructive)...');
// Define categories to upsert
const categoriesToUpsert = [
{
name: 'User Account',
description: 'Authentication and account-related notifications (password reset, email verification, etc.)',
is_enabled: true,
is_default_enabled: true
},
{
name: 'Tickets',
description: 'Notifications related to support tickets',
is_enabled: true,
is_default_enabled: true
},
{
name: 'Invoices',
description: 'Notifications related to billing and invoices',
is_enabled: true,
is_default_enabled: true
},
{
name: 'Projects',
description: 'Notifications related to project updates',
is_enabled: true,
is_default_enabled: true
},
{
name: 'Time Entries',
description: 'Notifications related to time tracking and approvals',
is_enabled: true,
is_default_enabled: true
},
{
name: 'Surveys',
description: 'Customer satisfaction surveys and feedback loops',
is_enabled: true,
is_default_enabled: true
}
];
// Upsert categories - insert if not exists, update description if exists
for (const category of categoriesToUpsert) {
await knex('notification_categories')
.insert(category)
.onConflict('name')
.merge({
description: category.description,
updated_at: knex.fn.now()
});
}
console.log(` ✓ Upserted ${categoriesToUpsert.length} notification categories`);
// Get all categories (including any added by migrations)
const allCategories = await knex('notification_categories').select('id', 'name');
const categoryMap = allCategories.reduce((acc, cat) => {
acc[cat.name] = cat;
return acc;
}, {});
// Helper to safely get category ID
const getCategoryId = (name) => {
const category = categoryMap[name];
if (!category) {
console.warn(` ⚠️ Category '${name}' not found, skipping related subtypes`);
return null;
}
return category.id;
};
// Define subtypes to upsert (only for categories we know exist)
const subtypesToUpsert = [
// Ticket notifications
{ category: 'Tickets', name: 'Ticket Assigned', description: 'When a ticket is assigned to a user' },
{ category: 'Tickets', name: 'Ticket Created', description: 'When a new ticket is created' },
{ category: 'Tickets', name: 'Ticket Updated', description: 'When a ticket is modified' },
{ category: 'Tickets', name: 'Ticket Closed', description: 'When a ticket is closed' },
{ category: 'Tickets', name: 'Ticket Comment Added', description: 'When a comment is added to a ticket' },
// Survey notifications
{ category: 'Surveys', name: 'survey-ticket-closed', description: 'When a customer satisfaction survey invitation is sent after a ticket is closed' },
// Invoice notifications
{ category: 'Invoices', name: 'Invoice Generated', description: 'When a new invoice is generated' },
{ category: 'Invoices', name: 'Payment Received', description: 'When a payment is received' },
{ category: 'Invoices', name: 'Payment Overdue', description: 'When an invoice payment is overdue' },
// Project notifications
{ category: 'Projects', name: 'Project Updated', description: 'When a project is modified' },
{ category: 'Projects', name: 'Project Closed', description: 'When a project is closed' },
{ category: 'Projects', name: 'Project Assigned', description: 'When a project is assigned to a user' },
{ category: 'Projects', name: 'Project Task Assigned', description: 'When a project task is assigned to a user' },
{ category: 'Projects', name: 'Project Created', description: 'When a new project is created' },
{ category: 'Projects', name: 'Task Updated', description: 'When a project task is updated' },
{ category: 'Projects', name: 'Milestone Completed', description: 'When a project milestone is completed' },
// Time Entry notifications
{ category: 'Time Entries', name: 'Time Entry Submitted', description: 'When time entries are submitted for approval' },
{ category: 'Time Entries', name: 'Time Entry Approved', description: 'When time entries are approved' },
{ category: 'Time Entries', name: 'Time Entry Rejected', description: 'When time entries are rejected' },
];
// Upsert subtypes
let subtypeCount = 0;
for (const subtype of subtypesToUpsert) {
const categoryId = getCategoryId(subtype.category);
if (categoryId === null) continue;
await knex('notification_subtypes')
.insert({
category_id: categoryId,
name: subtype.name,
description: subtype.description,
is_enabled: true,
is_default_enabled: true
})
.onConflict(['category_id', 'name'])
.merge({
description: subtype.description,
updated_at: knex.fn.now()
});
subtypeCount++;
}
console.log(` ✓ Upserted ${subtypeCount} notification subtypes`);
// Ensure notification settings exist for tenant.
// NOTE: Some schemas do not enforce uniqueness on (tenant), so ON CONFLICT (tenant) may fail.
const existingSettings = await knex('notification_settings')
.where({ tenant: tenant.tenant })
.first('id');
if (!existingSettings) {
await knex('notification_settings').insert({
tenant: tenant.tenant,
is_enabled: true,
rate_limit_per_minute: 60
});
} else {
await knex('notification_settings')
.where({ tenant: tenant.tenant })
.update({ updated_at: knex.fn.now() });
}
console.log(` ✓ Ensured notification settings exist for tenant`);
console.log('Notification seed completed (non-destructive)');
};