+233
-5
@@ -4,7 +4,7 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import cors from 'cors';
|
||||
import { dbReady } from './db/index.js';
|
||||
import { dbReady, run, query } from './db/index.js';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
dotenv.config({ path: path.join(__dirname, '.env') });
|
||||
@@ -12,12 +12,32 @@ import { getProducts } from './routes/products.js';
|
||||
import { getBlockTypes } from './routes/blockTypes.js';
|
||||
import { getFinishes } from './routes/finishes.js';
|
||||
import { getServiceOptions } from './routes/serviceOptions.js';
|
||||
import { getSettings } from './routes/settings.js';
|
||||
import { getSettings, updateSettings } from './routes/settings.js';
|
||||
import {
|
||||
ensureEngineeringRulesSeed,
|
||||
getEngineeringRules,
|
||||
createEngineeringRule,
|
||||
updateEngineeringRule,
|
||||
deleteEngineeringRule
|
||||
} from './routes/engineeringRules.js';
|
||||
import { postPrice } from './routes/price.js';
|
||||
import { getOrders, postOrder } from './routes/orders.js';
|
||||
import {
|
||||
getOrders,
|
||||
postOrder,
|
||||
getOrderSupplierVouchers,
|
||||
listOrderPlanExports,
|
||||
createOrderPlanExport,
|
||||
downloadOrderPlanExport,
|
||||
generateOrderPlanExports,
|
||||
getOrderProductionDetails,
|
||||
deleteOrderPlanExport
|
||||
} from './routes/orders.js';
|
||||
import { getSuppliers, createSupplier, updateSupplier, deleteSupplier } from './routes/suppliers.js';
|
||||
import {
|
||||
triggerFormexSync,
|
||||
streamFormexSync,
|
||||
triggerHaefeleSync,
|
||||
streamHaefeleSync,
|
||||
getLogs,
|
||||
getStatus,
|
||||
getFormexMaterials,
|
||||
@@ -26,7 +46,9 @@ import {
|
||||
updateFormexMaterial,
|
||||
deleteFormexMaterial,
|
||||
getFormexCollections,
|
||||
getFormexCategories
|
||||
getFormexCategories,
|
||||
getFormexSuppliers,
|
||||
getHaefeleMaterials
|
||||
} from './routes/sync.js';
|
||||
import {
|
||||
uploadMiddleware,
|
||||
@@ -53,7 +75,11 @@ import {
|
||||
generatePlansRoute,
|
||||
generateCuttingListRoute,
|
||||
exportCuttingListText,
|
||||
exportPDFHtml
|
||||
exportPDFHtml,
|
||||
exportCncRoute,
|
||||
exportMprRoute,
|
||||
exportMprPieceRoute,
|
||||
generateHardwareListRoute
|
||||
} from './routes/plans.js';
|
||||
import {
|
||||
designTipsRoute,
|
||||
@@ -76,13 +102,33 @@ app.get('/api/block-types', getBlockTypes);
|
||||
app.get('/api/finishes', getFinishes);
|
||||
app.get('/api/service-options', getServiceOptions);
|
||||
app.get('/api/settings', getSettings);
|
||||
app.put('/api/settings', updateSettings);
|
||||
app.get('/api/engineering-rules', getEngineeringRules);
|
||||
app.post('/api/engineering-rules', createEngineeringRule);
|
||||
app.put('/api/engineering-rules/:id', updateEngineeringRule);
|
||||
app.delete('/api/engineering-rules/:id', deleteEngineeringRule);
|
||||
app.post('/api/price', postPrice);
|
||||
app.get('/api/orders', getOrders);
|
||||
app.post('/api/orders', postOrder);
|
||||
app.get('/api/orders/:id/supplier-vouchers', getOrderSupplierVouchers);
|
||||
app.get('/api/orders/:id/plan-exports', listOrderPlanExports);
|
||||
app.post('/api/orders/:id/plan-exports', createOrderPlanExport);
|
||||
app.post('/api/orders/:id/plan-exports/generate', generateOrderPlanExports);
|
||||
app.get('/api/orders/:id/plan-exports/:exportId/download', downloadOrderPlanExport);
|
||||
app.delete('/api/orders/:id/plan-exports/:exportId', deleteOrderPlanExport);
|
||||
app.get('/api/orders/:id/production-details', getOrderProductionDetails);
|
||||
|
||||
// Supplier routes
|
||||
app.get('/api/suppliers', getSuppliers);
|
||||
app.post('/api/suppliers', createSupplier);
|
||||
app.put('/api/suppliers/:id', updateSupplier);
|
||||
app.delete('/api/suppliers/:id', deleteSupplier);
|
||||
|
||||
// Sync routes (Formex crawler)
|
||||
app.post('/api/sync/formex', triggerFormexSync);
|
||||
app.get('/api/sync/formex/stream', streamFormexSync);
|
||||
app.post('/api/sync/haefele', triggerHaefeleSync);
|
||||
app.get('/api/sync/haefele/stream', streamHaefeleSync);
|
||||
app.get('/api/sync/logs', getLogs);
|
||||
app.get('/api/sync/status', getStatus);
|
||||
|
||||
@@ -94,6 +140,8 @@ app.put('/api/formex/materials/:id', updateFormexMaterial);
|
||||
app.delete('/api/formex/materials/:id', deleteFormexMaterial);
|
||||
app.get('/api/formex/collections', getFormexCollections);
|
||||
app.get('/api/formex/categories', getFormexCategories);
|
||||
app.get('/api/formex/suppliers', getFormexSuppliers);
|
||||
app.get('/api/haefele/materials', getHaefeleMaterials);
|
||||
|
||||
// Room analysis routes (AI-powered)
|
||||
app.post('/api/room/analyze', uploadMiddleware, analyzeRoom);
|
||||
@@ -125,8 +173,12 @@ app.post('/api/ai/render', renderRoute);
|
||||
// Plans and cutting list routes
|
||||
app.post('/api/plans/generate', generatePlansRoute);
|
||||
app.post('/api/plans/cutting-list', generateCuttingListRoute);
|
||||
app.post('/api/plans/hardware-list', generateHardwareListRoute);
|
||||
app.post('/api/plans/export-text', exportCuttingListText);
|
||||
app.post('/api/plans/export-pdf', exportPDFHtml);
|
||||
app.post('/api/plans/export-cnc', exportCncRoute);
|
||||
app.post('/api/plans/export-mpr', exportMprRoute);
|
||||
app.post('/api/plans/export-mpr-piece', exportMprPieceRoute);
|
||||
|
||||
if (hasClientBuild) {
|
||||
app.use(express.static(clientDist));
|
||||
@@ -152,6 +204,182 @@ if (hasClientBuild) {
|
||||
|
||||
await dbReady;
|
||||
|
||||
// Apply migration 007: suppliers table + supplier_id columns
|
||||
async function applyMigration007() {
|
||||
try {
|
||||
await run(`CREATE TABLE IF NOT EXISTS suppliers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT UNIQUE NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
active INTEGER DEFAULT 1,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
)`);
|
||||
|
||||
// Add supplier_id to formex_materials if missing
|
||||
const fmCols = (await query("PRAGMA table_info(formex_materials)")).rows;
|
||||
if (!fmCols.find(c => c.name === 'supplier_id')) {
|
||||
await run('ALTER TABLE formex_materials ADD COLUMN supplier_id INTEGER REFERENCES suppliers(id)');
|
||||
}
|
||||
// Add supplier_id to blum_hardware if missing
|
||||
const bhCols = (await query("PRAGMA table_info(blum_hardware)")).rows;
|
||||
if (!bhCols.find(c => c.name === 'supplier_id')) {
|
||||
await run('ALTER TABLE blum_hardware ADD COLUMN supplier_id INTEGER REFERENCES suppliers(id)');
|
||||
}
|
||||
// Add supplier_id to handle_variants if missing
|
||||
const hvCols = (await query("PRAGMA table_info(handle_variants)")).rows;
|
||||
if (!hvCols.find(c => c.name === 'supplier_id')) {
|
||||
await run('ALTER TABLE handle_variants ADD COLUMN supplier_id INTEGER REFERENCES suppliers(id)');
|
||||
}
|
||||
// Add supplier_id to hinge_variants if missing
|
||||
const hingeCols = (await query("PRAGMA table_info(hinge_variants)")).rows;
|
||||
if (!hingeCols.find(c => c.name === 'supplier_id')) {
|
||||
await run('ALTER TABLE hinge_variants ADD COLUMN supplier_id INTEGER REFERENCES suppliers(id)');
|
||||
}
|
||||
|
||||
// Seed default suppliers
|
||||
await run(`INSERT OR IGNORE INTO suppliers (code, name, email, type) VALUES
|
||||
('formex', 'Formex', '[email protected]', 'panels'),
|
||||
('blum', 'Blum Switzerland', '[email protected]', 'hardware'),
|
||||
('haefele', 'Hafele', '[email protected]', 'materials')`);
|
||||
|
||||
// Assign existing rows to their suppliers
|
||||
await run(`UPDATE formex_materials SET supplier_id = (SELECT id FROM suppliers WHERE code = 'formex') WHERE supplier_id IS NULL`);
|
||||
await run(`UPDATE blum_hardware SET supplier_id = (SELECT id FROM suppliers WHERE code = 'blum') WHERE supplier_id IS NULL`);
|
||||
await run(`UPDATE handle_variants SET supplier_id = (SELECT id FROM suppliers WHERE code = 'blum') WHERE supplier_id IS NULL`);
|
||||
await run(`UPDATE hinge_variants SET supplier_id = (SELECT id FROM suppliers WHERE code = 'blum') WHERE supplier_id IS NULL`);
|
||||
|
||||
console.log('[migration-007] Suppliers table ready.');
|
||||
} catch (err) {
|
||||
console.error('[migration-007] Error:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
await applyMigration007();
|
||||
|
||||
// Apply migration 008: price_unit column on formex_materials
|
||||
async function applyMigration008() {
|
||||
try {
|
||||
const cols = (await query("PRAGMA table_info(formex_materials)")).rows;
|
||||
if (!cols.find(c => c.name === 'price_unit')) {
|
||||
await run('ALTER TABLE formex_materials ADD COLUMN price_unit TEXT');
|
||||
console.log('[migration-008] Added price_unit column to formex_materials.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[migration-008] Error:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
await applyMigration008();
|
||||
|
||||
// Apply migration 009: persist generated plan exports
|
||||
async function applyMigration009() {
|
||||
try {
|
||||
await run(`CREATE TABLE IF NOT EXISTS plan_exports (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_id INTEGER NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
|
||||
export_type TEXT NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
file_path TEXT NOT NULL,
|
||||
mime_type TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
)`);
|
||||
await run('CREATE INDEX IF NOT EXISTS idx_plan_exports_order ON plan_exports(order_id)');
|
||||
console.log('[migration-009] Plan exports table ready.');
|
||||
} catch (err) {
|
||||
console.error('[migration-009] Error:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
await applyMigration009();
|
||||
|
||||
async function applyMigration010() {
|
||||
try {
|
||||
const cols = (await query("PRAGMA table_info(formex_materials)")).rows;
|
||||
if (!cols.find(c => c.name === 'bore_diameter_mm')) {
|
||||
await run('ALTER TABLE formex_materials ADD COLUMN bore_diameter_mm REAL');
|
||||
console.log('[migration-010] Added bore_diameter_mm to formex_materials.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[migration-010] Error:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
await applyMigration010();
|
||||
|
||||
async function applyMigration011() {
|
||||
try {
|
||||
const cols = (await query("PRAGMA table_info(formex_materials)")).rows;
|
||||
const colNames = cols.map(c => c.name);
|
||||
if (!colNames.includes('bore_depth_base_mm')) {
|
||||
await run('ALTER TABLE formex_materials ADD COLUMN bore_depth_base_mm REAL');
|
||||
console.log('[migration-011] Added bore_depth_base_mm to formex_materials.');
|
||||
}
|
||||
if (!colNames.includes('bore_depth_piece_mm')) {
|
||||
await run('ALTER TABLE formex_materials ADD COLUMN bore_depth_piece_mm REAL');
|
||||
console.log('[migration-011] Added bore_depth_piece_mm to formex_materials.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[migration-011] Error:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
await applyMigration011();
|
||||
|
||||
async function applyMigration012() {
|
||||
try {
|
||||
const cols = (await query("PRAGMA table_info(formex_materials)")).rows;
|
||||
const colNames = cols.map(c => c.name);
|
||||
if (!colNames.includes('diameter_mm')) {
|
||||
await run('ALTER TABLE formex_materials ADD COLUMN diameter_mm REAL');
|
||||
console.log('[migration-012] Added diameter_mm to formex_materials.');
|
||||
}
|
||||
if (!colNames.includes('length_mm')) {
|
||||
await run('ALTER TABLE formex_materials ADD COLUMN length_mm REAL');
|
||||
console.log('[migration-012] Added length_mm to formex_materials.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[migration-012] Error:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
await applyMigration012();
|
||||
|
||||
async function applyMigration013() {
|
||||
try {
|
||||
await run(`CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
)`);
|
||||
|
||||
const defaults = [
|
||||
['allow_sliding_doors', '0'],
|
||||
['sanitary_gap_mm', '30'],
|
||||
['drawer_depth_min_mm', '400'],
|
||||
['drawer_depth_max_mm', '650'],
|
||||
['drawer_depth_step_mm', '50'],
|
||||
['drawer_back_gap_mm', '10'],
|
||||
['drawer_hidden_handle_reduction_mm', '40'],
|
||||
['standard_handle_axis_mm', '1000'],
|
||||
['standard_handle_tolerance_mm', '200'],
|
||||
['groove_width_mm', '9'],
|
||||
['groove_depth_mm', '10.5'],
|
||||
['groove_edge_offset_mm', '16']
|
||||
];
|
||||
|
||||
for (const [key, value] of defaults) {
|
||||
await run('INSERT OR IGNORE INTO settings (key, value) VALUES ($1, $2)', [key, value]);
|
||||
}
|
||||
console.log('[migration-013] Rule settings defaults ready.');
|
||||
} catch (err) {
|
||||
console.error('[migration-013] Error:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
await applyMigration013();
|
||||
await ensureEngineeringRulesSeed();
|
||||
|
||||
// Initialize the scheduler for automated sync
|
||||
initScheduler();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user