feat: add SQL Server support and refactor database layer for multi-database compatibility

- Add SqlServerService integration alongside existing MySQL support
- Refactor DAOs (DiscreteMaterialPlanDAO, MaterialsToBeDeletedDAO, BipUsersDAO) to support both MySQL and SQL Server
- Update validation handler to dynamically select database service based on DB_TYPE environment variable
- Add connection pooling and proper connection management for SQL Server
- Update package-lock.json dependency peer flags

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-03-02 11:10:59 +08:00
parent bbdd1e18f8
commit 05d856309f
6 changed files with 1207 additions and 473 deletions

33
package-lock.json generated
View File

@@ -99,6 +99,7 @@
"resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz",
"integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.1.2", "@azure/abort-controller": "^2.1.2",
"@azure/core-auth": "^1.10.0", "@azure/core-auth": "^1.10.0",
@@ -160,6 +161,7 @@
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz", "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz",
"integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==", "integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.1.2", "@azure/abort-controller": "^2.1.2",
"@azure/core-auth": "^1.10.0", "@azure/core-auth": "^1.10.0",
@@ -351,6 +353,7 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.29.0", "@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0", "@babel/generator": "^7.29.0",
@@ -1099,7 +1102,6 @@
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"cross-dirname": "^0.1.0", "cross-dirname": "^0.1.0",
"debug": "^4.3.4", "debug": "^4.3.4",
@@ -1121,7 +1123,6 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1", "jsonfile": "^6.0.1",
@@ -1138,7 +1139,6 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"universalify": "^2.0.0" "universalify": "^2.0.0"
}, },
@@ -1153,7 +1153,6 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"engines": { "engines": {
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
@@ -3008,6 +3007,7 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.13.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.13.tgz",
"integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==", "integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
@@ -3030,6 +3030,7 @@
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
} }
@@ -3132,6 +3133,7 @@
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.56.1", "@typescript-eslint/types": "8.56.1",
@@ -3565,6 +3567,7 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -3597,6 +3600,7 @@
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@@ -4329,6 +4333,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@@ -4984,8 +4989,7 @@
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true
"peer": true
}, },
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
@@ -5334,6 +5338,7 @@
"integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"app-builder-lib": "26.8.1", "app-builder-lib": "26.8.1",
"builder-util": "26.8.1", "builder-util": "26.8.1",
@@ -5562,6 +5567,7 @@
"integrity": "sha512-Rz5QvP1pTqoU1DPRrG3EeX2oWBtS3uRmd6Z/wzZsb2e/iIUsrT+XcBaAhFr4FW48gDc8uP2wYVyY5Aamha/5Zg==", "integrity": "sha512-Rz5QvP1pTqoU1DPRrG3EeX2oWBtS3uRmd6Z/wzZsb2e/iIUsrT+XcBaAhFr4FW48gDc8uP2wYVyY5Aamha/5Zg==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@electron/get": "^2.0.0", "@electron/get": "^2.0.0",
"@types/node": "^22.7.7", "@types/node": "^22.7.7",
@@ -5750,7 +5756,6 @@
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@electron/asar": "^3.2.1", "@electron/asar": "^3.2.1",
"debug": "^4.1.1", "debug": "^4.1.1",
@@ -5771,7 +5776,6 @@
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"graceful-fs": "^4.1.2", "graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0", "jsonfile": "^4.0.0",
@@ -6099,6 +6103,7 @@
"integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -6159,6 +6164,7 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"eslint-config-prettier": "bin/cli.js" "eslint-config-prettier": "bin/cli.js"
}, },
@@ -9870,6 +9876,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -9951,6 +9958,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -9974,7 +9982,6 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"commander": "^9.4.0" "commander": "^9.4.0"
}, },
@@ -9992,7 +9999,6 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"engines": { "engines": {
"node": "^12.20.0 || >=14" "node": "^12.20.0 || >=14"
} }
@@ -10013,6 +10019,7 @@
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },
@@ -10145,6 +10152,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -11380,7 +11388,6 @@
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"rimraf": "~2.6.2" "rimraf": "~2.6.2"
@@ -11668,6 +11675,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -11902,6 +11910,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.27.0", "esbuild": "^0.27.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",
@@ -12448,6 +12457,7 @@
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@vitest/expect": "4.0.18", "@vitest/expect": "4.0.18",
"@vitest/mocker": "4.0.18", "@vitest/mocker": "4.0.18",
@@ -12845,6 +12855,7 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }

View File

