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>
180 lines
6.9 KiB
TypeScript
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;
|