Added more functionality
This commit is contained in:
@@ -34,8 +34,8 @@ app.get('/api/service-records', async (_request, response) => {
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT so.service_order_id, so.status, so.complaint, so.opened_at, so.service_date,
|
||||
btrim(v.vin) AS vin, v.registration_number AS license_plate, v.mileage,
|
||||
c.first_name || ' ' || c.last_name AS customer_name, c.email AS customer_email,
|
||||
v.vehicle_id, btrim(v.vin) AS vin, v.registration_number AS license_plate, v.make, v.model, v.model_year, v.engine, v.transmission, v.fuel_type, v.color, v.mileage,
|
||||
c.first_name || ' ' || c.last_name AS customer_name, c.email AS customer_email, c.phone AS customer_phone,
|
||||
COALESCE(e.first_name || ' ' || e.last_name, 'Unassigned') AS technician
|
||||
FROM service_orders so
|
||||
JOIN vehicles v ON v.vehicle_id = so.vehicle_id
|
||||
@@ -48,8 +48,72 @@ app.get('/api/service-records', async (_request, response) => {
|
||||
} catch (error) { response.status(500).json({ error: 'Unable to load service records.' }); }
|
||||
});
|
||||
|
||||
app.get('/api/vehicles', async (_request, response) => {
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT v.vehicle_id, v.registration_number AS license_plate, btrim(v.vin) AS vin,
|
||||
v.make, v.model, v.model_year, v.engine, v.transmission, v.fuel_type, v.color, v.mileage,
|
||||
c.first_name || ' ' || c.last_name AS customer_name, c.email AS customer_email,
|
||||
COUNT(so.service_order_id)::int AS service_record_count
|
||||
FROM vehicles v
|
||||
JOIN customers c ON c.customer_id = v.customer_id
|
||||
LEFT JOIN service_orders so ON so.vehicle_id = v.vehicle_id
|
||||
GROUP BY v.vehicle_id, c.customer_id
|
||||
ORDER BY v.created_at DESC
|
||||
`);
|
||||
response.json(result.rows);
|
||||
} catch (error) { response.status(500).json({ error: 'Unable to load vehicles.' }); }
|
||||
});
|
||||
|
||||
app.get('/api/vehicles/lookup', async (request, response) => {
|
||||
const identifier = String(request.query.identifier || '').trim();
|
||||
if (!identifier) return response.status(400).json({ error: 'Enter a VIN or license plate.' });
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT v.vehicle_id, v.registration_number AS license_plate, btrim(v.vin) AS vin,
|
||||
v.make, v.model, v.model_year, v.engine, v.transmission, v.fuel_type, v.color, v.mileage,
|
||||
c.first_name || ' ' || c.last_name AS customer_name, c.email AS customer_email, c.phone AS customer_phone
|
||||
FROM vehicles v JOIN customers c ON c.customer_id = v.customer_id
|
||||
WHERE UPPER(v.registration_number) = UPPER($1) OR btrim(v.vin) = UPPER($1)
|
||||
LIMIT 1
|
||||
`, [identifier]);
|
||||
if (!result.rowCount) return response.status(404).json({ error: 'No vehicle found for that VIN or license plate.' });
|
||||
response.json(result.rows[0]);
|
||||
} catch (error) { response.status(500).json({ error: 'Unable to look up vehicle.' }); }
|
||||
});
|
||||
|
||||
app.post('/api/vehicles', async (request, response) => {
|
||||
const { customerName, customerEmail, customerPhone, vin, licensePlate, make, model, modelYear, engine, transmission, fuelType, color, mileage } = request.body;
|
||||
const parsedMileage = Number(mileage || 0);
|
||||
if (!customerName || !vin || !licensePlate || !make || !model || !Number.isInteger(parsedMileage) || parsedMileage < 0) return response.status(400).json({ error: 'Customer, VIN, license plate, make, model, and non-negative mileage are required.' });
|
||||
const customerParts = customerName.trim().split(/\s+/);
|
||||
const firstName = customerParts.shift();
|
||||
const lastName = customerParts.join(' ') || firstName;
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
const existingCustomer = customerEmail?.trim() ? await client.query(`SELECT customer_id FROM customers WHERE LOWER(email) = LOWER($1) LIMIT 1`, [customerEmail.trim()]) : { rowCount: 0, rows: [] };
|
||||
let customerId;
|
||||
if (existingCustomer.rowCount) {
|
||||
customerId = existingCustomer.rows[0].customer_id;
|
||||
await client.query(`UPDATE customers SET first_name = $1, last_name = $2, phone = NULLIF($3, '') WHERE customer_id = $4`, [firstName, lastName, customerPhone?.trim() || '', customerId]);
|
||||
} else {
|
||||
const customer = await client.query(`INSERT INTO customers (first_name, last_name, email, phone) VALUES ($1, $2, NULLIF($3, ''), NULLIF($4, '')) RETURNING customer_id`, [firstName, lastName, customerEmail?.trim() || '', customerPhone?.trim() || '']);
|
||||
customerId = customer.rows[0].customer_id;
|
||||
}
|
||||
const vehicle = await client.query(`INSERT INTO vehicles (customer_id, registration_number, make, model, model_year, engine, transmission, fuel_type, color, vin, mileage) VALUES ($1, $2, $3, $4, NULLIF($5, '')::integer, NULLIF($6, ''), NULLIF($7, ''), NULLIF($8, ''), NULLIF($9, ''), $10, $11) RETURNING vehicle_id`, [customerId, licensePlate.trim(), make.trim(), model.trim(), modelYear || '', engine?.trim() || '', transmission?.trim() || '', fuelType?.trim() || '', color?.trim() || '', vin.trim(), parsedMileage]);
|
||||
await client.query('COMMIT');
|
||||
response.status(201).json({ vehicle_id: vehicle.rows[0].vehicle_id });
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
const status = error.code === '23505' ? 409 : 500;
|
||||
const message = error.constraint === 'vehicles_vin_key' ? 'That VIN is already registered.' : error.constraint === 'vehicles_registration_number_key' ? 'That license plate is already registered.' : status === 409 ? 'A customer with that email already exists.' : 'Unable to create vehicle.';
|
||||
response.status(status).json({ error: message });
|
||||
} finally { client.release(); }
|
||||
});
|
||||
|
||||
app.post('/api/service-records', async (request, response) => {
|
||||
const { customerName, customerEmail, vin, licensePlate, mileage, serviceDate, technicianName, complaint } = request.body;
|
||||
const { customerName, customerEmail, customerPhone, vin, licensePlate, mileage, serviceDate, technicianName, complaint, make, model, modelYear, engine, transmission, fuelType, color } = request.body;
|
||||
const parsedMileage = Number(mileage);
|
||||
if (!customerName || !vin || !licensePlate || !technicianName || !complaint || !serviceDate || !Number.isInteger(parsedMileage) || parsedMileage < 0) {
|
||||
return response.status(400).json({ error: 'Customer, VIN, license plate, non-negative mileage, service date, technician, and complaint are required.' });
|
||||
@@ -64,18 +128,30 @@ app.post('/api/service-records', async (request, response) => {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
const customer = await client.query(
|
||||
`INSERT INTO customers (first_name, last_name, email) VALUES ($1, $2, NULLIF($3, '')) RETURNING customer_id`,
|
||||
[firstName, lastName, customerEmail?.trim() || '']
|
||||
);
|
||||
const existingVehicle = await client.query(`SELECT vehicle_id, customer_id FROM vehicles WHERE UPPER(registration_number) = UPPER($1) OR btrim(vin) = UPPER($2) LIMIT 1`, [licensePlate.trim(), vin.trim()]);
|
||||
let customerId;
|
||||
if (existingVehicle.rowCount) {
|
||||
customerId = existingVehicle.rows[0].customer_id;
|
||||
await client.query(`UPDATE customers SET first_name = $1, last_name = $2, email = NULLIF($3, ''), phone = NULLIF($4, '') WHERE customer_id = $5`, [firstName, lastName, customerEmail?.trim() || '', customerPhone?.trim() || '', customerId]);
|
||||
} else {
|
||||
const existingCustomer = customerEmail?.trim()
|
||||
? await client.query(`SELECT customer_id FROM customers WHERE LOWER(email) = LOWER($1) LIMIT 1`, [customerEmail.trim()])
|
||||
: { rowCount: 0, rows: [] };
|
||||
if (existingCustomer.rowCount) {
|
||||
customerId = existingCustomer.rows[0].customer_id;
|
||||
await client.query(`UPDATE customers SET first_name = $1, last_name = $2, phone = NULLIF($3, '') WHERE customer_id = $4`, [firstName, lastName, customerPhone?.trim() || '', customerId]);
|
||||
} else {
|
||||
const customer = await client.query(`INSERT INTO customers (first_name, last_name, email, phone) VALUES ($1, $2, NULLIF($3, ''), NULLIF($4, '')) RETURNING customer_id`, [firstName, lastName, customerEmail?.trim() || '', customerPhone?.trim() || '']);
|
||||
customerId = customer.rows[0].customer_id;
|
||||
}
|
||||
}
|
||||
const technician = await client.query(
|
||||
`INSERT INTO employees (first_name, last_name, role) VALUES ($1, $2, 'technician') RETURNING employee_id`,
|
||||
[technicianFirstName, technicianLastName]
|
||||
);
|
||||
const vehicle = await client.query(
|
||||
`INSERT INTO vehicles (customer_id, registration_number, make, model, vin, mileage) VALUES ($1, $2, 'Not specified', 'Not specified', $3, $4) RETURNING vehicle_id`,
|
||||
[customer.rows[0].customer_id, licensePlate.trim(), vin.trim(), parsedMileage]
|
||||
);
|
||||
const vehicle = existingVehicle.rowCount
|
||||
? await client.query(`UPDATE vehicles SET customer_id = $1, registration_number = $2, make = COALESCE(NULLIF($3, ''), make), model = COALESCE(NULLIF($4, ''), model), model_year = NULLIF($5, '')::integer, engine = NULLIF($6, ''), transmission = NULLIF($7, ''), fuel_type = NULLIF($8, ''), color = NULLIF($9, ''), vin = $10, mileage = $11 WHERE vehicle_id = $12 RETURNING vehicle_id`, [customerId, licensePlate.trim(), make?.trim() || '', model?.trim() || '', modelYear || '', engine?.trim() || '', transmission?.trim() || '', fuelType?.trim() || '', color?.trim() || '', vin.trim(), parsedMileage, existingVehicle.rows[0].vehicle_id])
|
||||
: await client.query(`INSERT INTO vehicles (customer_id, registration_number, make, model, model_year, engine, transmission, fuel_type, color, vin, mileage) VALUES ($1, $2, NULLIF($3, ''), NULLIF($4, ''), NULLIF($5, '')::integer, NULLIF($6, ''), NULLIF($7, ''), NULLIF($8, ''), NULLIF($9, ''), $10, $11) RETURNING vehicle_id`, [customerId, licensePlate.trim(), make?.trim() || 'Not specified', model?.trim() || 'Not specified', modelYear || '', engine?.trim() || '', transmission?.trim() || '', fuelType?.trim() || '', color?.trim() || '', vin.trim(), parsedMileage]);
|
||||
const record = await client.query(
|
||||
`INSERT INTO service_orders (vehicle_id, assigned_employee_id, complaint, service_date) VALUES ($1, $2, $3, $4) RETURNING service_order_id`,
|
||||
[vehicle.rows[0].vehicle_id, technician.rows[0].employee_id, complaint.trim(), serviceDate]
|
||||
@@ -85,12 +161,13 @@ app.post('/api/service-records', async (request, response) => {
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
const status = error.code === '23505' ? 409 : 500;
|
||||
response.status(status).json({ error: status === 409 ? 'That VIN or license plate is already registered.' : 'Unable to create service record.' });
|
||||
const message = error.constraint === 'vehicles_vin_key' ? 'That VIN is already registered.' : error.constraint === 'vehicles_registration_number_key' ? 'That license plate is already registered.' : status === 409 ? 'A record with one of these values already exists.' : 'Unable to create service record.';
|
||||
response.status(status).json({ error: message });
|
||||
} finally { client.release(); }
|
||||
});
|
||||
|
||||
app.patch('/api/service-records/:id', async (request, response) => {
|
||||
const { customerName, customerEmail, vin, licensePlate, mileage, serviceDate, technicianName, complaint } = request.body;
|
||||
const { customerName, customerEmail, customerPhone, vin, licensePlate, mileage, serviceDate, technicianName, complaint, make, model, modelYear, engine, transmission, fuelType, color } = request.body;
|
||||
const parsedMileage = Number(mileage);
|
||||
if (!customerName || !vin || !licensePlate || !technicianName || !complaint || !serviceDate || !Number.isInteger(parsedMileage) || parsedMileage < 0) return response.status(400).json({ error: 'Customer, VIN, license plate, non-negative mileage, service date, technician, and complaint are required.' });
|
||||
const customerParts = customerName.trim().split(/\s+/);
|
||||
@@ -102,9 +179,9 @@ app.patch('/api/service-records/:id', async (request, response) => {
|
||||
if (!current.rowCount) { await client.query('ROLLBACK'); return response.status(404).json({ error: 'Service record not found.' }); }
|
||||
const customerFirst = customerParts.shift();
|
||||
const technicianFirst = technicianParts.shift();
|
||||
await client.query('UPDATE customers SET first_name = $1, last_name = $2, email = NULLIF($3, \'\') WHERE customer_id = $4', [customerFirst, customerParts.join(' ') || customerFirst, customerEmail?.trim() || '', current.rows[0].customer_id]);
|
||||
await client.query('UPDATE customers SET first_name = $1, last_name = $2, email = NULLIF($3, \'\'), phone = NULLIF($4, \'\') WHERE customer_id = $5', [customerFirst, customerParts.join(' ') || customerFirst, customerEmail?.trim() || '', customerPhone?.trim() || '', current.rows[0].customer_id]);
|
||||
await client.query('UPDATE employees SET first_name = $1, last_name = $2 WHERE employee_id = $3', [technicianFirst, technicianParts.join(' ') || technicianFirst, current.rows[0].assigned_employee_id]);
|
||||
await client.query('UPDATE vehicles SET vin = $1, registration_number = $2, mileage = $3 WHERE vehicle_id = $4', [vin.trim(), licensePlate.trim(), parsedMileage, current.rows[0].vehicle_id]);
|
||||
await client.query(`UPDATE vehicles SET vin = $1, registration_number = $2, make = NULLIF($3, ''), model = NULLIF($4, ''), model_year = NULLIF($5, '')::integer, engine = NULLIF($6, ''), transmission = NULLIF($7, ''), fuel_type = NULLIF($8, ''), color = NULLIF($9, ''), mileage = $10 WHERE vehicle_id = $11`, [vin.trim(), licensePlate.trim(), make?.trim() || '', model?.trim() || '', modelYear || '', engine?.trim() || '', transmission?.trim() || '', fuelType?.trim() || '', color?.trim() || '', parsedMileage, current.rows[0].vehicle_id]);
|
||||
await client.query('UPDATE service_orders SET complaint = $1, service_date = $2 WHERE service_order_id = $3', [complaint.trim(), serviceDate, request.params.id]);
|
||||
await client.query('COMMIT');
|
||||
response.json({ service_order_id: request.params.id });
|
||||
@@ -121,6 +198,20 @@ app.get('/api/customers', async (_request, response) => {
|
||||
} catch (error) { response.status(500).json({ error: 'Unable to load customers.' }); }
|
||||
});
|
||||
|
||||
app.get('/api/customers/lookup', async (request, response) => {
|
||||
const email = String(request.query.email || '').trim();
|
||||
const name = String(request.query.name || '').trim();
|
||||
if (!email && !name) return response.status(400).json({ error: 'Enter a customer email or name.' });
|
||||
try {
|
||||
const result = email
|
||||
? await pool.query(`SELECT customer_id, first_name, last_name, email, phone FROM customers WHERE LOWER(email) = LOWER($1) LIMIT 1`, [email])
|
||||
: await pool.query(`SELECT customer_id, first_name, last_name, email, phone FROM customers WHERE LOWER(first_name || ' ' || last_name) = LOWER($1) LIMIT 2`, [name]);
|
||||
if (!result.rowCount) return response.status(404).json({ error: 'No customer found.' });
|
||||
if (!email && result.rowCount > 1) return response.status(409).json({ error: 'More than one customer has that name. Use email to identify the customer.' });
|
||||
response.json(result.rows[0]);
|
||||
} catch (error) { response.status(500).json({ error: 'Unable to look up customer.' }); }
|
||||
});
|
||||
|
||||
app.post('/api/customers', async (request, response) => {
|
||||
const { firstName, lastName, email, phone } = request.body;
|
||||
if (!firstName || !lastName) return response.status(400).json({ error: 'First name and last name are required.' });
|
||||
@@ -184,6 +275,7 @@ app.delete('/api/users/:id', async (request, response) => {
|
||||
app.use((_request, response) => response.sendFile(path.join(__dirname, 'public', 'index.html')));
|
||||
|
||||
async function start() {
|
||||
await pool.query(`ALTER TABLE vehicles ADD COLUMN IF NOT EXISTS engine TEXT, ADD COLUMN IF NOT EXISTS transmission TEXT, ADD COLUMN IF NOT EXISTS fuel_type TEXT, ADD COLUMN IF NOT EXISTS color TEXT`);
|
||||
await pool.query(`ALTER TABLE service_orders ADD COLUMN IF NOT EXISTS service_date DATE`);
|
||||
await pool.query(`UPDATE service_orders SET service_date = opened_at::date WHERE service_date IS NULL`);
|
||||
await pool.query(`ALTER TABLE service_orders ALTER COLUMN service_date SET DEFAULT CURRENT_DATE`);
|
||||
|
||||
Reference in New Issue
Block a user