- Add admin dashboard with sync controls and data browser - Implement RMM, Auvik, and Addigy organization mappings - Add chunked ticket sync with progress tracking - Implement entity sync service with rate limiting - Add analytics engine and performance optimizer - Create data browser for all PSA entities - Add navigation components and UI improvements - Implement background processing and sync services - Add comprehensive documentation and migration scripts - Update configuration items with multi-system support - Enhance contact management and purchase history - Add issue type assignment and LLM analyzer - Improve error handling and logging utilities
171 lines
5.3 KiB
TypeScript
171 lines
5.3 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { Pool } from 'pg';
|
|
import { getAddigyClient } from '@/lib/services/addigy-factory';
|
|
import { AddigyOrgMapping } from '@/lib/types/addigy';
|
|
|
|
const pool = new Pool({
|
|
host: process.env.POSTGRES_HOST,
|
|
port: parseInt(process.env.POSTGRES_PORT || '5432'),
|
|
database: process.env.POSTGRES_DB,
|
|
user: process.env.POSTGRES_USER,
|
|
password: process.env.POSTGRES_PASSWORD,
|
|
});
|
|
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const searchParams = request.nextUrl.searchParams;
|
|
const includeUnmapped = searchParams.get('includeUnmapped') === 'true';
|
|
|
|
// Get all mappings from database
|
|
const result = await pool.query<{
|
|
id: number;
|
|
addigy_org_id: string;
|
|
addigy_org_name: string;
|
|
autotask_company_id: number;
|
|
autotask_company_name: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}>('SELECT * FROM addigy_org_mappings ORDER BY addigy_org_name');
|
|
|
|
const mappings: AddigyOrgMapping[] = result.rows.map((row) => ({
|
|
id: row.id,
|
|
addigyOrgId: row.addigy_org_id,
|
|
addigyOrgName: row.addigy_org_name,
|
|
autotaskCompanyId: row.autotask_company_id,
|
|
autotaskCompanyName: row.autotask_company_name,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
}));
|
|
|
|
// If includeUnmapped is true, fetch all Addigy policies (which act as organizations/sites) and merge
|
|
if (includeUnmapped) {
|
|
try {
|
|
const addigyClient = getAddigyClient();
|
|
// In Addigy, policies are the grouping mechanism (like sites/organizations)
|
|
const allPolicies = await addigyClient.getAllPolicies();
|
|
|
|
const mappedOrgIds = new Set(mappings.map((m) => m.addigyOrgId));
|
|
|
|
const unmappedOrgs = allPolicies
|
|
.filter((policy) => !mappedOrgIds.has(policy.policyId))
|
|
.map((policy) => ({
|
|
id: 0, // Temporary ID for unmapped
|
|
addigyOrgId: policy.policyId,
|
|
addigyOrgName: policy.name,
|
|
autotaskCompanyId: 0,
|
|
autotaskCompanyName: '',
|
|
createdAt: '',
|
|
updatedAt: '',
|
|
}));
|
|
|
|
return NextResponse.json({
|
|
mappings: [...mappings, ...unmappedOrgs],
|
|
totalMapped: mappings.length,
|
|
totalUnmapped: unmappedOrgs.length,
|
|
});
|
|
} catch (addigyError) {
|
|
// If Addigy API fails, just return the mapped organizations
|
|
console.warn('Failed to fetch unmapped Addigy policies:', addigyError);
|
|
return NextResponse.json({
|
|
mappings: mappings,
|
|
totalMapped: mappings.length,
|
|
totalUnmapped: 0,
|
|
warning: 'Could not fetch unmapped policies from Addigy API. Check API configuration.',
|
|
});
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({ mappings });
|
|
} catch (error) {
|
|
console.error('Error fetching Addigy org mappings:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to fetch Addigy org mappings' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const body = await request.json();
|
|
const {
|
|
addigyOrgId,
|
|
addigyOrgName,
|
|
autotaskCompanyId,
|
|
autotaskCompanyName,
|
|
} = body;
|
|
|
|
if (!addigyOrgId || !autotaskCompanyId) {
|
|
return NextResponse.json(
|
|
{ error: 'Missing required fields' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Insert or update mapping
|
|
const result = await pool.query<{
|
|
id: number;
|
|
addigy_org_id: string;
|
|
addigy_org_name: string;
|
|
autotask_company_id: number;
|
|
autotask_company_name: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}>(
|
|
`INSERT INTO addigy_org_mappings
|
|
(addigy_org_id, addigy_org_name, autotask_company_id, autotask_company_name)
|
|
VALUES ($1, $2, $3, $4)
|
|
ON CONFLICT (addigy_org_id)
|
|
DO UPDATE SET
|
|
autotask_company_id = EXCLUDED.autotask_company_id,
|
|
autotask_company_name = EXCLUDED.autotask_company_name,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
RETURNING *`,
|
|
[addigyOrgId, addigyOrgName, autotaskCompanyId, autotaskCompanyName]
|
|
);
|
|
|
|
const mapping: AddigyOrgMapping = {
|
|
id: result.rows[0].id,
|
|
addigyOrgId: result.rows[0].addigy_org_id,
|
|
addigyOrgName: result.rows[0].addigy_org_name,
|
|
autotaskCompanyId: result.rows[0].autotask_company_id,
|
|
autotaskCompanyName: result.rows[0].autotask_company_name,
|
|
createdAt: result.rows[0].created_at,
|
|
updatedAt: result.rows[0].updated_at,
|
|
};
|
|
|
|
return NextResponse.json({ mapping });
|
|
} catch (error) {
|
|
console.error('Error creating Addigy org mapping:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to create Addigy org mapping' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
export async function DELETE(request: NextRequest) {
|
|
try {
|
|
const searchParams = request.nextUrl.searchParams;
|
|
const id = searchParams.get('id');
|
|
|
|
if (!id) {
|
|
return NextResponse.json(
|
|
{ error: 'Missing mapping ID' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
await pool.query('DELETE FROM addigy_org_mappings WHERE id = $1', [
|
|
parseInt(id),
|
|
]);
|
|
|
|
return NextResponse.json({ success: true });
|
|
} catch (error) {
|
|
console.error('Error deleting Addigy org mapping:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to delete Addigy org mapping' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|