Add data import export functions

This commit is contained in:
2026-08-26 23:52:34 +03:00
parent 99d6123f13
commit ce70b7322f
5 changed files with 67 additions and 1 deletions
+45
View File
@@ -26,6 +26,51 @@ async function insertItems(client, serviceOrderId, items) {
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
const dataTables = [
{ name: 'customers', columns: ['customer_id', 'first_name', 'last_name', 'email', 'phone', 'created_at'] },
{ name: 'employees', columns: ['employee_id', 'first_name', 'last_name', 'role', 'email', 'active'] },
{ name: 'vehicles', columns: ['vehicle_id', 'customer_id', 'registration_number', 'make', 'model', 'model_year', 'engine', 'transmission', 'fuel_type', 'color', 'vin', 'mileage', 'created_at'] },
{ name: 'appointments', columns: ['appointment_id', 'vehicle_id', 'employee_id', 'scheduled_at', 'reason', 'status', 'created_at'] },
{ name: 'service_orders', columns: ['service_order_id', 'vehicle_id', 'assigned_employee_id', 'opened_at', 'closed_at', 'complaint', 'service_date', 'diagnosis', 'status'] },
{ name: 'service_order_items', columns: ['item_id', 'service_order_id', 'item_type', 'description', 'part_number', 'labor_time_minutes', 'quantity', 'unit_price', 'created_at'] },
{ name: 'invoices', columns: ['invoice_id', 'service_order_id', 'issued_at', 'due_at', 'status', 'tax_rate'] },
{ name: 'app_users', columns: ['user_id', 'username', 'display_name', 'email', 'role', 'can_read', 'can_add', 'can_delete', 'created_at'] }
];
app.get('/api/data/export', async (_request, response) => {
try {
const tables = {};
for (const table of dataTables) {
const result = await pool.query(`SELECT ${table.columns.join(', ')} FROM ${table.name} ORDER BY 1`);
tables[table.name] = result.rows;
}
response.json({ format: 'car-service-database', version: 1, exportedAt: new Date().toISOString(), tables });
} catch (error) { response.status(500).json({ error: 'Unable to export database data.' }); }
});
app.post('/api/data/import', async (request, response) => {
const snapshot = request.body;
if (!snapshot || snapshot.format !== 'car-service-database' || snapshot.version !== 1 || !snapshot.tables || typeof snapshot.tables !== 'object') return response.status(400).json({ error: 'Invalid backup file.' });
for (const table of dataTables) if (!Array.isArray(snapshot.tables[table.name])) return response.status(400).json({ error: `Backup is missing the ${table.name} table.` });
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query('TRUNCATE TABLE invoices, service_order_items, service_orders, appointments, vehicles, employees, customers, app_users RESTART IDENTITY CASCADE');
for (const table of dataTables) {
for (const row of snapshot.tables[table.name]) {
const values = table.columns.map((column) => row[column] === undefined ? null : row[column]);
const placeholders = values.map((_value, index) => `$${index + 1}`).join(', ');
await client.query(`INSERT INTO ${table.name} (${table.columns.join(', ')}) VALUES (${placeholders})`, values);
}
}
await client.query('COMMIT');
response.json({ imported: dataTables.reduce((total, table) => total + snapshot.tables[table.name].length, 0) });
} catch (error) {
await client.query('ROLLBACK');
response.status(400).json({ error: 'Import failed. No existing data was changed.' });
} finally { client.release(); }
});
app.get('/api/summary', async (_request, response) => {
try {
const result = await pool.query(`SELECT (SELECT COUNT(*)::int FROM customers) AS customers, (SELECT COUNT(*)::int FROM vehicles) AS vehicles, (SELECT COUNT(*)::int FROM service_orders WHERE status NOT IN ('closed', 'cancelled')) AS active_orders, (SELECT COUNT(*)::int FROM appointments WHERE scheduled_at >= CURRENT_DATE AND scheduled_at < CURRENT_DATE + INTERVAL '1 day' AND status NOT IN ('cancelled', 'no_show')) AS today_appointments`);