Files
shop-manager/server/src/routes/appointments.ts
tonym 8839412b52 Initial commit — Shop Manager for Tony Moon
Full auto mechanic shop management tool: jobs, invoices, estimates,
inspections, inventory, recommendations, technicians, appointments,
reports, file uploads, kanban board, and more.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 00:08:35 -05:00

180 lines
6.9 KiB
TypeScript

import { Router, Response } from 'express';
import db from '../database';
import { AuthRequest } from '../middleware/auth';
const router = Router();
// List appointments
router.get('/', (req: AuthRequest, res: Response) => {
try {
const { status, date, ownerId } = req.query;
let query = `
SELECT a.*, o.first_name as owner_first_name, o.last_name as owner_last_name,
o.phone as owner_phone, v.year, v.make, v.model
FROM appointments a
LEFT JOIN owners o ON a.owner_id = o.id
LEFT JOIN vehicles v ON a.vehicle_id = v.id
`;
const conditions: string[] = [];
const params: any[] = [];
if (status) { conditions.push('a.status = ?'); params.push(status); }
if (date) { conditions.push('a.requested_date = ?'); params.push(date); }
if (ownerId) { conditions.push('a.owner_id = ?'); params.push(Number(ownerId)); }
if (conditions.length > 0) query += ' WHERE ' + conditions.join(' AND ');
query += ' ORDER BY a.requested_date ASC, a.requested_time ASC';
const appointments = db.prepare(query).all(...params);
res.json(appointments);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// Get appointment
router.get('/:id', (req: AuthRequest, res: Response) => {
try {
const appointment = db.prepare(`
SELECT a.*, o.first_name as owner_first_name, o.last_name as owner_last_name,
o.phone as owner_phone, o.email as owner_email, v.year, v.make, v.model
FROM appointments a
LEFT JOIN owners o ON a.owner_id = o.id
LEFT JOIN vehicles v ON a.vehicle_id = v.id
WHERE a.id = ?
`).get(req.params.id);
if (!appointment) return res.status(404).json({ error: 'Appointment not found' });
res.json(appointment);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// Create appointment
router.post('/', (req: AuthRequest, res: Response) => {
try {
const { ownerId, shopId, vehicleId, serviceDescription, requestedDate, requestedTime, durationMinutes, notes } = req.body;
if (!requestedDate) return res.status(400).json({ error: 'Requested date required' });
const result = db.prepare(`
INSERT INTO appointments (owner_id, shop_id, vehicle_id, service_description, requested_date, requested_time, duration_minutes, notes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`).run(ownerId || null, shopId || null, vehicleId || null, serviceDescription || null,
requestedDate, requestedTime || null, durationMinutes || 60, notes || null);
const appointment = db.prepare('SELECT * FROM appointments WHERE id = ?').get(result.lastInsertRowid);
res.status(201).json(appointment);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// Update appointment
router.put('/:id', (req: AuthRequest, res: Response) => {
try {
const appt = db.prepare('SELECT * FROM appointments WHERE id = ?').get(req.params.id) as any;
if (!appt) return res.status(404).json({ error: 'Appointment not found' });
const { ownerId, vehicleId, serviceDescription, requestedDate, requestedTime, durationMinutes, status, notes } = req.body;
db.prepare(`
UPDATE appointments SET owner_id = ?, vehicle_id = ?, service_description = ?,
requested_date = ?, requested_time = ?, duration_minutes = ?, status = ?, notes = ?,
updated_at = datetime('now') WHERE id = ?
`).run(
ownerId !== undefined ? (ownerId || null) : appt.owner_id,
vehicleId !== undefined ? (vehicleId || null) : appt.vehicle_id,
serviceDescription ?? appt.service_description,
requestedDate ?? appt.requested_date,
requestedTime ?? appt.requested_time,
durationMinutes ?? appt.duration_minutes,
status ?? appt.status,
notes ?? appt.notes,
appt.id,
);
const updated = db.prepare('SELECT * FROM appointments WHERE id = ?').get(appt.id);
res.json(updated);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// Delete appointment
router.delete('/:id', (req: AuthRequest, res: Response) => {
try {
db.prepare('DELETE FROM appointments WHERE id = ?').run(req.params.id);
res.json({ success: true });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// --- Deferred Services ---
router.get('/deferred', (req: AuthRequest, res: Response) => {
try {
const { status, vehicleId } = req.query;
let query = `
SELECT d.*, o.first_name as owner_first_name, o.last_name as owner_last_name,
v.year, v.make, v.model, j.job_number
FROM deferred_services d
LEFT JOIN owners o ON d.owner_id = o.id
LEFT JOIN vehicles v ON d.vehicle_id = v.id
LEFT JOIN jobs j ON d.job_id = j.id
`;
const conditions: string[] = [];
const params: any[] = [];
if (status) { conditions.push('d.status = ?'); params.push(status); }
if (vehicleId) { conditions.push('d.vehicle_id = ?'); params.push(Number(vehicleId)); }
if (conditions.length > 0) query += ' WHERE ' + conditions.join(' AND ');
query += ' ORDER BY d.follow_up_date ASC';
const deferred = db.prepare(query).all(...params);
res.json(deferred);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
router.post('/deferred', (req: AuthRequest, res: Response) => {
try {
const { jobId, vehicleId, ownerId, description, reason, followUpDate } = req.body;
if (!description) return res.status(400).json({ error: 'Description required' });
const result = db.prepare(`
INSERT INTO deferred_services (job_id, vehicle_id, owner_id, description, reason, follow_up_date)
VALUES (?, ?, ?, ?, ?, ?)
`).run(jobId || null, vehicleId || null, ownerId || null, description, reason || null, followUpDate || null);
const deferred = db.prepare('SELECT * FROM deferred_services WHERE id = ?').get(result.lastInsertRowid);
res.status(201).json(deferred);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
router.put('/deferred/:id', (req: AuthRequest, res: Response) => {
try {
const existing = db.prepare('SELECT * FROM deferred_services WHERE id = ?').get(req.params.id) as any;
if (!existing) return res.status(404).json({ error: 'Deferred service not found' });
const { status, followUpDate } = req.body;
db.prepare('UPDATE deferred_services SET status = ?, follow_up_date = ? WHERE id = ?')
.run(status ?? existing.status, followUpDate ?? existing.follow_up_date, existing.id);
const updated = db.prepare('SELECT * FROM deferred_services WHERE id = ?').get(existing.id);
res.json(updated);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
router.delete('/deferred/:id', (req: AuthRequest, res: Response) => {
try {
db.prepare('DELETE FROM deferred_services WHERE id = ?').run(req.params.id);
res.json({ success: true });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
export default router;