Files
sap-erp/services/sqlPool.js
T
John 69b4e68baf
SAP-ERP Portal CI/CD / build (push) Failing after 5m20s
first commit
2026-09-23 17:31:02 +05:30

103 lines
3.8 KiB
JavaScript

// services/sqlPool.js
// MSSQL connection pool(s) for all portal stores' direct-SQL access to SAP
// tables. ONE POOL PER DATABASE NAME — the SQL Server login (SQL_USER/
// SQL_PASSWORD) is a single server-level credential that can reach every SAP
// B1 company database on that server (unlike SAP Service Layer, where each
// company has its own OUSR table with its own per-company password — see
// [[per-company-sap-login]]), so only the DATABASE a pool points at needs to
// vary, not the credentials. getPool() with no argument keeps its original
// behavior (the .env SQL_DATABASE default) for 100% backward compat with
// every existing caller; pass a companyDB to get/create a pool for that
// specific database instead.
//
// Bug this fixes (2026-08-04): getPool() used to be a SINGLE global pool
// hardcoded to .env's SQL_DATABASE with no way to target any other company at
// all — every direct-SQL route (routes/sap.js's item/warehouse/etc. lookups,
// routes/workOrders.js, reports, dashboards, GST, general ledger…) silently
// queried the same one database regardless of which company a user selected
// or was restricted to. Using ConnectionPool directly (not the global
// sql.connect() singleton) avoids conflicts between concurrently-initialising
// pools for different databases.
'use strict';
const sql = require('mssql');
const _pools = new Map(); // dbName → { pool, connecting, waiters }
const DEFAULT_DB = process.env.SQL_DATABASE;
function buildConfig(database) {
return {
server: process.env.SQL_SERVER_NAME || process.env.SQL_HOST,
port: parseInt(process.env.SQL_PORT) || 1433,
user: process.env.SQL_USER,
password: process.env.SQL_PASSWORD,
database,
connectionTimeout: 30000,
requestTimeout: 60000,
pool: { max: 10, min: 0, idleTimeoutMillis: 30000 },
options: {
encrypt: true,
trustServerCertificate: true,
enableArithAbort: true,
// SQL Server 2017 on this host requires TLS 1.0.
// Node.js 17+ disables TLS 1.0 by default — re-enable it for this pool.
cryptoCredentialsDetails: { minVersion: 'TLSv1' },
},
};
}
async function getPool(companyDB) {
const db = companyDB || DEFAULT_DB;
let entry = _pools.get(db);
if (!entry) { entry = { pool: null, connecting: false, waiters: [] }; _pools.set(db, entry); }
// Return healthy pool immediately
if (entry.pool && entry.pool.connected) return entry.pool;
// Queue if already connecting
if (entry.connecting) {
return new Promise((resolve, reject) => entry.waiters.push({ resolve, reject }));
}
entry.connecting = true;
try {
const pool = new sql.ConnectionPool(buildConfig(db));
// Reset on pool-level errors so the next caller reconnects
pool.on('error', err => {
console.error(`[SQL-POOL] Pool error (${db}) — resetting:`, err.message);
entry.pool = null;
});
await pool.connect();
entry.pool = pool;
console.log(`[SQL-POOL] ✅ Connected ${db} @ ${process.env.SQL_HOST}`);
entry.waiters.forEach(w => w.resolve(entry.pool));
return entry.pool;
} catch (err) {
entry.pool = null;
console.error(`[SQL-POOL] ❌ Connection failed (${db}):`, err.message);
entry.waiters.forEach(w => w.reject(err));
throw err;
} finally {
entry.connecting = false;
entry.waiters = [];
}
}
// Parameterised query helper — use ? as placeholder, works like the legacy per-store exec()
async function query(sqlText, params = [], companyDB) {
const pool = await getPool(companyDB);
const req = pool.request();
let i = 0;
const text = sqlText.replace(/\?/g, () => {
const name = `p${i}`;
req.input(name, params[i]);
i++;
return `@${name}`;
});
const result = await req.query(text);
return result.recordset || [];
}
module.exports = { getPool, query };