feat: Morning NOC Summary adaptive card for Teams
- Add MorningSummaryService with Zabbix aggregation and adaptive card builder - Add webhook delivery system with Teams incoming webhooks - Add admin UI at /admin/morning-summary for webhook/config management - Add API routes: /send, /test, /webhooks, /webhooks/[id], /config, /history - Register morning-summary cron job in SyncScheduler (Mon-Fri 6:30 AM) - Add outages_only filter (Unavailable triggers only) - Fix host resolution: use getTriggerEnabledHosts to exclude disabled hosts - Fix resolved events: event.get value:1 scoped to window with r_eventid filter - Remove emojis from fact rows and section headers in card - Remove Open Zabbix button (duplicate of View Problems) - Add migrations: morning_summary_config + morning_summaries tables - Add outages_only column to morning_summary_config
This commit is contained in:
parent
19605f82aa
commit
c518eefdb2
61 changed files with 11236 additions and 237 deletions
85
app/api/data/contracts/[id]/services/route.ts
Normal file
85
app/api/data/contracts/[id]/services/route.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* Contract Services Detail API
|
||||
* GET /api/data/contracts/[id]/services - Returns a contract with all its service lines
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import postgresClient from '@/lib/services/postgres-client';
|
||||
|
||||
export async function GET(
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
const contractId = parseInt(id);
|
||||
if (isNaN(contractId)) {
|
||||
return NextResponse.json({ error: 'Invalid contract ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
const contractResult = await postgresClient.query(
|
||||
`SELECT ct.*, c.company_name
|
||||
FROM contracts ct
|
||||
LEFT JOIN companies c ON c.id = ct.company_id
|
||||
WHERE ct.id = $1 AND ct.is_deleted = false`,
|
||||
[contractId]
|
||||
);
|
||||
|
||||
if (contractResult.rows.length === 0) {
|
||||
return NextResponse.json({ error: 'Contract not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const contract = contractResult.rows[0];
|
||||
|
||||
const servicesResult = await postgresClient.query(
|
||||
`SELECT
|
||||
cs.id,
|
||||
cs.service_id,
|
||||
cs.service_name,
|
||||
cs.description,
|
||||
cs.unit_price,
|
||||
cs.unit_cost,
|
||||
cs.quantity,
|
||||
cs.adjusted_price,
|
||||
cs.period_type,
|
||||
cs.start_date,
|
||||
cs.end_date,
|
||||
s.name AS catalog_name
|
||||
FROM contract_services cs
|
||||
LEFT JOIN autotask_services s ON s.id = cs.service_id
|
||||
WHERE cs.contract_id = $1 AND cs.is_deleted = false
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN COALESCE(cs.service_name, s.name) ILIKE '%workstation%backup%'
|
||||
OR COALESCE(cs.service_name, s.name) ILIKE '%w/ backup%'
|
||||
OR COALESCE(cs.service_name, s.name) ILIKE '%windows server%'
|
||||
OR COALESCE(cs.service_name, s.name) ILIKE '%server virtual%'
|
||||
OR COALESCE(cs.service_name, s.name) ILIKE '%server phys%'
|
||||
OR COALESCE(cs.service_name, s.name) ILIKE '%esxi host%'
|
||||
THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
COALESCE(cs.service_name, s.name)`,
|
||||
[contractId]
|
||||
);
|
||||
|
||||
const periodLabels: Record<number, string> = {
|
||||
1: 'Monthly',
|
||||
2: 'Quarterly',
|
||||
3: 'Semi-Annual',
|
||||
4: 'Annual',
|
||||
5: 'One-Time',
|
||||
};
|
||||
|
||||
const services = servicesResult.rows.map((row) => ({
|
||||
...row,
|
||||
display_name: row.service_name || row.catalog_name || `Service #${row.service_id}`,
|
||||
period_label: row.period_type ? (periodLabels[row.period_type] ?? `Type ${row.period_type}`) : null,
|
||||
}));
|
||||
|
||||
return NextResponse.json({ contract, services });
|
||||
} catch (error) {
|
||||
console.error('[CONTRACT-SERVICES-API] Error:', error);
|
||||
return NextResponse.json({ error: 'Failed to fetch contract services' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue