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

478 lines
23 KiB
JavaScript

// backend/services/hanaVendorStore.js
// SQL persistence for vendor registrations.
// Schema: uses SAP_B1_COMPANY env var (same as hanaStore.js)
// Table: ZVENDOR_PORTAL (auto-created on first run)
const { getPool } = require('./appSqlPool');
const { DEFAULT_COMPANY } = require('./companyConfig');
const DEFAULT_SCHEMA = DEFAULT_COMPANY;
const tbl = (s) => `[dbo].[ZVENDOR_PORTAL]`;
async function exec(sqlQuery, params = []) {
const pool = await getPool();
const request = pool.request();
params.forEach((param, index) => {
request.input(`param${index}`, param);
});
const replacedSql = sqlQuery.replace(/\?/g, (match, offset, string) => {
const paramIndex = (string.slice(0, offset).match(/\?/g) || []).length;
return `@param${paramIndex}`;
});
const result = await request.query(replacedSql);
return result.recordset || [];
}
function isAlreadyExists(e) {
const m = (e.message || '').toLowerCase();
return m.includes('already exists')
|| m.includes('duplicate')
|| m.includes('existing object')
|| m.includes('there is already an object');
}
// ── Bootstrap ─────────────────────────────────────────────────────────────────
async function bootstrap(schema) {
console.log('[VENDOR STORE] Checking table', tbl(schema), '...');
let tableExisted = false;
await exec(`
CREATE TABLE ${tbl(schema)} (
[ID] INT IDENTITY(1,1) PRIMARY KEY,
[STATUS] NVARCHAR(30) DEFAULT 'PENDING',
[VENDOR_TYPE] NVARCHAR(20) DEFAULT 'SUPPLIER',
[CARD_NAME] NVARCHAR(100),
[FOREIGN_NAME] NVARCHAR(100),
[TYPE_OF_BUSINESS] NVARCHAR(50),
[INDUSTRY] NVARCHAR(100),
[PRODUCTS] NVARCHAR(500),
[PAYMENT_TERMS] NVARCHAR(50),
[CONTACT_FIRST] NVARCHAR(60),
[CONTACT_LAST] NVARCHAR(60),
[CONTACT_TITLE] NVARCHAR(60),
[MOBILE] NVARCHAR(20),
[ALT_CONTACT] NVARCHAR(20),
[EMAIL] NVARCHAR(150),
[BILL_STREET] NVARCHAR(200),
[BILL_BLOCK] NVARCHAR(100),
[BILL_CITY] NVARCHAR(100),
[BILL_ZIP] NVARCHAR(20),
[BILL_STATE] NVARCHAR(100),
[BILL_COUNTRY] NVARCHAR(60),
[GSTIN] NVARCHAR(15),
[PAN] NVARCHAR(10),
[TAN] NVARCHAR(10),
[CURRENCY] NVARCHAR(50),
[HAS_TDS] NVARCHAR(1) DEFAULT 'N',
[TDS_CATEGORY] NVARCHAR(100),
[TDS_RATE] DECIMAL(5,2) DEFAULT 0,
[TDS_LDC_NO] NVARCHAR(50),
[HAS_MSME] NVARCHAR(1) DEFAULT 'N',
[MSME_NO] NVARCHAR(30),
[MSME_TYPE] NVARCHAR(20),
[MSME_BTYPE] NVARCHAR(30),
[FSSAI_NO] NVARCHAR(20),
[REMARKS] NVARCHAR(MAX),
[BANK_ACCOUNTS] NVARCHAR(MAX),
[ATTACHMENTS] NVARCHAR(MAX),
[ALL_BILL_ADDRS] NVARCHAR(MAX),
[ALL_SHIP_ADDRS] NVARCHAR(MAX),
[USER_ID] NVARCHAR(50),
[USER_TYPE] NVARCHAR(50),
[USER_DEPT] NVARCHAR(100),
[SUBMITTED_AT] DATETIME2 DEFAULT GETDATE(),
[MGR_CARD_CODE_PREFIX] NVARCHAR(20) DEFAULT 'VENDA',
[MGR_GROUP_CODE] INT,
[MGR_GROUP] NVARCHAR(100),
[MGR_PAY_TERMS_CODE] INT,
[MGR_PAY_TERMS] NVARCHAR(100),
[MGR_PURCHASE_ACCOUNT] NVARCHAR(20) DEFAULT '2101001',
[MGR_PURCHASE_ACCT_NAME] NVARCHAR(100),
[MGR_CURRENCY] NVARCHAR(50) DEFAULT 'Indian Rupee',
[MGR_LANGUAGE] NVARCHAR(50) DEFAULT 'English (UK)',
[MGR_CREDIT_LIMIT] DECIMAL(18,2) DEFAULT 0,
[MGR_NOTES] NVARCHAR(MAX),
[MGR_BRANCH] NVARCHAR(100),
[MGR_AREA] NVARCHAR(100),
[MGR_TERRITORY] NVARCHAR(100),
[MGR_SALES_PERSON_CODE] INT,
[MGR_SALES_EMPLOYEE] NVARCHAR(100),
[MGR_MAIN_GROUP] NVARCHAR(50),
[MGR_CHAIN] NVARCHAR(50),
[VERIFIED_BY] NVARCHAR(60),
[VERIFIED_AT] DATETIME2,
[APPROVED_BY] NVARCHAR(60),
[APPROVED_AT] DATETIME2,
[REJECTED_BY] NVARCHAR(60),
[REJECTED_AT] DATETIME2,
[SAP_CARD_CODE] NVARCHAR(30),
[SAP_ATTACHMENT_ENTRY] INT,
[COMPANY] NVARCHAR(50) DEFAULT '${DEFAULT_SCHEMA}'
)
`).catch(e => {
if (isAlreadyExists(e)) {
tableExisted = true;
console.log('[VENDOR STORE] Table already exists — checking for missing columns...');
} else {
throw e;
}
});
// ── Safe migrations — only add columns that don't exist yet ──────────────
if (tableExisted) {
const migrations = [
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='MGR_SALES_PERSON_CODE') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [MGR_SALES_PERSON_CODE] INT`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='MGR_SALES_EMPLOYEE') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [MGR_SALES_EMPLOYEE] NVARCHAR(100)`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='MGR_MAIN_GROUP') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [MGR_MAIN_GROUP] NVARCHAR(50)`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='MGR_CHAIN') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [MGR_CHAIN] NVARCHAR(50)`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='COMPANY') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [COMPANY] NVARCHAR(50) DEFAULT '${DEFAULT_SCHEMA}'`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='MSME_BTYPE') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [MSME_BTYPE] NVARCHAR(30)`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='FSSAI_NO') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [FSSAI_NO] NVARCHAR(20)`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='ALL_BILL_ADDRS') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [ALL_BILL_ADDRS] NVARCHAR(MAX)`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='ALL_SHIP_ADDRS') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [ALL_SHIP_ADDRS] NVARCHAR(MAX)`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='VERIFIED_BY') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [VERIFIED_BY] NVARCHAR(60)`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='VERIFIED_AT') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [VERIFIED_AT] DATETIME2`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='APPROVED_BY') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [APPROVED_BY] NVARCHAR(60)`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='APPROVED_AT') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [APPROVED_AT] DATETIME2`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='REJECTED_BY') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [REJECTED_BY] NVARCHAR(60)`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='REJECTED_AT') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [REJECTED_AT] DATETIME2`,
`IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='ZVENDOR_PORTAL' AND COLUMN_NAME='SAP_ATTACHMENT_ENTRY') ALTER TABLE [dbo].[ZVENDOR_PORTAL] ADD [SAP_ATTACHMENT_ENTRY] INT`,
];
for (const migration of migrations) {
await exec(migration).catch(e => {
console.warn('[VENDOR STORE] Migration warning:', e.message);
});
}
console.log('[VENDOR STORE] ✅ Column migration check complete');
}
console.log('[VENDOR STORE] ✅ Table ready:', tbl(schema));
}
// ── Row → JS object ───────────────────────────────────────────────────────────
function rowToVendor(row) {
if (!row) return null;
let bankAccounts = [];
let attachments = {};
try { bankAccounts = JSON.parse(row.BANK_ACCOUNTS || '[]'); } catch (_) {}
try { attachments = JSON.parse(row.ATTACHMENTS || '{}'); } catch (_) {}
return {
id: row.ID,
status: row.STATUS,
vendorType: row.VENDOR_TYPE || 'SUPPLIER',
cardName: row.CARD_NAME || '',
foreignName: row.FOREIGN_NAME || '',
typeOfBusiness: row.TYPE_OF_BUSINESS || '',
industry: row.INDUSTRY || '',
products: row.PRODUCTS || '',
paymentTerms: row.PAYMENT_TERMS || '30 Days',
contactFirst: row.CONTACT_FIRST || '',
contactLast: row.CONTACT_LAST || '',
contactTitle: row.CONTACT_TITLE || '',
mobile: row.MOBILE || '',
altContact: row.ALT_CONTACT || '',
email: row.EMAIL || '',
billStreet: row.BILL_STREET || '',
billBlock: row.BILL_BLOCK || '',
billCity: row.BILL_CITY || '',
billZip: row.BILL_ZIP || '',
billState: row.BILL_STATE || '',
billCountry: row.BILL_COUNTRY || 'India',
gstin: row.GSTIN || '',
pan: row.PAN || '',
tan: row.TAN || '',
currency: row.CURRENCY || 'Indian Rupee',
hasTds: row.HAS_TDS === 'Y',
tdsCategory: row.TDS_CATEGORY || '',
tdsRate: Number(row.TDS_RATE) || 0,
tdsLdcNo: row.TDS_LDC_NO || '',
hasMsme: row.HAS_MSME === 'Y',
msmeNo: row.MSME_NO || '',
msmeType: row.MSME_TYPE || '',
msmeBType: row.MSME_BTYPE || '',
fssaiNo: row.FSSAI_NO || '',
remarks: row.REMARKS || '',
bankAccounts,
attachments,
allBillAddresses: safeJson(row.ALL_BILL_ADDRS, []),
allShipAddresses: safeJson(row.ALL_SHIP_ADDRS, []),
userId: row.USER_ID || '',
userType: row.USER_TYPE || '',
userDept: row.USER_DEPT || '',
submittedAt: row.SUBMITTED_AT ? new Date(row.SUBMITTED_AT).toISOString() : null,
mgrCardCodePrefix: row.MGR_CARD_CODE_PREFIX || 'VENDA',
mgrGroupCode: row.MGR_GROUP_CODE,
mgrGroup: row.MGR_GROUP || '',
mgrPayTermsCode: row.MGR_PAY_TERMS_CODE,
mgrPayTerms: row.MGR_PAY_TERMS || '',
mgrPurchaseAccount: row.MGR_PURCHASE_ACCOUNT || '2101001',
mgrPurchaseAcctName: row.MGR_PURCHASE_ACCT_NAME|| '',
mgrCurrency: row.MGR_CURRENCY || 'Indian Rupee',
mgrLanguage: row.MGR_LANGUAGE || 'English (UK)',
mgrCreditLimit: Number(row.MGR_CREDIT_LIMIT) || 0,
mgrNotes: row.MGR_NOTES || '',
mgrBranch: row.MGR_BRANCH || '',
mgrArea: row.MGR_AREA || '',
mgrTerritory: row.MGR_TERRITORY || '',
mgrSalesPersonCode: row.MGR_SALES_PERSON_CODE || null,
mgrSalesEmployee: row.MGR_SALES_EMPLOYEE || '',
mgrMainGroup: row.MGR_MAIN_GROUP || '',
mgrChain: row.MGR_CHAIN || '',
verifiedBy: row.VERIFIED_BY || null,
verifiedAt: row.VERIFIED_AT ? new Date(row.VERIFIED_AT).toISOString() : null,
approvedBy: row.APPROVED_BY || null,
approvedAt: row.APPROVED_AT ? new Date(row.APPROVED_AT).toISOString() : null,
rejectedBy: row.REJECTED_BY || null,
rejectedAt: row.REJECTED_AT ? new Date(row.REJECTED_AT).toISOString() : null,
sapCardCode: row.SAP_CARD_CODE || null,
sapAttachmentEntry: row.SAP_ATTACHMENT_ENTRY || null,
company: row.COMPANY || '',
};
}
function safeJson(v, fb) {
if (!v) return fb;
try { return JSON.parse(v); } catch (_) { return fb; }
}
// ── Helpers ───────────────────────────────────────────────────────────────────
function toTs(isoStr) {
if (!isoStr) return null;
return isoStr.replace('T', ' ').replace('Z', '').substring(0, 23);
}
function s(v, max) {
if (v === null || v === undefined) return null;
const str = String(v);
return max ? str.substring(0, max) : str;
}
// ── INSERT ────────────────────────────────────────────────────────────────────
async function insertVendor(data, _schema) {
const now = toTs(new Date().toISOString());
// INSERT and SELECT SCOPE_IDENTITY() must be one batch — as two separate
// exec() calls, a pooled connection can route the second one to a
// DIFFERENT physical connection than the one that just inserted, where
// SCOPE_IDENTITY() correctly returns NULL (see workOrderStore.js's
// insertWorkOrder() for the full write-up of this bug class).
const idRows = await exec(`
INSERT INTO ${tbl()} (
[VENDOR_TYPE],[CARD_NAME],[FOREIGN_NAME],[TYPE_OF_BUSINESS],[INDUSTRY],
[PRODUCTS],[PAYMENT_TERMS],
[CONTACT_FIRST],[CONTACT_LAST],[CONTACT_TITLE],
[MOBILE],[ALT_CONTACT],[EMAIL],
[BILL_STREET],[BILL_BLOCK],[BILL_CITY],[BILL_ZIP],[BILL_STATE],[BILL_COUNTRY],
[GSTIN],[PAN],[TAN],[CURRENCY],
[HAS_TDS],[TDS_CATEGORY],[TDS_RATE],[TDS_LDC_NO],
[HAS_MSME],[MSME_NO],[MSME_TYPE],[MSME_BTYPE],[FSSAI_NO],
[REMARKS],[BANK_ACCOUNTS],[ATTACHMENTS],
[ALL_BILL_ADDRS],[ALL_SHIP_ADDRS],
[USER_ID],[USER_TYPE],[USER_DEPT],
[SUBMITTED_AT],
[MGR_CARD_CODE_PREFIX],[MGR_PURCHASE_ACCOUNT],[MGR_CURRENCY],[MGR_LANGUAGE],
[COMPANY]
) VALUES (
?,?,?,?,?,
?,?,
?,?,?,
?,?,?,
?,?,?,?,?,?,
?,?,?,?,
?,?,?,?,
?,?,?,?,?,
?,?,?,
?,?,
?,?,?,
?,
?,?,?,?,
?
);
SELECT SCOPE_IDENTITY() AS ID;
`, [
s(data.vendorType || 'SUPPLIER', 20),
s(data.cardName, 100),
s(data.foreignName || '', 100),
s(data.typeOfBusiness || 'Company', 50),
s(data.industry || '', 100),
s(data.products || '', 500),
s(data.paymentTerms || '30 Days', 50),
s(data.contactFirst, 60),
s(data.contactLast, 60),
s(data.contactTitle || '', 60),
s(data.mobile || '', 20),
s(data.altContact || '', 20),
s(data.email, 150),
s(data.billStreet || '', 200),
s(data.billBlock || '', 100),
s(data.billCity || '', 100),
s(data.billZip || '', 20),
s(data.billState || '', 100),
s(data.billCountry || 'India', 60),
s(data.gstin || '', 15),
s(data.pan || '', 10),
s(data.tan || '', 10),
s(data.currency || 'Indian Rupee', 50),
data.hasTds ? 'Y' : 'N',
s(data.tdsCategory || '', 100),
parseFloat(data.tdsRate) || 0,
s(data.tdsLdcNo || '', 50),
data.hasMsme ? 'Y' : 'N',
s(data.msmeNo || '', 30),
s(data.msmeType || '', 20),
s(data.msmeBType || '', 30),
s(data.fssaiNo || '', 20),
data.remarks || '',
JSON.stringify(data.bankAccounts || []),
JSON.stringify(data.attachments || {}),
JSON.stringify(data.allBillAddresses || []),
JSON.stringify(data.allShipAddresses || []),
s(data.userId || '', 50),
s(data.userType || '', 50),
s(data.userDept || '', 100),
now,
s(data.mgrCardCodePrefix || 'VENDA', 20),
s(data.mgrPurchaseAccount || '2101001', 20),
s(data.mgrCurrency || 'Indian Rupee', 50),
s(data.mgrLanguage || 'English (UK)', 50),
s(data.company || '', 50),
]);
const id = idRows[0].ID;
console.log(`[VENDOR STORE] ✅ Inserted ID=${id} — ${data.cardName}`);
return { id };
}
// ── FIND BY ID ────────────────────────────────────────────────────────────────
async function findById(id, companyDB) {
const rows = await exec(
`SELECT * FROM ${tbl()} WHERE [ID] = ?`,
[parseInt(id)]
);
return rowToVendor(rows[0]);
}
// ── LIST BY STATUS ────────────────────────────────────────────────────────────
async function listByStatus(status, companyDB) {
let rows;
if (status === 'ALL') {
rows = await exec(`SELECT * FROM ${tbl()} ORDER BY [SUBMITTED_AT] DESC`);
} else {
rows = await exec(
`SELECT * FROM ${tbl()} WHERE [STATUS] = ? ORDER BY [SUBMITTED_AT] DESC`,
[status.toUpperCase()]
);
}
let result = rows.map(rowToVendor);
if (companyDB) result = result.filter(r => r.company === companyDB);
return result;
}
// ── UPDATE ────────────────────────────────────────────────────────────────────
async function updateVendor(id, patch, _schema) {
if (!patch || Object.keys(patch).length === 0) return;
const COL_MAP = {
status: 'STATUS',
vendorType: 'VENDOR_TYPE',
cardName: 'CARD_NAME',
foreignName: 'FOREIGN_NAME',
typeOfBusiness: 'TYPE_OF_BUSINESS',
industry: 'INDUSTRY',
products: 'PRODUCTS',
paymentTerms: 'PAYMENT_TERMS',
contactFirst: 'CONTACT_FIRST',
contactLast: 'CONTACT_LAST',
contactTitle: 'CONTACT_TITLE',
mobile: 'MOBILE',
altContact: 'ALT_CONTACT',
email: 'EMAIL',
billStreet: 'BILL_STREET',
billBlock: 'BILL_BLOCK',
billCity: 'BILL_CITY',
billZip: 'BILL_ZIP',
billState: 'BILL_STATE',
billCountry: 'BILL_COUNTRY',
gstin: 'GSTIN',
pan: 'PAN',
tan: 'TAN',
currency: 'CURRENCY',
hasTds: 'HAS_TDS',
tdsCategory: 'TDS_CATEGORY',
tdsRate: 'TDS_RATE',
tdsLdcNo: 'TDS_LDC_NO',
hasMsme: 'HAS_MSME',
msmeNo: 'MSME_NO',
msmeType: 'MSME_TYPE',
msmeBType: 'MSME_BTYPE',
fssaiNo: 'FSSAI_NO',
remarks: 'REMARKS',
bankAccounts: 'BANK_ACCOUNTS',
attachments: 'ATTACHMENTS',
allBillAddresses: 'ALL_BILL_ADDRS',
allShipAddresses: 'ALL_SHIP_ADDRS',
verifiedBy: 'VERIFIED_BY',
verifiedAt: 'VERIFIED_AT',
approvedBy: 'APPROVED_BY',
approvedAt: 'APPROVED_AT',
rejectedBy: 'REJECTED_BY',
rejectedAt: 'REJECTED_AT',
sapCardCode: 'SAP_CARD_CODE',
sapAttachmentEntry: 'SAP_ATTACHMENT_ENTRY',
mgrCardCodePrefix: 'MGR_CARD_CODE_PREFIX',
mgrGroupCode: 'MGR_GROUP_CODE',
mgrGroup: 'MGR_GROUP',
mgrPayTermsCode: 'MGR_PAY_TERMS_CODE',
mgrPayTerms: 'MGR_PAY_TERMS',
mgrPurchaseAccount: 'MGR_PURCHASE_ACCOUNT',
mgrPurchaseAcctName: 'MGR_PURCHASE_ACCT_NAME',
mgrCurrency: 'MGR_CURRENCY',
mgrLanguage: 'MGR_LANGUAGE',
mgrCreditLimit: 'MGR_CREDIT_LIMIT',
mgrNotes: 'MGR_NOTES',
mgrBranch: 'MGR_BRANCH',
mgrArea: 'MGR_AREA',
mgrTerritory: 'MGR_TERRITORY',
mgrSalesPersonCode: 'MGR_SALES_PERSON_CODE',
mgrSalesEmployee: 'MGR_SALES_EMPLOYEE',
mgrMainGroup: 'MGR_MAIN_GROUP',
mgrChain: 'MGR_CHAIN',
company: 'COMPANY',
};
const BOOL_COLS = new Set(['HAS_TDS', 'HAS_MSME']);
const JSON_COLS = new Set(['BANK_ACCOUNTS', 'ATTACHMENTS', 'ALL_BILL_ADDRS', 'ALL_SHIP_ADDRS']);
const NUMBER_COLS = new Set(['MGR_GROUP_CODE', 'MGR_PAY_TERMS_CODE', 'MGR_CREDIT_LIMIT', 'TDS_RATE', 'SAP_ATTACHMENT_ENTRY', 'MGR_SALES_PERSON_CODE']);
const TS_COLS = new Set(['VERIFIED_AT', 'APPROVED_AT', 'REJECTED_AT']);
const setClauses = [];
const vals = [];
Object.entries(patch).forEach(([k, v]) => {
const col = COL_MAP[k];
if (!col) return;
setClauses.push(`[${col}] = ?`);
if (BOOL_COLS.has(col)) {
vals.push(v ? 'Y' : 'N');
} else if (JSON_COLS.has(col)) {
vals.push(JSON.stringify(v || (col === 'ATTACHMENTS' ? {} : [])));
} else if (NUMBER_COLS.has(col)) {
vals.push(v === null || v === undefined ? null : Number(v));
} else if (TS_COLS.has(col)) {
vals.push(v ? toTs(v) : null);
} else {
vals.push(v === null || v === undefined ? null : String(v));
}
});
if (!setClauses.length) return;
vals.push(parseInt(id));
await exec(
`UPDATE ${tbl()} SET ${setClauses.join(', ')} WHERE [ID] = ?`,
vals
);
console.log(`[VENDOR STORE] ✅ Updated ID=${id} (${setClauses.length} fields)`);
}
module.exports = { bootstrap, insertVendor, findById, listByStatus, updateVendor };