@@ -9,6 +9,7 @@
import { ipcMain } from 'electron' import { ipcMain } from 'electron'
import { MySqlService } from '../services/database/mysql' import { MySqlService } from '../services/database/mysql'
import { SqlServerService } from '../services/database/sql-server'
import { MaterialsToBeDeletedDAO } from '../services/database/materials-to-be-deleted-dao' import { MaterialsToBeDeletedDAO } from '../services/database/materials-to-be-deleted-dao'
import { DiscreteMaterialPlanDAO } from '../services/database/discrete-material-plan-dao' import { DiscreteMaterialPlanDAO } from '../services/database/discrete-material-plan-dao'
import type { import type {
@@ -49,19 +50,53 @@ export function clearSharedProductionIds(): void {
} }
/** /**
* Get MySQL service for validation operations * Get database service for validation operations (MySQL or SQL Server)
*/ */
async function getValidationMySqlService(): Promise<MySqlService> { async function getValidationDatabaseService(): Promise<MySqlService | SqlServerService> {
const mysqlService = new MySqlService({ const dbType = process.env.DB_TYPE?.toLowerCase()
host: process.env.DB_MYSQL_HOST || 'localhost',
port: parseInt(process.env.DB_MYSQL_PORT || '3306', 10),
user: process.env.DB_USERNAME || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || ''
})
await mysqlService.connect() if (dbType === 'sqlserver' || dbType === 'mssql') {
return mysqlService const sqlServerService = new SqlServerService({
server: process.env.DB_SERVER || 'localhost',
port: parseInt(process.env.DB_SQLSERVER_PORT || '1433', 10),
user: process.env.DB_USERNAME || 'sa',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || '',
options: {
encrypt: process.env.DB_TRUST_SERVER_CERTIFICATE === 'yes',
trustServerCertificate: process.env.DB_TRUST_SERVER_CERTIFICATE === 'yes'
}
})
await sqlServerService.connect()
return sqlServerService
} else {
const mysqlService = new MySqlService({
host: process.env.DB_MYSQL_HOST || 'localhost',
port: parseInt(process.env.DB_MYSQL_PORT || '3306', 10),
user: process.env.DB_USERNAME || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || ''
})
await mysqlService.connect()
return mysqlService
}
}
/**
* Get table name based on database type
*/
function getTableName(mysqlTableName: string): string {
const dbType = process.env.DB_TYPE?.toLowerCase()
if (dbType === 'sqlserver' || dbType === 'mssql') {
// Convert MySQL table name to SQL Server format
// e.g., dbo_MaterialsToBeDeleted -> [dbo].[MaterialsToBeDeleted]
if (mysqlTableName.startsWith('dbo_')) {
const tableName = mysqlTableName.substring(4)
return `[dbo].[${tableName}]`
}
return `[dbo].[${mysqlTableName}]`
}
return mysqlTableName
} }
/** /**
@@ -96,10 +131,12 @@ function identifyInputType(input: string): 'production_id' | 'order_number' | 'u
*/ */
async function getSourceNumbersFromInputs( async function getSourceNumbersFromInputs(
inputs: string[], inputs: string[],
mysqlService: MySqlService dbService: MySqlService | SqlServerService
): Promise<string[]> { ): Promise<string[]> {
const productionIds: string[] = [] const productionIds: string[] = []
const orderNumbers: string[] = [] const orderNumbers: string[] = []
const dbType = process.env.DB_TYPE?.toLowerCase()
const isSqlServer = dbType === 'sqlserver' || dbType === 'mssql'
for (const item of inputs) { for (const item of inputs) {
const type = identifyInputType(item) const type = identifyInputType(item)
@@ -114,17 +151,38 @@ async function getSourceNumbersFromInputs(
// Table name in MySQL: productionContractData_26年压力表合同数据 // Table name in MySQL: productionContractData_26年压力表合同数据
// Column name: 生产订单号 (SourceNumber) // Column name: 生产订单号 (SourceNumber)
if (productionIds.length > 0) { if (productionIds.length > 0) {
const placeholders = productionIds.map(() => '?').join(',') if (isSqlServer) {
const contractSql = ` const sql = require('mssql')
SELECT DISTINCT 生产订单号 const placeholders = productionIds.map((_, idx) => `@p${idx}`).join(',')
FROM productionContractData_26年压力表合同数据 const params: Record<string, { value: string; type: any }> = {}
WHERE 总排号 IN (${placeholders})
` productionIds.forEach((id, idx) => {
const contractResult = await mysqlService.query(contractSql, productionIds) params[`p${idx}`] = { value: id, type: sql.NVarChar }
const dbOrderNumbers = contractResult.rows.map( })
row => row. as string
) const contractSql = `
orderNumbers.push(...dbOrderNumbers) SELECT DISTINCT 生产订单号
FROM productionContractData_26年压力表合同数据
WHERE 总排号 IN (${placeholders})
`
const contractResult = await (dbService as SqlServerService).queryWithParams(contractSql, params)
const dbOrderNumbers = contractResult.rows.map(
row => row. as string
)
orderNumbers.push(...dbOrderNumbers)
} else {
const placeholders = productionIds.map(() => '?').join(',')
const contractSql = `
SELECT DISTINCT 生产订单号
FROM productionContractData_26年压力表合同数据
WHERE 总排号 IN (${placeholders})
`
const contractResult = await (dbService as MySqlService).query(contractSql, productionIds)
const dbOrderNumbers = contractResult.rows.map(
row => row. as string
)
orderNumbers.push(...dbOrderNumbers)
}
} }
// Deduplicate // Deduplicate
@@ -146,13 +204,15 @@ export function registerValidationHandlers(): void {
_event, _event,
request: ValidationRequest request: ValidationRequest
): Promise<ValidationResponse> => { ): Promise<ValidationResponse> => {
let mysqlService: MySqlService | null = null let dbService: MySqlService | SqlServerService | null = null
try { try {
console.log('[Validation] Starting validation:', request) console.log('[Validation] Starting validation:', request)
// Connect to MySQL // Connect to database
mysqlService = await getValidationMySqlService() dbService = await getValidationDatabaseService()
const dbType = process.env.DB_TYPE?.toLowerCase()
const isSqlServer = dbType === 'sqlserver' || dbType === 'mssql'
let sourceNumbers: string[] | null = null let sourceNumbers: string[] | null = null
@@ -175,7 +235,7 @@ export function registerValidationHandlers(): void {
} }
} }
sourceNumbers = await getSourceNumbersFromInputs(sharedIds, mysqlService) sourceNumbers = await getSourceNumbersFromInputs(sharedIds, dbService)
console.log( console.log(
`[Validation] Got ${sourceNumbers.length} source numbers from shared Production IDs` `[Validation] Got ${sourceNumbers.length} source numbers from shared Production IDs`
) )
@@ -185,7 +245,7 @@ export function registerValidationHandlers(): void {
console.log( console.log(
`[Validation] Read ${inputs.length} inputs from file` `[Validation] Read ${inputs.length} inputs from file`
) )
sourceNumbers = await getSourceNumbersFromInputs(inputs, mysqlService) sourceNumbers = await getSourceNumbersFromInputs(inputs, dbService)
console.log( console.log(
`[Validation] Got ${sourceNumbers.length} source numbers` `[Validation] Got ${sourceNumbers.length} source numbers`
) )
@@ -194,8 +254,6 @@ export function registerValidationHandlers(): void {
// Get material records from DiscreteMaterialPlanData // Get material records from DiscreteMaterialPlanData
const materialDao = new DiscreteMaterialPlanDAO() const materialDao = new DiscreteMaterialPlanDAO()
// Inject the mysqlService instance
;(materialDao as any).mysqlService = mysqlService
let materialRecords: any[] = [] let materialRecords: any[] = []
@@ -222,24 +280,32 @@ export function registerValidationHandlers(): void {
} }
// Get type keywords from MaterialsTypeToBeDeleted // Get type keywords from MaterialsTypeToBeDeleted
const typeKeywordTableName = getTableName('dbo_MaterialsTypeToBeDeleted')
const typeKeywordSql = ` const typeKeywordSql = `
SELECT MaterialName, ManagerName SELECT MaterialName, ManagerName
FROM dbo_MaterialsTypeToBeDeleted FROM ${typeKeywordTableName}
WHERE MaterialName IS NOT NULL WHERE MaterialName IS NOT NULL
` `
const typeKeywordResult = await mysqlService.query(typeKeywordSql) const typeKeywordResult = isSqlServer
? await (dbService as SqlServerService).query(typeKeywordSql)
: await (dbService as MySqlService).query(typeKeywordSql)
const typeKeywords = typeKeywordResult.rows.map(row => ({ const typeKeywords = typeKeywordResult.rows.map(row => ({
materialName: row.MaterialName as string, materialName: row.MaterialName as string,
managerName: row.ManagerName as string managerName: row.ManagerName as string
})) }))
// Get marked material codes from MaterialsToBeDeleted // Get marked material codes from MaterialsToBeDeleted
const markedTableName = getTableName('dbo_MaterialsToBeDeleted')
const markedSql = ` const markedSql = `
SELECT MaterialCode, ManagerName SELECT MaterialCode, ManagerName
FROM dbo_MaterialsToBeDeleted FROM ${markedTableName}
WHERE MaterialCode IS NOT NULL AND ManagerName IS NOT NULL WHERE MaterialCode IS NOT NULL AND ManagerName IS NOT NULL
` `
const markedResult = await mysqlService.query(markedSql) const markedResult = isSqlServer
? await (dbService as SqlServerService).query(markedSql)
: await (dbService as MySqlService).query(markedSql)
const markedCodesDict = new Map<string, string>() const markedCodesDict = new Map<string, string>()
for (const row of markedResult.rows) { for (const row of markedResult.rows) {
markedCodesDict.set( markedCodesDict.set(
@@ -306,11 +372,11 @@ export function registerValidationHandlers(): void {
error: `Validation failed: ${message}` error: `Validation failed: ${message}`
} }
} finally { } finally {
if (mysqlService) { if (dbService) {
try { try {
await mysqlService.disconnect() await dbService.disconnect()
} catch (closeError) { } catch (closeError) {
console.warn('[Validation] Error disconnecting MySQL:', closeError) console.warn('[Validation] Error disconnecting database:', closeError)
} }
} }
} }
@@ -328,13 +394,8 @@ export function registerValidationHandlers(): void {
_event, _event,
request: MaterialUpsertBatchRequest request: MaterialUpsertBatchRequest
): Promise<MaterialOperationResponse> => { ): Promise<MaterialOperationResponse> => {
let mysqlService: MySqlService | null = null
try { try {
mysqlService = await getValidationMySqlService()
const dao = new MaterialsToBeDeletedDAO() const dao = new MaterialsToBeDeletedDAO()
;(dao as any).mysqlService = mysqlService
const stats = await dao.upsertBatch(request.materials) const stats = await dao.upsertBatch(request.materials)
return { return {
@@ -348,14 +409,6 @@ export function registerValidationHandlers(): void {
success: false, success: false,
error: `Upsert failed: ${message}` error: `Upsert failed: ${message}`
} }
} finally {
if (mysqlService) {
try {
await mysqlService.disconnect()
} catch (closeError) {
console.warn('[Materials] Error disconnecting MySQL:', closeError)
}
}
} }
} }
) )
@@ -369,13 +422,8 @@ export function registerValidationHandlers(): void {
_event, _event,
request: MaterialDeleteRequest request: MaterialDeleteRequest
): Promise<MaterialOperationResponse> => { ): Promise<MaterialOperationResponse> => {
let mysqlService: MySqlService | null = null
try { try {
mysqlService = await getValidationMySqlService()
const dao = new MaterialsToBeDeletedDAO() const dao = new MaterialsToBeDeletedDAO()
;(dao as any).mysqlService = mysqlService
const count = await dao.deleteByMaterialCodes(request.materialCodes) const count = await dao.deleteByMaterialCodes(request.materialCodes)
return { return {
@@ -389,14 +437,6 @@ export function registerValidationHandlers(): void {
success: false, success: false,
error: `Delete failed: ${message}` error: `Delete failed: ${message}`
} }
} finally {
if (mysqlService) {
try {
await mysqlService.disconnect()
} catch (closeError) {
console.warn('[Materials] Error disconnecting MySQL:', closeError)
}
}
} }
} }
) )
@@ -407,26 +447,13 @@ export function registerValidationHandlers(): void {
ipcMain.handle( ipcMain.handle(
'materials:getManagers', 'materials:getManagers',
async (_event): Promise<{ managers: string[] }> => { async (_event): Promise<{ managers: string[] }> => {
let mysqlService: MySqlService | null = null
try { try {
mysqlService = await getValidationMySqlService()
const dao = new MaterialsToBeDeletedDAO() const dao = new MaterialsToBeDeletedDAO()
;(dao as any).mysqlService = mysqlService
const managers = await dao.getManagers() const managers = await dao.getManagers()
return { managers } return { managers }
} catch (error) { } catch (error) {
console.error('[Materials] Get managers error:', error) console.error('[Materials] Get managers error:', error)
return { managers: [] } return { managers: [] }
} finally {
if (mysqlService) {
try {
await mysqlService.disconnect()
} catch (closeError) {
console.warn('[Materials] Error disconnecting MySQL:', closeError)
}
}
} }
} }
) )
@@ -440,13 +467,14 @@ export function registerValidationHandlers(): void {
_event, _event,
managerName: string managerName: string
): Promise<{ materials: MaterialRecordSummary[] }> => { ): Promise<{ materials: MaterialRecordSummary[] }> => {
let mysqlService: MySqlService | null = null let dbService: MySqlService | SqlServerService | null = null
try { try {
mysqlService = await getValidationMySqlService() dbService = await getValidationDatabaseService()
const dao = new MaterialsToBeDeletedDAO() const dbType = process.env.DB_TYPE?.toLowerCase()
;(dao as any).mysqlService = mysqlService const isSqlServer = dbType === 'sqlserver' || dbType === 'mssql'
const dao = new MaterialsToBeDeletedDAO()
const materials = await dao.getMaterialsByManager(managerName) const materials = await dao.getMaterialsByManager(managerName)
// Get material codes set for quick lookup // Get material codes set for quick lookup
@@ -454,17 +482,32 @@ export function registerValidationHandlers(): void {
// Enrich with material details from DiscreteMaterialPlanData // Enrich with material details from DiscreteMaterialPlanData
const enrichedMaterials: MaterialRecordSummary[] = [] const enrichedMaterials: MaterialRecordSummary[] = []
const detailTableName = getTableName('dbo_DiscreteMaterialPlanData')
for (const mat of materials) { for (const mat of materials) {
const detailSql = ` let detailResult: any
SELECT MaterialName, Specification, Model
FROM dbo_DiscreteMaterialPlanData if (isSqlServer) {
WHERE MaterialCode = ? const sql = require('mssql')
LIMIT 1 const detailSql = `
` SELECT TOP 1 MaterialName, Specification, Model
const detailResult = await mysqlService.query(detailSql, [ FROM ${detailTableName}
mat.materialCode WHERE MaterialCode = @materialCode
]) `
detailResult = await (dbService as SqlServerService).queryWithParams(detailSql, {
materialCode: { value: mat.materialCode, type: sql.NVarChar }
})
} else {
const detailSql = `
SELECT MaterialName, Specification, Model
FROM ${detailTableName}
WHERE MaterialCode = ?
LIMIT 1
`
detailResult = await (dbService as MySqlService).query(detailSql, [
mat.materialCode
])
}
enrichedMaterials.push({ enrichedMaterials.push({
materialCode: mat.materialCode, materialCode: mat.materialCode,
@@ -490,11 +533,11 @@ export function registerValidationHandlers(): void {
console.error('[Materials] Get by manager error:', error) console.error('[Materials] Get by manager error:', error)
return { materials: [] } return { materials: [] }
} finally { } finally {
if (mysqlService) { if (dbService) {
try { try {
await mysqlService.disconnect() await dbService.disconnect()
} catch (closeError) { } catch (closeError) {
console.warn('[Materials] Error disconnecting MySQL:', closeError) console.warn('[Materials] Error disconnecting database:', closeError)
} }
} }
} }
@@ -507,28 +550,44 @@ export function registerValidationHandlers(): void {
ipcMain.handle( ipcMain.handle(
'materials:getAll', 'materials:getAll',
async (_event): Promise<{ materials: MaterialRecordSummary[] }> => { async (_event): Promise<{ materials: MaterialRecordSummary[] }> => {
let mysqlService: MySqlService | null = null let dbService: MySqlService | SqlServerService | null = null
try { try {
mysqlService = await getValidationMySqlService() dbService = await getValidationDatabaseService()
const dao = new MaterialsToBeDeletedDAO() const dbType = process.env.DB_TYPE?.toLowerCase()
;(dao as any).mysqlService = mysqlService const isSqlServer = dbType === 'sqlserver' || dbType === 'mssql'
const dao = new MaterialsToBeDeletedDAO()
const materials = await dao.getAllRecords() const materials = await dao.getAllRecords()
const markedCodes = await dao.getAllMaterialCodes() const markedCodes = await dao.getAllMaterialCodes()
const enrichedMaterials: MaterialRecordSummary[] = [] const enrichedMaterials: MaterialRecordSummary[] = []
const detailTableName = getTableName('dbo_DiscreteMaterialPlanData')
for (const mat of materials) { for (const mat of materials) {
const detailSql = ` let detailResult: any
SELECT MaterialName, Specification, Model
FROM dbo_DiscreteMaterialPlanData if (isSqlServer) {
WHERE MaterialCode = ? const sql = require('mssql')
LIMIT 1 const detailSql = `
` SELECT TOP 1 MaterialName, Specification, Model
const detailResult = await mysqlService.query(detailSql, [ FROM ${detailTableName}
mat.materialCode WHERE MaterialCode = @materialCode
]) `
detailResult = await (dbService as SqlServerService).queryWithParams(detailSql, {
materialCode: { value: mat.materialCode, type: sql.NVarChar }
})
} else {
const detailSql = `
SELECT MaterialName, Specification, Model
FROM ${detailTableName}
WHERE MaterialCode = ?
LIMIT 1
`
detailResult = await (dbService as MySqlService).query(detailSql, [
mat.materialCode
])
}
enrichedMaterials.push({ enrichedMaterials.push({
materialCode: mat.materialCode, materialCode: mat.materialCode,
@@ -554,11 +613,11 @@ export function registerValidationHandlers(): void {
console.error('[Materials] Get all error:', error) console.error('[Materials] Get all error:', error)
return { materials: [] } return { materials: [] }
} finally { } finally {
if (mysqlService) { if (dbService) {
try { try {
await mysqlService.disconnect() await dbService.disconnect()
} catch (closeError) { } catch (closeError) {
console.warn('[Materials] Error disconnecting MySQL:', closeError) console.warn('[Materials] Error disconnecting database:', closeError)
} }
} }
} }
@@ -571,26 +630,13 @@ export function registerValidationHandlers(): void {
ipcMain.handle( ipcMain.handle(
'materials:getStatistics', 'materials:getStatistics',
async (_event): Promise<{ stats: any }> => { async (_event): Promise<{ stats: any }> => {
let mysqlService: MySqlService | null = null
try { try {
mysqlService = await getValidationMySqlService()
const dao = new MaterialsToBeDeletedDAO() const dao = new MaterialsToBeDeletedDAO()
;(dao as any).mysqlService = mysqlService
const stats = await dao.getStatistics() const stats = await dao.getStatistics()
return { stats } return { stats }
} catch (error) { } catch (error) {
console.error('[Materials] Get statistics error:', error) console.error('[Materials] Get statistics error:', error)
return { stats: null } return { stats: null }
} finally {
if (mysqlService) {
try {
await mysqlService.disconnect()
} catch (closeError) {
console.warn('[Materials] Error disconnecting MySQL:', closeError)
}
}
} }
} }
) )
@@ -628,7 +674,7 @@ export function registerValidationHandlers(): void {
materialCodes?: string[] materialCodes?: string[]
error?: string error?: string
}> => { }> => {
let mysqlService: MySqlService | null = null let dbService: MySqlService | SqlServerService | null = null
const sessionManager = (await import('../services/user/session-manager')).SessionManager.getInstance() const sessionManager = (await import('../services/user/session-manager')).SessionManager.getInstance()
try { try {
@@ -643,11 +689,13 @@ export function registerValidationHandlers(): void {
const isAdmin = userInfo.userType === 'Admin' const isAdmin = userInfo.userType === 'Admin'
const username = userInfo.username const username = userInfo.username
const dbType = process.env.DB_TYPE?.toLowerCase()
const isSqlServer = dbType === 'sqlserver' || dbType === 'mssql'
console.log(`[CleanerData] User: ${username}, isAdmin: ${isAdmin}`) console.log(`[CleanerData] User: ${username}, isAdmin: ${isAdmin}`)
// Connect to MySQL // Connect to database
mysqlService = await getValidationMySqlService() dbService = await getValidationDatabaseService()
// 1. Get order numbers from shared Production IDs // 1. Get order numbers from shared Production IDs
const sharedIds = getSharedProductionIds() const sharedIds = getSharedProductionIds()
@@ -655,36 +703,55 @@ export function registerValidationHandlers(): void {
if (sharedIds.length > 0) { if (sharedIds.length > 0) {
console.log(`[CleanerData] Using ${sharedIds.length} shared Production IDs`) console.log(`[CleanerData] Using ${sharedIds.length} shared Production IDs`)
orderNumbers = await getSourceNumbersFromInputs(sharedIds, mysqlService) orderNumbers = await getSourceNumbersFromInputs(sharedIds, dbService)
console.log(`[CleanerData] Got ${orderNumbers.length} order numbers`) console.log(`[CleanerData] Got ${orderNumbers.length} order numbers`)
} }
// 2. Get material codes from MaterialsToBeDeleted table // 2. Get material codes from MaterialsToBeDeleted table
let materialCodes: string[] = [] let materialCodes: string[] = []
const markedTableName = getTableName('dbo_MaterialsToBeDeleted')
if (isAdmin) { if (isAdmin) {
// Admin sees all materials // Admin sees all materials
const allCodesSql = ` const allCodesSql = `
SELECT MaterialCode SELECT MaterialCode
FROM dbo_MaterialsToBeDeleted FROM ${markedTableName}
WHERE MaterialCode IS NOT NULL WHERE MaterialCode IS NOT NULL
` `
const result = await mysqlService.query(allCodesSql) const result = isSqlServer
? await (dbService as SqlServerService).query(allCodesSql)
: await (dbService as MySqlService).query(allCodesSql)
materialCodes = result.rows materialCodes = result.rows
.map(row => row.MaterialCode as string) .map(row => row.MaterialCode as string)
.filter(Boolean) .filter(Boolean)
console.log(`[CleanerData] Admin user: got ${materialCodes.length} materials`) console.log(`[CleanerData] Admin user: got ${materialCodes.length} materials`)
} else { } else {
// Regular users only see their own materials // Regular users only see their own materials
const userMaterialsSql = ` if (isSqlServer) {
SELECT MaterialCode const sql = require('mssql')
FROM dbo_MaterialsToBeDeleted const userMaterialsSql = `
WHERE ManagerName = ? AND MaterialCode IS NOT NULL SELECT MaterialCode
` FROM ${markedTableName}
const result = await mysqlService.query(userMaterialsSql, [username]) WHERE ManagerName = @username AND MaterialCode IS NOT NULL
materialCodes = result.rows `
.map(row => row.MaterialCode as string) const result = await (dbService as SqlServerService).queryWithParams(userMaterialsSql, {
.filter(Boolean) username: { value: username, type: sql.NVarChar }
})
materialCodes = result.rows
.map(row => row.MaterialCode as string)
.filter(Boolean)
} else {
const userMaterialsSql = `
SELECT MaterialCode
FROM ${markedTableName}
WHERE ManagerName = ? AND MaterialCode IS NOT NULL
`
const result = await (dbService as MySqlService).query(userMaterialsSql, [username])
materialCodes = result.rows
.map(row => row.MaterialCode as string)
.filter(Boolean)
}
console.log(`[CleanerData] Regular user: got ${materialCodes.length} materials`) console.log(`[CleanerData] Regular user: got ${materialCodes.length} materials`)
} }
@@ -701,11 +768,11 @@ export function registerValidationHandlers(): void {
error: `获取清理数据失败:${message}` error: `获取清理数据失败:${message}`
} }
} finally { } finally {
if (mysqlService) { if (dbService) {
try { try {
await mysqlService.disconnect() await dbService.disconnect()
} catch (closeError) { } catch (closeError) {
console.warn('[CleanerData] Error disconnecting MySQL:', closeError) console.warn('[CleanerData] Error disconnecting database:', closeError)
} }
} }
} }

View File

@@ -9,6 +9,8 @@
*/ */
import { MySqlService } from './mysql' import { MySqlService } from './mysql'
import { SqlServerService } from './sql-server'
import sql from 'mssql'
/** /**
* Material plan record interface * Material plan record interface
@@ -50,6 +52,7 @@ export interface MaterialPlanRecord {
* Configuration for DiscreteMaterialPlanData table * Configuration for DiscreteMaterialPlanData table
*/ */
export const DISCRETE_MATERIAL_PLAN_CONFIG = { export const DISCRETE_MATERIAL_PLAN_CONFIG = {
TABLE_NAME_SQLSERVER: '[dbo].[DiscreteMaterialPlanData]',
TABLE_NAME_MYSQL: 'dbo_DiscreteMaterialPlanData', TABLE_NAME_MYSQL: 'dbo_DiscreteMaterialPlanData',
COLUMNS: { COLUMNS: {
ID: 'ID', ID: 'ID',
@@ -90,25 +93,69 @@ export const DISCRETE_MATERIAL_PLAN_CONFIG = {
*/ */
export class DiscreteMaterialPlanDAO { export class DiscreteMaterialPlanDAO {
private mysqlService: MySqlService | null = null private mysqlService: MySqlService | null = null
private sqlServerService: SqlServerService | null = null
private dbType: 'mysql' | 'sqlserver' = 'mysql'
/** /**
* Get MySQL service instance * Constructor - determine database type from environment
*/ */
private async getMySqlService(): Promise<MySqlService> { constructor() {
if (this.mysqlService && this.mysqlService.isConnected()) { const dbType = process.env.DB_TYPE?.toLowerCase()
if (dbType === 'sqlserver' || dbType === 'mssql') {
this.dbType = 'sqlserver'
} else {
this.dbType = 'mysql'
}
}
/**
* Get the appropriate table name based on database type
*/
private getTableName(): string {
return this.dbType === 'sqlserver'
? DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_SQLSERVER
: DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL
}
/**
* Get database service instance (MySQL or SQL Server)
*/
private async getDatabaseService(): Promise<MySqlService | SqlServerService> {
if (this.dbType === 'sqlserver') {
if (this.sqlServerService && this.sqlServerService.isConnected()) {
return this.sqlServerService
}
this.sqlServerService = new SqlServerService({
server: process.env.DB_SERVER || 'localhost',
port: parseInt(process.env.DB_SQLSERVER_PORT || '1433', 10),
user: process.env.DB_USERNAME || 'sa',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || '',
options: {
encrypt: process.env.DB_TRUST_SERVER_CERTIFICATE === 'yes',
trustServerCertificate: process.env.DB_TRUST_SERVER_CERTIFICATE === 'yes'
}
})
await this.sqlServerService.connect()
return this.sqlServerService
} else {
if (this.mysqlService && this.mysqlService.isConnected()) {
return this.mysqlService
}
this.mysqlService = new MySqlService({
host: process.env.DB_MYSQL_HOST || 'localhost',
port: parseInt(process.env.DB_MYSQL_PORT || '3306', 10),
user: process.env.DB_USERNAME || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || ''
})
await this.mysqlService.connect()
return this.mysqlService return this.mysqlService
} }
this.mysqlService = new MySqlService({
host: process.env.DB_MYSQL_HOST || 'localhost',
port: parseInt(process.env.DB_MYSQL_PORT || '3306', 10),
user: process.env.DB_USERNAME || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || ''
})
await this.mysqlService.connect()
return this.mysqlService
} }
// ==================== QUERY ALL ==================== // ==================== QUERY ALL ====================
@@ -119,11 +166,15 @@ export class DiscreteMaterialPlanDAO {
*/ */
async queryAll(): Promise<any[]> { async queryAll(): Promise<any[]> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = `SELECT * FROM ${DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL}` const sql = `SELECT * FROM ${tableName}`
const result = this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sql)
: await (dbService as MySqlService).query(sql)
const result = await mysqlService.query(sql)
return result.rows return result.rows
} catch (error) { } catch (error) {
console.error('[DiscreteMaterialPlanDAO] Query all error:', error) console.error('[DiscreteMaterialPlanDAO] Query all error:', error)
@@ -139,7 +190,8 @@ export class DiscreteMaterialPlanDAO {
*/ */
async queryAllDistinctByMaterialCode(): Promise<any[]> { async queryAllDistinctByMaterialCode(): Promise<any[]> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` const sql = `
WITH RankedRecords AS ( WITH RankedRecords AS (
@@ -149,7 +201,7 @@ export class DiscreteMaterialPlanDAO {
PARTITION BY MaterialCode PARTITION BY MaterialCode
ORDER BY CreateDate ASC, SequenceNumber ASC ORDER BY CreateDate ASC, SequenceNumber ASC
) AS rn ) AS rn
FROM ${DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL} FROM ${tableName}
WHERE MaterialCode IS NOT NULL WHERE MaterialCode IS NOT NULL
) )
SELECT SELECT
@@ -163,7 +215,10 @@ export class DiscreteMaterialPlanDAO {
WHERE rn = 1 WHERE rn = 1
` `
const result = await mysqlService.query(sql) const result = this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sql)
: await (dbService as MySqlService).query(sql)
return result.rows return result.rows
} catch (error) { } catch (error) {
console.error('[DiscreteMaterialPlanDAO] Query all distinct by material code error:', error) console.error('[DiscreteMaterialPlanDAO] Query all distinct by material code error:', error)
@@ -184,22 +239,42 @@ export class DiscreteMaterialPlanDAO {
} }
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const batchSize = 2000 const batchSize = 2000
const allResults: any[] = [] const allResults: any[] = []
for (let i = 0; i < sourceNumbers.length; i += batchSize) { for (let i = 0; i < sourceNumbers.length; i += batchSize) {
const batch = sourceNumbers.slice(i, i + batchSize) const batch = sourceNumbers.slice(i, i + batchSize)
const placeholders = batch.map(() => '?').join(',')
const sql = ` if (this.dbType === 'sqlserver') {
SELECT * const placeholders = batch.map((_, idx) => `@p${idx}`).join(',')
FROM ${DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL} const params: Record<string, { value: string; type: sql.ISqlType }> = {}
WHERE SourceNumber IN (${placeholders})
`
const result = await mysqlService.query(sql, batch) batch.forEach((num, idx) => {
allResults.push(...result.rows) params[`p${idx}`] = { value: num, type: sql.NVarChar }
})
const sql = `
SELECT *
FROM ${tableName}
WHERE SourceNumber IN (${placeholders})
`
const result = await (dbService as SqlServerService).queryWithParams(sql, params)
allResults.push(...result.rows)
} else {
const placeholders = batch.map(() => '?').join(',')
const sql = `
SELECT *
FROM ${tableName}
WHERE SourceNumber IN (${placeholders})
`
const result = await (dbService as MySqlService).query(sql, batch)
allResults.push(...result.rows)
}
} }
return allResults return allResults
@@ -222,39 +297,76 @@ export class DiscreteMaterialPlanDAO {
} }
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const batchSize = 2000 const batchSize = 2000
const allResults: any[] = [] const allResults: any[] = []
for (let i = 0; i < sourceNumbers.length; i += batchSize) { for (let i = 0; i < sourceNumbers.length; i += batchSize) {
const batch = sourceNumbers.slice(i, i + batchSize) const batch = sourceNumbers.slice(i, i + batchSize)
const placeholders = batch.map(() => '?').join(',')
const sql = ` if (this.dbType === 'sqlserver') {
WITH RankedRecords AS ( const placeholders = batch.map((_, idx) => `@p${idx}`).join(',')
const params: Record<string, { value: string; type: sql.ISqlType }> = {}
batch.forEach((num, idx) => {
params[`p${idx}`] = { value: num, type: sql.NVarChar }
})
const sql = `
WITH RankedRecords AS (
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY MaterialCode
ORDER BY CreateDate ASC, SequenceNumber ASC
) AS rn
FROM ${tableName}
WHERE SourceNumber IN (${placeholders})
AND MaterialCode IS NOT NULL
)
SELECT SELECT
*, Factory, MaterialStatus, PlanNumber, SourceNumber, MaterialType,
ROW_NUMBER() OVER ( ProductCode, ProductName, ProductUnit, ProductPlanQuantity,
PARTITION BY MaterialCode UseDepartment, Remark, Creator, CreateDate, Approver, ApproveDate,
ORDER BY CreateDate ASC, SequenceNumber ASC SequenceNumber, MaterialCode, MaterialName, Specification, Model,
) AS rn DrawingNumber, MaterialQuality, PlanQuantity, Unit, RequiredDate,
FROM ${DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL} Warehouse, UnitUsage, CumulativeOutputQuantity, BOMVersion
WHERE SourceNumber IN (${placeholders}) FROM RankedRecords
AND MaterialCode IS NOT NULL WHERE rn = 1
) `
SELECT
Factory, MaterialStatus, PlanNumber, SourceNumber, MaterialType,
ProductCode, ProductName, ProductUnit, ProductPlanQuantity,
UseDepartment, Remark, Creator, CreateDate, Approver, ApproveDate,
SequenceNumber, MaterialCode, MaterialName, Specification, Model,
DrawingNumber, MaterialQuality, PlanQuantity, Unit, RequiredDate,
Warehouse, UnitUsage, CumulativeOutputQuantity, BOMVersion
FROM RankedRecords
WHERE rn = 1
`
const result = await mysqlService.query(sql, batch) const result = await (dbService as SqlServerService).queryWithParams(sql, params)
allResults.push(...result.rows) allResults.push(...result.rows)
} else {
const placeholders = batch.map(() => '?').join(',')
const sql = `
WITH RankedRecords AS (
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY MaterialCode
ORDER BY CreateDate ASC, SequenceNumber ASC
) AS rn
FROM ${tableName}
WHERE SourceNumber IN (${placeholders})
AND MaterialCode IS NOT NULL
)
SELECT
Factory, MaterialStatus, PlanNumber, SourceNumber, MaterialType,
ProductCode, ProductName, ProductUnit, ProductPlanQuantity,
UseDepartment, Remark, Creator, CreateDate, Approver, ApproveDate,
SequenceNumber, MaterialCode, MaterialName, Specification, Model,
DrawingNumber, MaterialQuality, PlanQuantity, Unit, RequiredDate,
Warehouse, UnitUsage, CumulativeOutputQuantity, BOMVersion
FROM RankedRecords
WHERE rn = 1
`
const result = await (dbService as MySqlService).query(sql, batch)
allResults.push(...result.rows)
}
} }
return allResults return allResults
@@ -271,16 +383,32 @@ export class DiscreteMaterialPlanDAO {
*/ */
async queryBySourceNumber(sourceNumber: string): Promise<any[]> { async queryBySourceNumber(sourceNumber: string): Promise<any[]> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` if (this.dbType === 'sqlserver') {
SELECT * const sql = `
FROM ${DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL} SELECT *
WHERE SourceNumber = ? FROM ${tableName}
` WHERE SourceNumber = @sourceNumber
`
const result = await mysqlService.query(sql, [sourceNumber]) const result = await (dbService as SqlServerService).queryWithParams(sql, {
return result.rows sourceNumber: { value: sourceNumber, type: sql.NVarChar }
})
return result.rows
} else {
const sql = `
SELECT *
FROM ${tableName}
WHERE SourceNumber = ?
`
const result = await (dbService as MySqlService).query(sql, [sourceNumber])
return result.rows
}
} catch (error) { } catch (error) {
console.error('[DiscreteMaterialPlanDAO] Query by source number error:', error) console.error('[DiscreteMaterialPlanDAO] Query by source number error:', error)
return [] return []
@@ -296,16 +424,32 @@ export class DiscreteMaterialPlanDAO {
*/ */
async queryByPlanNumber(planNumber: string): Promise<any[]> { async queryByPlanNumber(planNumber: string): Promise<any[]> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` if (this.dbType === 'sqlserver') {
SELECT * const sql = `
FROM ${DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL} SELECT *
WHERE PlanNumber = ? FROM ${tableName}
` WHERE PlanNumber = @planNumber
`
const result = await mysqlService.query(sql, [planNumber]) const result = await (dbService as SqlServerService).queryWithParams(sql, {
return result.rows planNumber: { value: planNumber, type: sql.NVarChar }
})
return result.rows
} else {
const sql = `
SELECT *
FROM ${tableName}
WHERE PlanNumber = ?
`
const result = await (dbService as MySqlService).query(sql, [planNumber])
return result.rows
}
} catch (error) { } catch (error) {
console.error('[DiscreteMaterialPlanDAO] Query by plan number error:', error) console.error('[DiscreteMaterialPlanDAO] Query by plan number error:', error)
return [] return []
@@ -323,17 +467,37 @@ export class DiscreteMaterialPlanDAO {
} }
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const placeholders = planNumbers.map(() => '?').join(',') const tableName = this.getTableName()
const sql = ` if (this.dbType === 'sqlserver') {
SELECT * const placeholders = planNumbers.map((_, idx) => `@p${idx}`).join(',')
FROM ${DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL} const params: Record<string, { value: string; type: sql.ISqlType }> = {}
WHERE PlanNumber IN (${placeholders})
`
const result = await mysqlService.query(sql, planNumbers) planNumbers.forEach((num, idx) => {
return result.rows params[`p${idx}`] = { value: num, type: sql.NVarChar }
})
const sql = `
SELECT *
FROM ${tableName}
WHERE PlanNumber IN (${placeholders})
`
const result = await (dbService as SqlServerService).queryWithParams(sql, params)
return result.rows
} else {
const placeholders = planNumbers.map(() => '?').join(',')
const sql = `
SELECT *
FROM ${tableName}
WHERE PlanNumber IN (${placeholders})
`
const result = await (dbService as MySqlService).query(sql, planNumbers)
return result.rows
}
} catch (error) { } catch (error) {
console.error('[DiscreteMaterialPlanDAO] Query by plan numbers error:', error) console.error('[DiscreteMaterialPlanDAO] Query by plan numbers error:', error)
return [] return []
@@ -348,11 +512,15 @@ export class DiscreteMaterialPlanDAO {
*/ */
async countAll(): Promise<number> { async countAll(): Promise<number> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = `SELECT COUNT(*) as count FROM ${DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL}` const sql = `SELECT COUNT(*) as count FROM ${tableName}`
const result = this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sql)
: await (dbService as MySqlService).query(sql)
const result = await mysqlService.query(sql)
return result.rows.length > 0 ? (result.rows[0].count as number) : 0 return result.rows.length > 0 ? (result.rows[0].count as number) : 0
} catch (error) { } catch (error) {
console.error('[DiscreteMaterialPlanDAO] Count all error:', error) console.error('[DiscreteMaterialPlanDAO] Count all error:', error)
@@ -367,16 +535,32 @@ export class DiscreteMaterialPlanDAO {
*/ */
async countByPlanNumber(planNumber: string): Promise<number> { async countByPlanNumber(planNumber: string): Promise<number> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` if (this.dbType === 'sqlserver') {
SELECT COUNT(*) as count const sql = `
FROM ${DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL} SELECT COUNT(*) as count
WHERE PlanNumber = ? FROM ${tableName}
` WHERE PlanNumber = @planNumber
`
const result = await mysqlService.query(sql, [planNumber]) const result = await (dbService as SqlServerService).queryWithParams(sql, {
return result.rows.length > 0 ? (result.rows[0].count as number) : 0 planNumber: { value: planNumber, type: sql.NVarChar }
})
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
} else {
const sql = `
SELECT COUNT(*) as count
FROM ${tableName}
WHERE PlanNumber = ?
`
const result = await (dbService as MySqlService).query(sql, [planNumber])
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
}
} catch (error) { } catch (error) {
console.error('[DiscreteMaterialPlanDAO] Count by plan number error:', error) console.error('[DiscreteMaterialPlanDAO] Count by plan number error:', error)
return 0 return 0
@@ -390,32 +574,59 @@ export class DiscreteMaterialPlanDAO {
*/ */
async getUniqueMaterialNames(sourceNumbers?: string[]): Promise<string[]> { async getUniqueMaterialNames(sourceNumbers?: string[]): Promise<string[]> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
let sql: string
let params: any[] = []
if (sourceNumbers && sourceNumbers.length > 0) { if (sourceNumbers && sourceNumbers.length > 0) {
const placeholders = sourceNumbers.map(() => '?').join(',') if (this.dbType === 'sqlserver') {
sql = ` const placeholders = sourceNumbers.map((_, idx) => `@p${idx}`).join(',')
SELECT DISTINCT MaterialName const params: Record<string, { value: string; type: sql.ISqlType }> = {}
FROM ${DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL}
WHERE SourceNumber IN (${placeholders}) sourceNumbers.forEach((num, idx) => {
AND MaterialName IS NOT NULL params[`p${idx}`] = { value: num, type: sql.NVarChar }
` })
params = sourceNumbers
const sql = `
SELECT DISTINCT MaterialName
FROM ${tableName}
WHERE SourceNumber IN (${placeholders})
AND MaterialName IS NOT NULL
`
const result = await (dbService as SqlServerService).queryWithParams(sql, params)
return result.rows
.map(row => row.MaterialName as string)
.filter(Boolean)
} else {
const placeholders = sourceNumbers.map(() => '?').join(',')
const sql = `
SELECT DISTINCT MaterialName
FROM ${tableName}
WHERE SourceNumber IN (${placeholders})
AND MaterialName IS NOT NULL
`
const result = await (dbService as MySqlService).query(sql, sourceNumbers)
return result.rows
.map(row => row.MaterialName as string)
.filter(Boolean)
}
} else { } else {
sql = ` const sql = `
SELECT DISTINCT MaterialName SELECT DISTINCT MaterialName
FROM ${DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL} FROM ${tableName}
WHERE MaterialName IS NOT NULL WHERE MaterialName IS NOT NULL
` `
}
const result = await mysqlService.query(sql, params) const result = this.dbType === 'sqlserver'
return result.rows ? await (dbService as SqlServerService).query(sql)
.map(row => row.MaterialName as string) : await (dbService as MySqlService).query(sql)
.filter(Boolean)
return result.rows
.map(row => row.MaterialName as string)
.filter(Boolean)
}
} catch (error) { } catch (error) {
console.error('[DiscreteMaterialPlanDAO] Get unique material names error:', error) console.error('[DiscreteMaterialPlanDAO] Get unique material names error:', error)
return [] return []
@@ -428,7 +639,8 @@ export class DiscreteMaterialPlanDAO {
*/ */
async getStatistics(): Promise<any> { async getStatistics(): Promise<any> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` const sql = `
SELECT SELECT
@@ -437,10 +649,13 @@ export class DiscreteMaterialPlanDAO {
COUNT(DISTINCT SourceNumber) as uniqueOrders, COUNT(DISTINCT SourceNumber) as uniqueOrders,
MIN(CreateDate) as earliestRecord, MIN(CreateDate) as earliestRecord,
MAX(CreateDate) as latestRecord MAX(CreateDate) as latestRecord
FROM ${DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL} FROM ${tableName}
` `
const result = await mysqlService.query(sql) const result = this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sql)
: await (dbService as MySqlService).query(sql)
return result.rows.length > 0 ? result.rows[0] : {} return result.rows.length > 0 ? result.rows[0] : {}
} catch (error) { } catch (error) {
console.error('[DiscreteMaterialPlanDAO] Get statistics error:', error) console.error('[DiscreteMaterialPlanDAO] Get statistics error:', error)
@@ -456,5 +671,9 @@ export class DiscreteMaterialPlanDAO {
await this.mysqlService.disconnect() await this.mysqlService.disconnect()
this.mysqlService = null this.mysqlService = null
} }
if (this.sqlServerService) {
await this.sqlServerService.disconnect()
this.sqlServerService = null
}
} }
} }

View File

@@ -9,6 +9,8 @@
*/ */
import { MySqlService } from './mysql' import { MySqlService } from './mysql'
import { SqlServerService } from './sql-server'
import sql from 'mssql'
/** /**
* Material record interface * Material record interface
@@ -41,6 +43,7 @@ export interface MaterialStats {
* Configuration for MaterialsToBeDeleted table * Configuration for MaterialsToBeDeleted table
*/ */
export const MATERIALS_TO_BE_DELETED_CONFIG = { export const MATERIALS_TO_BE_DELETED_CONFIG = {
TABLE_NAME_SQLSERVER: '[dbo].[MaterialsToBeDeleted]',
TABLE_NAME_MYSQL: 'dbo_MaterialsToBeDeleted', TABLE_NAME_MYSQL: 'dbo_MaterialsToBeDeleted',
COLUMNS: { COLUMNS: {
ID: 'ID', ID: 'ID',
@@ -54,25 +57,69 @@ export const MATERIALS_TO_BE_DELETED_CONFIG = {
*/ */
export class MaterialsToBeDeletedDAO { export class MaterialsToBeDeletedDAO {
private mysqlService: MySqlService | null = null private mysqlService: MySqlService | null = null
private sqlServerService: SqlServerService | null = null
private dbType: 'mysql' | 'sqlserver' = 'mysql'
/** /**
* Get MySQL service instance * Constructor - determine database type from environment
*/ */
private async getMySqlService(): Promise<MySqlService> { constructor() {
if (this.mysqlService && this.mysqlService.isConnected()) { const dbType = process.env.DB_TYPE?.toLowerCase()
if (dbType === 'sqlserver' || dbType === 'mssql') {
this.dbType = 'sqlserver'
} else {
this.dbType = 'mysql'
}
}
/**
* Get the appropriate table name based on database type
*/
private getTableName(): string {
return this.dbType === 'sqlserver'
? MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_SQLSERVER
: MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL
}
/**
* Get database service instance (MySQL or SQL Server)
*/
private async getDatabaseService(): Promise<MySqlService | SqlServerService> {
if (this.dbType === 'sqlserver') {
if (this.sqlServerService && this.sqlServerService.isConnected()) {
return this.sqlServerService
}
this.sqlServerService = new SqlServerService({
server: process.env.DB_SERVER || 'localhost',
port: parseInt(process.env.DB_SQLSERVER_PORT || '1433', 10),
user: process.env.DB_USERNAME || 'sa',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || '',
options: {
encrypt: process.env.DB_TRUST_SERVER_CERTIFICATE === 'yes',
trustServerCertificate: process.env.DB_TRUST_SERVER_CERTIFICATE === 'yes'
}
})
await this.sqlServerService.connect()
return this.sqlServerService
} else {
if (this.mysqlService && this.mysqlService.isConnected()) {
return this.mysqlService
}
this.mysqlService = new MySqlService({
host: process.env.DB_MYSQL_HOST || 'localhost',
port: parseInt(process.env.DB_MYSQL_PORT || '3306', 10),
user: process.env.DB_USERNAME || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || ''
})
await this.mysqlService.connect()
return this.mysqlService return this.mysqlService
} }
this.mysqlService = new MySqlService({
host: process.env.DB_MYSQL_HOST || 'localhost',
port: parseInt(process.env.DB_MYSQL_PORT || '3306', 10),
user: process.env.DB_USERNAME || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || ''
})
await this.mysqlService.connect()
return this.mysqlService
} }
// ==================== UPSERT (MERGE) ==================== // ==================== UPSERT (MERGE) ====================
@@ -90,15 +137,34 @@ export class MaterialsToBeDeletedDAO {
} }
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const code = materialCode.trim()
const manager = managerName?.trim() || null
const sql = ` if (this.dbType === 'sqlserver') {
INSERT INTO ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} (MaterialCode, ManagerName) const sql = `
VALUES (?, ?) MERGE ${tableName} AS target
ON DUPLICATE KEY UPDATE ManagerName = VALUES(ManagerName) USING (VALUES (@materialCode, @managerName)) AS source (MaterialCode, ManagerName)
` ON target.MaterialCode = source.MaterialCode
WHEN MATCHED THEN UPDATE SET ManagerName = source.ManagerName
WHEN NOT MATCHED THEN INSERT (MaterialCode, ManagerName) VALUES (source.MaterialCode, source.ManagerName);
`
await (dbService as SqlServerService).queryWithParams(sql, {
materialCode: { value: code, type: sql.NVarChar },
managerName: { value: manager, type: sql.NVarChar }
})
} else {
const sql = `
INSERT INTO ${tableName} (MaterialCode, ManagerName)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE ManagerName = VALUES(ManagerName)
`
await (dbService as MySqlService).query(sql, [code, manager])
}
await mysqlService.query(sql, [materialCode.trim(), managerName?.trim() || null])
return true return true
} catch (error) { } catch (error) {
console.error('[MaterialsToBeDeletedDAO] Upsert material error:', error) console.error('[MaterialsToBeDeletedDAO] Upsert material error:', error)
@@ -123,7 +189,8 @@ export class MaterialsToBeDeletedDAO {
} }
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
for (const material of materials) { for (const material of materials) {
const materialCode = material.materialCode?.trim() const materialCode = material.materialCode?.trim()
@@ -135,13 +202,29 @@ export class MaterialsToBeDeletedDAO {
} }
try { try {
const sql = ` if (this.dbType === 'sqlserver') {
INSERT INTO ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} (MaterialCode, ManagerName) const sql = `
VALUES (?, ?) MERGE ${tableName} AS target
ON DUPLICATE KEY UPDATE ManagerName = VALUES(ManagerName) USING (VALUES (@materialCode, @managerName)) AS source (MaterialCode, ManagerName)
` ON target.MaterialCode = source.MaterialCode
WHEN MATCHED THEN UPDATE SET ManagerName = source.ManagerName
WHEN NOT MATCHED THEN INSERT (MaterialCode, ManagerName) VALUES (source.MaterialCode, source.ManagerName);
`
await (dbService as SqlServerService).queryWithParams(sql, {
materialCode: { value: materialCode, type: sql.NVarChar },
managerName: { value: managerName || null, type: sql.NVarChar }
})
} else {
const sql = `
INSERT INTO ${tableName} (MaterialCode, ManagerName)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE ManagerName = VALUES(ManagerName)
`
await (dbService as MySqlService).query(sql, [materialCode, managerName || null])
}
await mysqlService.query(sql, [materialCode, managerName || null])
stats.success++ stats.success++
} catch (error) { } catch (error) {
console.error('[MaterialsToBeDeletedDAO] Error upserting material:', materialCode, error) console.error('[MaterialsToBeDeletedDAO] Error upserting material:', materialCode, error)
@@ -164,15 +247,19 @@ export class MaterialsToBeDeletedDAO {
*/ */
async getAllMaterialCodes(): Promise<Set<string>> { async getAllMaterialCodes(): Promise<Set<string>> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` const sql = `
SELECT MaterialCode SELECT MaterialCode
FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} FROM ${tableName}
WHERE MaterialCode IS NOT NULL WHERE MaterialCode IS NOT NULL
` `
const result = await mysqlService.query(sql) const result = this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sql)
: await (dbService as MySqlService).query(sql)
return new Set(result.rows.map(row => row.MaterialCode as string).filter(Boolean)) return new Set(result.rows.map(row => row.MaterialCode as string).filter(Boolean))
} catch (error) { } catch (error) {
console.error('[MaterialsToBeDeletedDAO] Get all material codes error:', error) console.error('[MaterialsToBeDeletedDAO] Get all material codes error:', error)
@@ -186,16 +273,20 @@ export class MaterialsToBeDeletedDAO {
*/ */
async getAllRecords(): Promise<MaterialRecord[]> { async getAllRecords(): Promise<MaterialRecord[]> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` const sql = `
SELECT ID, MaterialCode, ManagerName SELECT ID, MaterialCode, ManagerName
FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} FROM ${tableName}
WHERE MaterialCode IS NOT NULL WHERE MaterialCode IS NOT NULL
ORDER BY ManagerName, MaterialCode ORDER BY ManagerName, MaterialCode
` `
const result = await mysqlService.query(sql) const result = this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sql)
: await (dbService as MySqlService).query(sql)
return result.rows.map(row => ({ return result.rows.map(row => ({
id: row.ID as number, id: row.ID as number,
materialCode: row.MaterialCode as string, materialCode: row.MaterialCode as string,
@@ -214,21 +305,42 @@ export class MaterialsToBeDeletedDAO {
*/ */
async getMaterialsByManager(managerName: string): Promise<MaterialRecord[]> { async getMaterialsByManager(managerName: string): Promise<MaterialRecord[]> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` if (this.dbType === 'sqlserver') {
SELECT ID, MaterialCode, ManagerName const sql = `
FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} SELECT ID, MaterialCode, ManagerName
WHERE ManagerName = ? AND MaterialCode IS NOT NULL FROM ${tableName}
ORDER BY MaterialCode WHERE ManagerName = @managerName AND MaterialCode IS NOT NULL
` ORDER BY MaterialCode
`
const result = await mysqlService.query(sql, [managerName]) const result = await (dbService as SqlServerService).queryWithParams(sql, {
return result.rows.map(row => ({ managerName: { value: managerName, type: sql.NVarChar }
id: row.ID as number, })
materialCode: row.MaterialCode as string,
managerName: row.ManagerName as string return result.rows.map(row => ({
})) id: row.ID as number,
materialCode: row.MaterialCode as string,
managerName: row.ManagerName as string
}))
} else {
const sql = `
SELECT ID, MaterialCode, ManagerName
FROM ${tableName}
WHERE ManagerName = ? AND MaterialCode IS NOT NULL
ORDER BY MaterialCode
`
const result = await (dbService as MySqlService).query(sql, [managerName])
return result.rows.map(row => ({
id: row.ID as number,
materialCode: row.MaterialCode as string,
managerName: row.ManagerName as string
}))
}
} catch (error) { } catch (error) {
console.error('[MaterialsToBeDeletedDAO] Get materials by manager error:', error) console.error('[MaterialsToBeDeletedDAO] Get materials by manager error:', error)
return [] return []
@@ -241,16 +353,20 @@ export class MaterialsToBeDeletedDAO {
*/ */
async getManagers(): Promise<string[]> { async getManagers(): Promise<string[]> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` const sql = `
SELECT DISTINCT ManagerName SELECT DISTINCT ManagerName
FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} FROM ${tableName}
WHERE ManagerName IS NOT NULL WHERE ManagerName IS NOT NULL
ORDER BY ManagerName ORDER BY ManagerName
` `
const result = await mysqlService.query(sql) const result = this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sql)
: await (dbService as MySqlService).query(sql)
return result.rows.map(row => row.ManagerName as string).filter(Boolean) return result.rows.map(row => row.ManagerName as string).filter(Boolean)
} catch (error) { } catch (error) {
console.error('[MaterialsToBeDeletedDAO] Get managers error:', error) console.error('[MaterialsToBeDeletedDAO] Get managers error:', error)
@@ -265,24 +381,50 @@ export class MaterialsToBeDeletedDAO {
*/ */
async getRecordByMaterialCode(materialCode: string): Promise<MaterialRecord | null> { async getRecordByMaterialCode(materialCode: string): Promise<MaterialRecord | null> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const code = materialCode.trim()
const sql = ` if (this.dbType === 'sqlserver') {
SELECT ID, MaterialCode, ManagerName const sql = `
FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} SELECT ID, MaterialCode, ManagerName
WHERE MaterialCode = ? FROM ${tableName}
` WHERE MaterialCode = @materialCode
`
const result = await mysqlService.query(sql, [materialCode.trim()]) const result = await (dbService as SqlServerService).queryWithParams(sql, {
if (result.rows.length === 0) { materialCode: { value: code, type: sql.NVarChar }
return null })
}
const row = result.rows[0] if (result.rows.length === 0) {
return { return null
id: row.ID as number, }
materialCode: row.MaterialCode as string,
managerName: row.ManagerName as string const row = result.rows[0]
return {
id: row.ID as number,
materialCode: row.MaterialCode as string,
managerName: row.ManagerName as string
}
} else {
const sql = `
SELECT ID, MaterialCode, ManagerName
FROM ${tableName}
WHERE MaterialCode = ?
`
const result = await (dbService as MySqlService).query(sql, [code])
if (result.rows.length === 0) {
return null
}
const row = result.rows[0]
return {
id: row.ID as number,
materialCode: row.MaterialCode as string,
managerName: row.ManagerName as string
}
} }
} catch (error) { } catch (error) {
console.error('[MaterialsToBeDeletedDAO] Get record by material code error:', error) console.error('[MaterialsToBeDeletedDAO] Get record by material code error:', error)
@@ -299,15 +441,31 @@ export class MaterialsToBeDeletedDAO {
*/ */
async deleteByMaterialCode(materialCode: string): Promise<boolean> { async deleteByMaterialCode(materialCode: string): Promise<boolean> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const code = materialCode.trim()
const sql = ` if (this.dbType === 'sqlserver') {
DELETE FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} const sql = `
WHERE MaterialCode = ? DELETE FROM ${tableName}
` WHERE MaterialCode = @materialCode
`
const result = await mysqlService.query(sql, [materialCode.trim()]) const result = await (dbService as SqlServerService).queryWithParams(sql, {
return result.rowCount > 0 materialCode: { value: code, type: sql.NVarChar }
})
return result.rowCount > 0
} else {
const sql = `
DELETE FROM ${tableName}
WHERE MaterialCode = ?
`
const result = await (dbService as MySqlService).query(sql, [code])
return result.rowCount > 0
}
} catch (error) { } catch (error) {
console.error('[MaterialsToBeDeletedDAO] Delete by material code error:', error) console.error('[MaterialsToBeDeletedDAO] Delete by material code error:', error)
return false return false
@@ -321,15 +479,30 @@ export class MaterialsToBeDeletedDAO {
*/ */
async deleteByManager(managerName: string): Promise<number> { async deleteByManager(managerName: string): Promise<number> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` if (this.dbType === 'sqlserver') {
DELETE FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} const sql = `
WHERE ManagerName = ? DELETE FROM ${tableName}
` WHERE ManagerName = @managerName
`
const result = await mysqlService.query(sql, [managerName]) const result = await (dbService as SqlServerService).queryWithParams(sql, {
return result.rowCount managerName: { value: managerName, type: sql.NVarChar }
})
return result.rowCount
} else {
const sql = `
DELETE FROM ${tableName}
WHERE ManagerName = ?
`
const result = await (dbService as MySqlService).query(sql, [managerName])
return result.rowCount
}
} catch (error) { } catch (error) {
console.error('[MaterialsToBeDeletedDAO] Delete by manager error:', error) console.error('[MaterialsToBeDeletedDAO] Delete by manager error:', error)
return 0 return 0
@@ -342,11 +515,15 @@ export class MaterialsToBeDeletedDAO {
*/ */
async deleteAllMaterials(): Promise<number> { async deleteAllMaterials(): Promise<number> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = `DELETE FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL}` const sql = `DELETE FROM ${tableName}`
const result = this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sql)
: await (dbService as MySqlService).query(sql)
const result = await mysqlService.query(sql)
return result.rowCount return result.rowCount
} catch (error) { } catch (error) {
console.error('[MaterialsToBeDeletedDAO] Delete all materials error:', error) console.error('[MaterialsToBeDeletedDAO] Delete all materials error:', error)
@@ -368,19 +545,38 @@ export class MaterialsToBeDeletedDAO {
const batchSize = 1000 const batchSize = 1000
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
for (let i = 0; i < materialCodes.length; i += batchSize) { for (let i = 0; i < materialCodes.length; i += batchSize) {
const batch = materialCodes.slice(i, i + batchSize) const batch = materialCodes.slice(i, i + batchSize)
const placeholders = batch.map(() => '?').join(',')
const sql = ` if (this.dbType === 'sqlserver') {
DELETE FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} const placeholders = batch.map((_, idx) => `@p${idx}`).join(',')
WHERE MaterialCode IN (${placeholders}) const params: Record<string, { value: string; type: sql.ISqlType }> = {}
`
const result = await mysqlService.query(sql, batch) batch.forEach((code, idx) => {
totalDeleted += result.rowCount params[`p${idx}`] = { value: code.trim(), type: sql.NVarChar }
})
const sql = `
DELETE FROM ${tableName}
WHERE MaterialCode IN (${placeholders})
`
const result = await (dbService as SqlServerService).queryWithParams(sql, params)
totalDeleted += result.rowCount
} else {
const placeholders = batch.map(() => '?').join(',')
const sql = `
DELETE FROM ${tableName}
WHERE MaterialCode IN (${placeholders})
`
const result = await (dbService as MySqlService).query(sql, batch)
totalDeleted += result.rowCount
}
} }
} catch (error) { } catch (error) {
console.error('[MaterialsToBeDeletedDAO] Delete by material codes error:', error) console.error('[MaterialsToBeDeletedDAO] Delete by material codes error:', error)
@@ -398,16 +594,33 @@ export class MaterialsToBeDeletedDAO {
*/ */
async materialExists(materialCode: string): Promise<boolean> { async materialExists(materialCode: string): Promise<boolean> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const code = materialCode.trim()
const sql = ` if (this.dbType === 'sqlserver') {
SELECT COUNT(*) as count const sql = `
FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} SELECT COUNT(*) as count
WHERE MaterialCode = ? FROM ${tableName}
` WHERE MaterialCode = @materialCode
`
const result = await mysqlService.query(sql, [materialCode.trim()]) const result = await (dbService as SqlServerService).queryWithParams(sql, {
return result.rows.length > 0 && (result.rows[0].count as number) > 0 materialCode: { value: code, type: sql.NVarChar }
})
return result.rows.length > 0 && (result.rows[0].count as number) > 0
} else {
const sql = `
SELECT COUNT(*) as count
FROM ${tableName}
WHERE MaterialCode = ?
`
const result = await (dbService as MySqlService).query(sql, [code])
return result.rows.length > 0 && (result.rows[0].count as number) > 0
}
} catch (error) { } catch (error) {
console.error('[MaterialsToBeDeletedDAO] Material exists error:', error) console.error('[MaterialsToBeDeletedDAO] Material exists error:', error)
return false return false
@@ -420,11 +633,15 @@ export class MaterialsToBeDeletedDAO {
*/ */
async countAll(): Promise<number> { async countAll(): Promise<number> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = `SELECT COUNT(*) as count FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL}` const sql = `SELECT COUNT(*) as count FROM ${tableName}`
const result = this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sql)
: await (dbService as MySqlService).query(sql)
const result = await mysqlService.query(sql)
return result.rows.length > 0 ? (result.rows[0].count as number) : 0 return result.rows.length > 0 ? (result.rows[0].count as number) : 0
} catch (error) { } catch (error) {
console.error('[MaterialsToBeDeletedDAO] Count all error:', error) console.error('[MaterialsToBeDeletedDAO] Count all error:', error)
@@ -439,16 +656,32 @@ export class MaterialsToBeDeletedDAO {
*/ */
async countByManager(managerName: string): Promise<number> { async countByManager(managerName: string): Promise<number> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` if (this.dbType === 'sqlserver') {
SELECT COUNT(*) as count const sql = `
FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} SELECT COUNT(*) as count
WHERE ManagerName = ? FROM ${tableName}
` WHERE ManagerName = @managerName
`
const result = await mysqlService.query(sql, [managerName]) const result = await (dbService as SqlServerService).queryWithParams(sql, {
return result.rows.length > 0 ? (result.rows[0].count as number) : 0 managerName: { value: managerName, type: sql.NVarChar }
})
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
} else {
const sql = `
SELECT COUNT(*) as count
FROM ${tableName}
WHERE ManagerName = ?
`
const result = await (dbService as MySqlService).query(sql, [managerName])
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
}
} catch (error) { } catch (error) {
console.error('[MaterialsToBeDeletedDAO] Count by manager error:', error) console.error('[MaterialsToBeDeletedDAO] Count by manager error:', error)
return 0 return 0
@@ -461,30 +694,37 @@ export class MaterialsToBeDeletedDAO {
*/ */
async getStatistics(): Promise<MaterialStats> { async getStatistics(): Promise<MaterialStats> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
// Get total and unique managers // Get total and unique managers
const statsSql = ` const statsSql = `
SELECT SELECT
COUNT(*) as totalMaterials, COUNT(*) as totalMaterials,
COUNT(DISTINCT ManagerName) as uniqueManagers COUNT(DISTINCT ManagerName) as uniqueManagers
FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} FROM ${tableName}
WHERE MaterialCode IS NOT NULL WHERE MaterialCode IS NOT NULL
` `
const statsResult = await mysqlService.query(statsSql) const statsResult = this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(statsSql)
: await (dbService as MySqlService).query(statsSql)
const stats = statsResult.rows[0] || {} const stats = statsResult.rows[0] || {}
// Get materials per manager // Get materials per manager
const managerSql = ` const managerSql = `
SELECT ManagerName, COUNT(*) as count SELECT ManagerName, COUNT(*) as count
FROM ${MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL} FROM ${tableName}
WHERE ManagerName IS NOT NULL WHERE ManagerName IS NOT NULL
GROUP BY ManagerName GROUP BY ManagerName
ORDER BY count DESC ORDER BY count DESC
` `
const managerResult = await mysqlService.query(managerSql) const managerResult = this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(managerSql)
: await (dbService as MySqlService).query(managerSql)
const materialsPerManager = managerResult.rows.map(row => ({ const materialsPerManager = managerResult.rows.map(row => ({
[row.ManagerName as string]: row.count as number [row.ManagerName as string]: row.count as number
})) }))
@@ -512,5 +752,9 @@ export class MaterialsToBeDeletedDAO {
await this.mysqlService.disconnect() await this.mysqlService.disconnect()
this.mysqlService = null this.mysqlService = null
} }
if (this.sqlServerService) {
await this.sqlServerService.disconnect()
this.sqlServerService = null
}
} }
} }

View File

@@ -98,8 +98,9 @@ export class SqlServerService {
const result = await request.query(sqlString) const result = await request.query(sqlString)
// Convert recordset to array of objects // Convert recordset to array of objects
const columns = result.recordset.columns?.map((col) => col.name) || []
const rows = result.recordset as Record<string, unknown>[] const rows = result.recordset as Record<string, unknown>[]
// Extract column names from the first row if available
const columns = rows.length > 0 ? Object.keys(rows[0]) : []
return { return {
rows, rows,
@@ -137,8 +138,9 @@ export class SqlServerService {
const result = await request.query(sqlString) const result = await request.query(sqlString)
// Convert recordset to array of objects // Convert recordset to array of objects
const columns = result.recordset.columns?.map((col) => col.name) || []
const rows = result.recordset as Record<string, unknown>[] const rows = result.recordset as Record<string, unknown>[]
// Extract column names from the first row if available
const columns = rows.length > 0 ? Object.keys(rows[0]) : []
return { return {
rows, rows,

View File

@@ -9,6 +9,8 @@
*/ */
import { MySqlService } from '../database/mysql' import { MySqlService } from '../database/mysql'
import { SqlServerService } from '../database/sql-server'
import sql from 'mssql'
import type { UserInfo } from '../../types/user.types' import type { UserInfo } from '../../types/user.types'
/** /**
@@ -35,25 +37,69 @@ export const BIP_USERS_CONFIG = {
*/ */
export class BIPUsersDAO { export class BIPUsersDAO {
private mysqlService: MySqlService | null = null private mysqlService: MySqlService | null = null
private sqlServerService: SqlServerService | null = null
private dbType: 'mysql' | 'sqlserver' = 'mysql'
/** /**
* Get MySQL service instance * Constructor - determine database type from environment
*/ */
private async getMySqlService(): Promise<MySqlService> { constructor() {
if (this.mysqlService && this.mysqlService.isConnected()) { const dbType = process.env.DB_TYPE?.toLowerCase()
if (dbType === 'sqlserver' || dbType === 'mssql') {
this.dbType = 'sqlserver'
} else {
this.dbType = 'mysql'
}
}
/**
* Get the appropriate table name based on database type
*/
private getTableName(): string {
return this.dbType === 'sqlserver'
? BIP_USERS_CONFIG.TABLE_NAME_SQLSERVER
: BIP_USERS_CONFIG.TABLE_NAME_MYSQL
}
/**
* Get database service instance (MySQL or SQL Server)
*/
private async getDatabaseService(): Promise<MySqlService | SqlServerService> {
if (this.dbType === 'sqlserver') {
if (this.sqlServerService && this.sqlServerService.isConnected()) {
return this.sqlServerService
}
this.sqlServerService = new SqlServerService({
server: process.env.DB_SERVER || 'localhost',
port: parseInt(process.env.DB_SQLSERVER_PORT || '1433', 10),
user: process.env.DB_USERNAME || 'sa',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || '',
options: {
encrypt: process.env.DB_TRUST_SERVER_CERTIFICATE === 'yes',
trustServerCertificate: process.env.DB_TRUST_SERVER_CERTIFICATE === 'yes'
}
})
await this.sqlServerService.connect()
return this.sqlServerService
} else {
if (this.mysqlService && this.mysqlService.isConnected()) {
return this.mysqlService
}
this.mysqlService = new MySqlService({
host: process.env.DB_MYSQL_HOST || 'localhost',
port: parseInt(process.env.DB_MYSQL_PORT || '3306', 10),
user: process.env.DB_USERNAME || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || ''
})
await this.mysqlService.connect()
return this.mysqlService return this.mysqlService
} }
this.mysqlService = new MySqlService({
host: process.env.DB_MYSQL_HOST || 'localhost',
port: parseInt(process.env.DB_MYSQL_PORT || '3306', 10),
user: process.env.DB_USERNAME || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || ''
})
await this.mysqlService.connect()
return this.mysqlService
} }
/** /**
@@ -64,25 +110,49 @@ export class BIPUsersDAO {
*/ */
async authenticate(username: string, password: string): Promise<UserInfo | null> { async authenticate(username: string, password: string): Promise<UserInfo | null> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` if (this.dbType === 'sqlserver') {
SELECT ID, UserName, UserType const sql = `
FROM ${BIP_USERS_CONFIG.TABLE_NAME_MYSQL} SELECT ID, UserName, UserType
WHERE UserName = ? AND Password = ? FROM ${tableName}
` WHERE UserName = @username AND Password = @password
`
const result = await mysqlService.query(sql, [username, password]) const result = await (dbService as SqlServerService).queryWithParams(sql, {
username: { value: username, type: sql.NVarChar },
password: { value: password, type: sql.NVarChar }
})
if (result.rows.length > 0) { if (result.rows.length > 0) {
const row = result.rows[0] const row = result.rows[0]
return { return {
id: row.ID as number, id: row.ID as number,
username: row.UserName as string, username: row.UserName as string,
userType: row.UserType as 'Admin' | 'User' | 'Guest' userType: row.UserType as 'Admin' | 'User' | 'Guest'
}
} }
return null
} else {
const sql = `
SELECT ID, UserName, UserType
FROM ${tableName}
WHERE UserName = ? AND Password = ?
`
const result = await (dbService as MySqlService).query(sql, [username, password])
if (result.rows.length > 0) {
const row = result.rows[0]
return {
id: row.ID as number,
username: row.UserName as string,
userType: row.UserType as 'Admin' | 'User' | 'Guest'
}
}
return null
} }
return null
} catch (error) { } catch (error) {
console.error('[BIPUsersDAO] Authenticate error:', error) console.error('[BIPUsersDAO] Authenticate error:', error)
return null return null
@@ -96,25 +166,48 @@ export class BIPUsersDAO {
*/ */
async authenticateByComputerName(computerName: string): Promise<UserInfo | null> { async authenticateByComputerName(computerName: string): Promise<UserInfo | null> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` if (this.dbType === 'sqlserver') {
SELECT ID, UserName, UserType const sql = `
FROM ${BIP_USERS_CONFIG.TABLE_NAME_MYSQL} SELECT ID, UserName, UserType
WHERE ComputerNmae = ? FROM ${tableName}
` WHERE ComputerNmae = @computerName
`
const result = await mysqlService.query(sql, [computerName]) const result = await (dbService as SqlServerService).queryWithParams(sql, {
computerName: { value: computerName, type: sql.NVarChar }
})
if (result.rows.length > 0) { if (result.rows.length > 0) {
const row = result.rows[0] const row = result.rows[0]
return { return {
id: row.ID as number, id: row.ID as number,
username: row.UserName as string, username: row.UserName as string,
userType: row.UserType as 'Admin' | 'User' | 'Guest' userType: row.UserType as 'Admin' | 'User' | 'Guest'
}
} }
return null
} else {
const sql = `
SELECT ID, UserName, UserType
FROM ${tableName}
WHERE ComputerNmae = ?
`
const result = await (dbService as MySqlService).query(sql, [computerName])
if (result.rows.length > 0) {
const row = result.rows[0]
return {
id: row.ID as number,
username: row.UserName as string,
userType: row.UserType as 'Admin' | 'User' | 'Guest'
}
}
return null
} }
return null
} catch (error) { } catch (error) {
console.error('[BIPUsersDAO] Authenticate by computer name error:', error) console.error('[BIPUsersDAO] Authenticate by computer name error:', error)
return null return null
@@ -127,15 +220,18 @@ export class BIPUsersDAO {
*/ */
async getAllUsers(): Promise<UserInfo[]> { async getAllUsers(): Promise<UserInfo[]> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` const sql = `
SELECT ID, UserName, UserType, CreateTime SELECT ID, UserName, UserType, CreateTime
FROM ${BIP_USERS_CONFIG.TABLE_NAME_MYSQL} FROM ${tableName}
ORDER BY UserName ORDER BY UserName
` `
const result = await mysqlService.query(sql) const result = this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sql)
: await (dbService as MySqlService).query(sql)
return result.rows.map(row => ({ return result.rows.map(row => ({
id: row.ID as number, id: row.ID as number,
@@ -164,29 +260,63 @@ export class BIPUsersDAO {
computerName: string = '' computerName: string = ''
): Promise<boolean> { ): Promise<boolean> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
let sql: string if (this.dbType === 'sqlserver') {
let params: any[] let sql: string
let params: Record<string, { value: unknown; type?: sql.ISqlType }>
if (computerName) { if (computerName) {
sql = ` sql = `
INSERT INTO ${BIP_USERS_CONFIG.TABLE_NAME_MYSQL} INSERT INTO ${tableName}
(UserName, Password, UserType, ComputerNmae) (UserName, Password, UserType, ComputerNmae)
VALUES (?, ?, ?, ?) VALUES (@username, @password, @userType, @computerName)
` `
params = [username, password, userType, computerName] params = {
username: { value: username, type: sql.NVarChar },
password: { value: password, type: sql.NVarChar },
userType: { value: userType, type: sql.NVarChar },
computerName: { value: computerName, type: sql.NVarChar }
}
} else {
sql = `
INSERT INTO ${tableName}
(UserName, Password, UserType)
VALUES (@username, @password, @userType)
`
params = {
username: { value: username, type: sql.NVarChar },
password: { value: password, type: sql.NVarChar },
userType: { value: userType, type: sql.NVarChar }
}
}
await (dbService as SqlServerService).queryWithParams(sql, params)
return true
} else { } else {
sql = ` let sql: string
INSERT INTO ${BIP_USERS_CONFIG.TABLE_NAME_MYSQL} let params: any[]
(UserName, Password, UserType)
VALUES (?, ?, ?)
`
params = [username, password, userType]
}
await mysqlService.query(sql, params) if (computerName) {
return true sql = `
INSERT INTO ${tableName}
(UserName, Password, UserType, ComputerNmae)
VALUES (?, ?, ?, ?)
`
params = [username, password, userType, computerName]
} else {
sql = `
INSERT INTO ${tableName}
(UserName, Password, UserType)
VALUES (?, ?, ?)
`
params = [username, password, userType]
}
await (dbService as MySqlService).query(sql, params)
return true
}
} catch (error) { } catch (error) {
console.error('[BIPUsersDAO] Create user error:', error) console.error('[BIPUsersDAO] Create user error:', error)
return false return false
@@ -201,16 +331,31 @@ export class BIPUsersDAO {
*/ */
async updateUserType(username: string, userType: string): Promise<boolean> { async updateUserType(username: string, userType: string): Promise<boolean> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` if (this.dbType === 'sqlserver') {
UPDATE ${BIP_USERS_CONFIG.TABLE_NAME_MYSQL} const sql = `
SET UserType = ? UPDATE ${tableName}
WHERE UserName = ? SET UserType = @userType
` WHERE UserName = @username
`
await mysqlService.query(sql, [userType, username]) await (dbService as SqlServerService).queryWithParams(sql, {
return true username: { value: username, type: sql.NVarChar },
userType: { value: userType, type: sql.NVarChar }
})
return true
} else {
const sql = `
UPDATE ${tableName}
SET UserType = ?
WHERE UserName = ?
`
await (dbService as MySqlService).query(sql, [userType, username])
return true
}
} catch (error) { } catch (error) {
console.error('[BIPUsersDAO] Update user type error:', error) console.error('[BIPUsersDAO] Update user type error:', error)
return false return false
@@ -225,16 +370,31 @@ export class BIPUsersDAO {
*/ */
async updatePassword(username: string, newPassword: string): Promise<boolean> { async updatePassword(username: string, newPassword: string): Promise<boolean> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` if (this.dbType === 'sqlserver') {
UPDATE ${BIP_USERS_CONFIG.TABLE_NAME_MYSQL} const sql = `
SET Password = ? UPDATE ${tableName}
WHERE UserName = ? SET Password = @newPassword
` WHERE UserName = @username
`
await mysqlService.query(sql, [newPassword, username]) await (dbService as SqlServerService).queryWithParams(sql, {
return true username: { value: username, type: sql.NVarChar },
newPassword: { value: newPassword, type: sql.NVarChar }
})
return true
} else {
const sql = `
UPDATE ${tableName}
SET Password = ?
WHERE UserName = ?
`
await (dbService as MySqlService).query(sql, [newPassword, username])
return true
}
} catch (error) { } catch (error) {
console.error('[BIPUsersDAO] Update password error:', error) console.error('[BIPUsersDAO] Update password error:', error)
return false return false
@@ -248,15 +408,28 @@ export class BIPUsersDAO {
*/ */
async deleteUser(username: string): Promise<boolean> { async deleteUser(username: string): Promise<boolean> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` if (this.dbType === 'sqlserver') {
DELETE FROM ${BIP_USERS_CONFIG.TABLE_NAME_MYSQL} const sql = `
WHERE UserName = ? DELETE FROM ${tableName}
` WHERE UserName = @username
`
await mysqlService.query(sql, [username]) await (dbService as SqlServerService).queryWithParams(sql, {
return true username: { value: username, type: sql.NVarChar }
})
return true
} else {
const sql = `
DELETE FROM ${tableName}
WHERE UserName = ?
`
await (dbService as MySqlService).query(sql, [username])
return true
}
} catch (error) { } catch (error) {
console.error('[BIPUsersDAO] Delete user error:', error) console.error('[BIPUsersDAO] Delete user error:', error)
return false return false
@@ -270,16 +443,30 @@ export class BIPUsersDAO {
*/ */
async userExists(username: string): Promise<boolean> { async userExists(username: string): Promise<boolean> {
try { try {
const mysqlService = await this.getMySqlService() const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const sql = ` if (this.dbType === 'sqlserver') {
SELECT COUNT(*) as count const sql = `
FROM ${BIP_USERS_CONFIG.TABLE_NAME_MYSQL} SELECT COUNT(*) as count
WHERE UserName = ? FROM ${tableName}
` WHERE UserName = @username
`
const result = await mysqlService.query(sql, [username]) const result = await (dbService as SqlServerService).queryWithParams(sql, {
return result.rows.length > 0 && (result.rows[0].count as number) > 0 username: { value: username, type: sql.NVarChar }
})
return result.rows.length > 0 && (result.rows[0].count as number) > 0
} else {
const sql = `
SELECT COUNT(*) as count
FROM ${tableName}
WHERE UserName = ?
`
const result = await (dbService as MySqlService).query(sql, [username])
return result.rows.length > 0 && (result.rows[0].count as number) > 0
}
} catch (error) { } catch (error) {
console.error('[BIPUsersDAO] User exists error:', error) console.error('[BIPUsersDAO] User exists error:', error)
return false return false
@@ -294,5 +481,9 @@ export class BIPUsersDAO {
await this.mysqlService.disconnect() await this.mysqlService.disconnect()
this.mysqlService = null this.mysqlService = null
} }
if (this.sqlServerService) {
await this.sqlServerService.disconnect()
this.sqlServerService = null
}
} }
} }