refactor: abstract database layer to support MySQL and SQL Server

- Add IDatabaseService interface with unified query(transaction) methods
- Create DatabaseFactory for centralized database service creation
- Modify MySqlService and SqlServerService to implement IDatabaseService
- Unify SqlServerService.query() to accept array params (internally converts to @p0, @p1...)
- Refactor OrderNumberResolver to use IDatabaseService
- Refactor DiscreteMaterialPlanDAO to use DatabaseFactory
- Refactor MaterialsToBeDeletedDAO to use DatabaseFactory
- Update IPC handlers to use DatabaseFactory.create()
- Add database.types.ts with shared type definitions

This enables switching between MySQL and SQL Server via DB_TYPE env variable.

Co-Authored-By: Claude (glm-5) <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-03-03 20:18:20 +08:00
parent 6698d82d6b
commit 0040ca7521
10 changed files with 624 additions and 702 deletions

View File

@@ -8,9 +8,7 @@
* - Statistics gathering
*/
import { MySqlService } from './mysql'
import { SqlServerService } from './sql-server'
import sql from 'mssql'
import { create, type IDatabaseService } from './index'
import { createLogger } from '../logger'
const log = createLogger('DiscreteMaterialPlanDAO')
@@ -95,70 +93,37 @@ export const DISCRETE_MATERIAL_PLAN_CONFIG = {
* DiscreteMaterialPlanDAO Class
*/
export class DiscreteMaterialPlanDAO {
private mysqlService: MySqlService | null = null
private sqlServerService: SqlServerService | null = null
private dbType: 'mysql' | 'sqlserver' = 'mysql'
/**
* Constructor - determine database type from environment
*/
constructor() {
const dbType = process.env.DB_TYPE?.toLowerCase()
if (dbType === 'sqlserver' || dbType === 'mssql') {
this.dbType = 'sqlserver'
} else {
this.dbType = 'mysql'
}
}
private dbService: IDatabaseService | null = null
/**
* Get the appropriate table name based on database type
*/
private getTableName(): string {
return this.dbType === 'sqlserver'
const isSqlServer = this.dbService?.type === 'sqlserver'
return isSqlServer
? DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_SQLSERVER
: DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL
}
/**
* Get database service instance (MySQL or SQL Server)
* Get database service instance using DatabaseFactory
*/
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
private async getDatabaseService(): Promise<IDatabaseService> {
if (this.dbService && this.dbService.isConnected()) {
return this.dbService
}
this.dbService = await create()
return this.dbService
}
/**
* Build placeholders for IN clause based on database type
*/
private buildPlaceholders(count: number, isSqlServer: boolean): string {
return isSqlServer
? Array.from({ length: count }, (_, idx) => `@p${idx}`).join(',')
: Array.from({ length: count }, () => '?').join(',')
}
// ==================== QUERY ALL ====================
@@ -173,11 +138,7 @@ export class DiscreteMaterialPlanDAO {
const tableName = this.getTableName()
const sqlString = `SELECT * FROM ${tableName}`
const result =
this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sqlString)
: await (dbService as MySqlService).query(sqlString)
const result = await dbService.query(sqlString)
return result.rows
} catch (error) {
@@ -221,11 +182,7 @@ export class DiscreteMaterialPlanDAO {
WHERE rn = 1
`
const result =
this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sqlString)
: await (dbService as MySqlService).query(sqlString)
const result = await dbService.query(sqlString)
return result.rows
} catch (error) {
log.error('Query all distinct by material code error', {
@@ -250,40 +207,22 @@ export class DiscreteMaterialPlanDAO {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
const batchSize = 2000
const allResults: any[] = []
for (let i = 0; i < sourceNumbers.length; i += batchSize) {
const batch = sourceNumbers.slice(i, i + batchSize)
const placeholders = this.buildPlaceholders(batch.length, isSqlServer)
if (this.dbType === 'sqlserver') {
const placeholders = batch.map((_, idx) => `@p${idx}`).join(',')
const params: Record<string, { value: string; type: sql.ISqlType }> = {}
const sqlString = `
SELECT *
FROM ${tableName}
WHERE SourceNumber IN (${placeholders})
`
batch.forEach((num, idx) => {
params[`p${idx}`] = { value: num, type: sql.NVarChar }
})
const sqlString = `
SELECT *
FROM ${tableName}
WHERE SourceNumber IN (${placeholders})
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, params)
allResults.push(...result.rows)
} else {
const placeholders = batch.map(() => '?').join(',')
const sqlString = `
SELECT *
FROM ${tableName}
WHERE SourceNumber IN (${placeholders})
`
const result = await (dbService as MySqlService).query(sqlString, batch)
allResults.push(...result.rows)
}
const result = await dbService.query(sqlString, batch)
allResults.push(...result.rows)
}
return allResults
@@ -310,74 +249,39 @@ export class DiscreteMaterialPlanDAO {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
const batchSize = 2000
const allResults: any[] = []
for (let i = 0; i < sourceNumbers.length; i += batchSize) {
const batch = sourceNumbers.slice(i, i + batchSize)
const placeholders = this.buildPlaceholders(batch.length, isSqlServer)
if (this.dbType === 'sqlserver') {
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 sqlString = `
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
)
const sqlString = `
WITH RankedRecords AS (
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
`
*,
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 SqlServerService).queryWithParams(sqlString, params)
allResults.push(...result.rows)
} else {
const placeholders = batch.map(() => '?').join(',')
const sqlString = `
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(sqlString, batch)
allResults.push(...result.rows)
}
const result = await dbService.query(sqlString, batch)
allResults.push(...result.rows)
}
return allResults
@@ -398,30 +302,17 @@ export class DiscreteMaterialPlanDAO {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
if (this.dbType === 'sqlserver') {
const sqlString = `
SELECT *
FROM ${tableName}
WHERE SourceNumber = @sourceNumber
`
const placeholder = isSqlServer ? '@p0' : '?'
const sqlString = `
SELECT *
FROM ${tableName}
WHERE SourceNumber = ${placeholder}
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
sourceNumber: { value: sourceNumber, type: sql.NVarChar }
})
return result.rows
} else {
const sqlString = `
SELECT *
FROM ${tableName}
WHERE SourceNumber = ?
`
const result = await (dbService as MySqlService).query(sqlString, [sourceNumber])
return result.rows
}
const result = await dbService.query(sqlString, [sourceNumber])
return result.rows
} catch (error) {
log.error('Query by source number error', {
error: error instanceof Error ? error.message : String(error)
@@ -441,30 +332,17 @@ export class DiscreteMaterialPlanDAO {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
if (this.dbType === 'sqlserver') {
const sqlString = `
SELECT *
FROM ${tableName}
WHERE PlanNumber = @planNumber
`
const placeholder = isSqlServer ? '@p0' : '?'
const sqlString = `
SELECT *
FROM ${tableName}
WHERE PlanNumber = ${placeholder}
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
planNumber: { value: planNumber, type: sql.NVarChar }
})
return result.rows
} else {
const sqlString = `
SELECT *
FROM ${tableName}
WHERE PlanNumber = ?
`
const result = await (dbService as MySqlService).query(sqlString, [planNumber])
return result.rows
}
const result = await dbService.query(sqlString, [planNumber])
return result.rows
} catch (error) {
log.error('Query by plan number error', {
error: error instanceof Error ? error.message : String(error)
@@ -486,35 +364,17 @@ export class DiscreteMaterialPlanDAO {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
const placeholders = this.buildPlaceholders(planNumbers.length, isSqlServer)
if (this.dbType === 'sqlserver') {
const placeholders = planNumbers.map((_, idx) => `@p${idx}`).join(',')
const params: Record<string, { value: string; type: sql.ISqlType }> = {}
const sqlString = `
SELECT *
FROM ${tableName}
WHERE PlanNumber IN (${placeholders})
`
planNumbers.forEach((num, idx) => {
params[`p${idx}`] = { value: num, type: sql.NVarChar }
})
const sqlString = `
SELECT *
FROM ${tableName}
WHERE PlanNumber IN (${placeholders})
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, params)
return result.rows
} else {
const placeholders = planNumbers.map(() => '?').join(',')
const sqlString = `
SELECT *
FROM ${tableName}
WHERE PlanNumber IN (${placeholders})
`
const result = await (dbService as MySqlService).query(sqlString, planNumbers)
return result.rows
}
const result = await dbService.query(sqlString, planNumbers)
return result.rows
} catch (error) {
log.error('Query by plan numbers error', {
error: error instanceof Error ? error.message : String(error)
@@ -535,11 +395,7 @@ export class DiscreteMaterialPlanDAO {
const tableName = this.getTableName()
const sqlString = `SELECT COUNT(*) as count FROM ${tableName}`
const result =
this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sqlString)
: await (dbService as MySqlService).query(sqlString)
const result = await dbService.query(sqlString)
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
} catch (error) {
@@ -559,30 +415,17 @@ export class DiscreteMaterialPlanDAO {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
if (this.dbType === 'sqlserver') {
const sqlString = `
SELECT COUNT(*) as count
FROM ${tableName}
WHERE PlanNumber = @planNumber
`
const placeholder = isSqlServer ? '@p0' : '?'
const sqlString = `
SELECT COUNT(*) as count
FROM ${tableName}
WHERE PlanNumber = ${placeholder}
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
planNumber: { value: planNumber, type: sql.NVarChar }
})
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
} else {
const sqlString = `
SELECT COUNT(*) as count
FROM ${tableName}
WHERE PlanNumber = ?
`
const result = await (dbService as MySqlService).query(sqlString, [planNumber])
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
}
const result = await dbService.query(sqlString, [planNumber])
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
} catch (error) {
log.error('Count by plan number error', {
error: error instanceof Error ? error.message : String(error)
@@ -600,38 +443,20 @@ export class DiscreteMaterialPlanDAO {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
if (sourceNumbers && sourceNumbers.length > 0) {
if (this.dbType === 'sqlserver') {
const placeholders = sourceNumbers.map((_, idx) => `@p${idx}`).join(',')
const params: Record<string, { value: string; type: sql.ISqlType }> = {}
const placeholders = this.buildPlaceholders(sourceNumbers.length, isSqlServer)
sourceNumbers.forEach((num, idx) => {
params[`p${idx}`] = { value: num, type: sql.NVarChar }
})
const sqlString = `
SELECT DISTINCT MaterialName
FROM ${tableName}
WHERE SourceNumber IN (${placeholders})
AND MaterialName IS NOT NULL
`
const sqlString = `
SELECT DISTINCT MaterialName
FROM ${tableName}
WHERE SourceNumber IN (${placeholders})
AND MaterialName IS NOT NULL
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, params)
return result.rows.map((row) => row.MaterialName as string).filter(Boolean)
} else {
const placeholders = sourceNumbers.map(() => '?').join(',')
const sqlString = `
SELECT DISTINCT MaterialName
FROM ${tableName}
WHERE SourceNumber IN (${placeholders})
AND MaterialName IS NOT NULL
`
const result = await (dbService as MySqlService).query(sqlString, sourceNumbers)
return result.rows.map((row) => row.MaterialName as string).filter(Boolean)
}
const result = await dbService.query(sqlString, sourceNumbers)
return result.rows.map((row) => row.MaterialName as string).filter(Boolean)
} else {
const sqlString = `
SELECT DISTINCT MaterialName
@@ -639,11 +464,7 @@ export class DiscreteMaterialPlanDAO {
WHERE MaterialName IS NOT NULL
`
const result =
this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sqlString)
: await (dbService as MySqlService).query(sqlString)
const result = await dbService.query(sqlString)
return result.rows.map((row) => row.MaterialName as string).filter(Boolean)
}
} catch (error) {
@@ -673,11 +494,7 @@ export class DiscreteMaterialPlanDAO {
FROM ${tableName}
`
const result =
this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sqlString)
: await (dbService as MySqlService).query(sqlString)
const result = await dbService.query(sqlString)
return result.rows.length > 0 ? result.rows[0] : {}
} catch (error) {
log.error('Get statistics error', {
@@ -691,13 +508,9 @@ export class DiscreteMaterialPlanDAO {
* Disconnect from database
*/
async disconnect(): Promise<void> {
if (this.mysqlService) {
await this.mysqlService.disconnect()
this.mysqlService = null
}
if (this.sqlServerService) {
await this.sqlServerService.disconnect()
this.sqlServerService = null
if (this.dbService) {
await this.dbService.disconnect()
this.dbService = null
}
}
}

View File

@@ -0,0 +1,182 @@
/**
* Database Factory
*
* Creates and manages database service instances based on configuration.
* Supports both MySQL and SQL Server databases.
*/
import { MySqlService } from './mysql'
import { SqlServerService } from './sql-server'
import type {
IDatabaseService,
DatabaseType,
MySqlConfig,
SqlServerConfig
} from '../../types/database.types'
import { createLogger } from '../logger'
const log = createLogger('DatabaseFactory')
/**
* Cached database service instances
*/
const instances: Map<DatabaseType, IDatabaseService> = new Map()
/**
* Get the current database type from environment
*/
export function getDatabaseType(): DatabaseType {
const dbType = process.env.DB_TYPE?.toLowerCase()
if (dbType === 'sqlserver' || dbType === 'mssql') {
return 'sqlserver'
}
return 'mysql'
}
/**
* Create MySQL configuration from environment variables
*/
export function createMySqlConfig(): MySqlConfig {
return {
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 || ''
}
}
/**
* Create SQL Server configuration from environment variables
*/
export function createSqlServerConfig(): SqlServerConfig {
return {
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'
}
}
}
/**
* Create a database service instance
*
* Uses singleton pattern - returns cached instance if available.
*
* @param type - Optional database type override (defaults to DB_TYPE env var)
* @returns Database service instance
*/
export async function create(type?: DatabaseType): Promise<IDatabaseService> {
const dbType = type || getDatabaseType()
// Return cached instance if available and connected
const cached = instances.get(dbType)
if (cached && cached.isConnected()) {
log.debug('Returning cached database instance', { type: dbType })
return cached
}
// Create new instance
let service: IDatabaseService
if (dbType === 'sqlserver') {
log.info('Creating SQL Server database service')
service = new SqlServerService(createSqlServerConfig())
} else {
log.info('Creating MySQL database service')
service = new MySqlService(createMySqlConfig())
}
// Connect to database
await service.connect()
log.info('Database connected', { type: dbType })
// Cache the instance
instances.set(dbType, service)
return service
}
/**
* Get existing database service without creating new one
*
* @param type - Optional database type (defaults to DB_TYPE env var)
* @returns Database service instance or undefined
*/
export function get(type?: DatabaseType): IDatabaseService | undefined {
const dbType = type || getDatabaseType()
return instances.get(dbType)
}
/**
* Disconnect and remove a specific database service
*
* @param type - Optional database type (defaults to DB_TYPE env var)
*/
export async function disconnect(type?: DatabaseType): Promise<void> {
const dbType = type || getDatabaseType()
const service = instances.get(dbType)
if (service) {
try {
await service.disconnect()
log.info('Database disconnected', { type: dbType })
} catch (error) {
log.warn('Error disconnecting database', {
type: dbType,
error: error instanceof Error ? error.message : String(error)
})
}
instances.delete(dbType)
}
}
/**
* Disconnect all database services
*/
export async function disconnectAll(): Promise<void> {
log.info('Disconnecting all database services')
const disconnectPromises = Array.from(instances.entries()).map(async ([type, service]) => {
try {
await service.disconnect()
log.debug('Database disconnected', { type })
} catch (error) {
log.warn('Error disconnecting database', {
type,
error: error instanceof Error ? error.message : String(error)
})
}
})
await Promise.all(disconnectPromises)
instances.clear()
log.info('All database services disconnected')
}
/**
* Check if a database service is connected
*
* @param type - Optional database type (defaults to DB_TYPE env var)
*/
export function isConnected(type?: DatabaseType): boolean {
const dbType = type || getDatabaseType()
const service = instances.get(dbType)
return service?.isConnected() ?? false
}
// Re-export types and services
export { MySqlService } from './mysql'
export { SqlServerService } from './sql-server'
export type {
IDatabaseService,
DatabaseType,
QueryResult,
MySqlConfig,
SqlServerConfig
} from '../../types/database.types'

View File

@@ -8,9 +8,7 @@
* - Statistics gathering
*/
import { MySqlService } from './mysql'
import { SqlServerService } from './sql-server'
import sql from 'mssql'
import { create, type IDatabaseService } from './index'
import { createLogger } from '../logger'
const log = createLogger('MaterialsToBeDeletedDAO')
@@ -59,70 +57,37 @@ export const MATERIALS_TO_BE_DELETED_CONFIG = {
* MaterialsToBeDeleted DAO Class
*/
export class MaterialsToBeDeletedDAO {
private mysqlService: MySqlService | null = null
private sqlServerService: SqlServerService | null = null
private dbType: 'mysql' | 'sqlserver' = 'mysql'
/**
* Constructor - determine database type from environment
*/
constructor() {
const dbType = process.env.DB_TYPE?.toLowerCase()
if (dbType === 'sqlserver' || dbType === 'mssql') {
this.dbType = 'sqlserver'
} else {
this.dbType = 'mysql'
}
}
private dbService: IDatabaseService | null = null
/**
* Get the appropriate table name based on database type
*/
private getTableName(): string {
return this.dbType === 'sqlserver'
const isSqlServer = this.dbService?.type === 'sqlserver'
return isSqlServer
? MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_SQLSERVER
: MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL
}
/**
* Get database service instance (MySQL or SQL Server)
* Get database service instance using DatabaseFactory
*/
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
private async getDatabaseService(): Promise<IDatabaseService> {
if (this.dbService && this.dbService.isConnected()) {
return this.dbService
}
this.dbService = await create()
return this.dbService
}
/**
* Build placeholders for IN clause based on database type
*/
private buildPlaceholders(count: number, isSqlServer: boolean): string {
return isSqlServer
? Array.from({ length: count }, (_, idx) => `@p${idx}`).join(',')
: Array.from({ length: count }, () => '?').join(',')
}
// ==================== UPSERT (MERGE) ====================
@@ -144,28 +109,28 @@ export class MaterialsToBeDeletedDAO {
const tableName = this.getTableName()
const code = materialCode.trim()
const manager = managerName?.trim() || null
const isSqlServer = dbService.type === 'sqlserver'
if (this.dbType === 'sqlserver') {
if (isSqlServer) {
// SQL Server MERGE statement
const sqlString = `
MERGE ${tableName} AS target
USING (VALUES (@materialCode, @managerName)) AS source (MaterialCode, ManagerName)
USING (VALUES (@p0, @p1)) 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(sqlString, {
materialCode: { value: code, type: sql.NVarChar },
managerName: { value: manager, type: sql.NVarChar }
})
await dbService.query(sqlString, [code, manager])
} else {
// MySQL ON DUPLICATE KEY UPDATE
const sqlString = `
INSERT INTO ${tableName} (MaterialCode, ManagerName)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE ManagerName = VALUES(ManagerName)
`
await (dbService as MySqlService).query(sqlString, [code, manager])
await dbService.query(sqlString, [code, manager])
}
return true
@@ -198,6 +163,7 @@ export class MaterialsToBeDeletedDAO {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
for (const material of materials) {
const materialCode = material.materialCode?.trim()
@@ -209,27 +175,26 @@ export class MaterialsToBeDeletedDAO {
}
try {
if (this.dbType === 'sqlserver') {
if (isSqlServer) {
// SQL Server MERGE statement
const sqlString = `
MERGE ${tableName} AS target
USING (VALUES (@materialCode, @managerName)) AS source (MaterialCode, ManagerName)
USING (VALUES (@p0, @p1)) 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(sqlString, {
materialCode: { value: materialCode, type: sql.NVarChar },
managerName: { value: managerName || null, type: sql.NVarChar }
})
await dbService.query(sqlString, [materialCode, managerName || null])
} else {
// MySQL ON DUPLICATE KEY UPDATE
const sqlString = `
INSERT INTO ${tableName} (MaterialCode, ManagerName)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE ManagerName = VALUES(ManagerName)
`
await (dbService as MySqlService).query(sqlString, [materialCode, managerName || null])
await dbService.query(sqlString, [materialCode, managerName || null])
}
stats.success++
@@ -268,11 +233,7 @@ export class MaterialsToBeDeletedDAO {
WHERE MaterialCode IS NOT NULL
`
const result =
this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sqlString)
: await (dbService as MySqlService).query(sqlString)
const result = await dbService.query(sqlString)
return new Set(result.rows.map((row) => row.MaterialCode as string).filter(Boolean))
} catch (error) {
log.error('Get all material codes error', {
@@ -298,11 +259,7 @@ export class MaterialsToBeDeletedDAO {
ORDER BY ManagerName, MaterialCode
`
const result =
this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sqlString)
: await (dbService as MySqlService).query(sqlString)
const result = await dbService.query(sqlString)
return result.rows.map((row) => ({
id: row.ID as number,
materialCode: row.MaterialCode as string,
@@ -325,40 +282,22 @@ export class MaterialsToBeDeletedDAO {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
if (this.dbType === 'sqlserver') {
const sqlString = `
SELECT ID, MaterialCode, ManagerName
FROM ${tableName}
WHERE ManagerName = @managerName AND MaterialCode IS NOT NULL
ORDER BY MaterialCode
`
const placeholder = isSqlServer ? '@p0' : '?'
const sqlString = `
SELECT ID, MaterialCode, ManagerName
FROM ${tableName}
WHERE ManagerName = ${placeholder} AND MaterialCode IS NOT NULL
ORDER BY MaterialCode
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
managerName: { value: managerName, type: sql.NVarChar }
})
return result.rows.map((row) => ({
id: row.ID as number,
materialCode: row.MaterialCode as string,
managerName: row.ManagerName as string
}))
} else {
const sqlString = `
SELECT ID, MaterialCode, ManagerName
FROM ${tableName}
WHERE ManagerName = ? AND MaterialCode IS NOT NULL
ORDER BY MaterialCode
`
const result = await (dbService as MySqlService).query(sqlString, [managerName])
return result.rows.map((row) => ({
id: row.ID as number,
materialCode: row.MaterialCode as string,
managerName: row.ManagerName as string
}))
}
const result = await dbService.query(sqlString, [managerName])
return result.rows.map((row) => ({
id: row.ID as number,
materialCode: row.MaterialCode as string,
managerName: row.ManagerName as string
}))
} catch (error) {
log.error('Get materials by manager error', {
error: error instanceof Error ? error.message : String(error)
@@ -383,11 +322,7 @@ export class MaterialsToBeDeletedDAO {
ORDER BY ManagerName
`
const result =
this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sqlString)
: await (dbService as MySqlService).query(sqlString)
const result = await dbService.query(sqlString)
return result.rows.map((row) => row.ManagerName as string).filter(Boolean)
} catch (error) {
log.error('Get managers error', {
@@ -407,47 +342,26 @@ export class MaterialsToBeDeletedDAO {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const code = materialCode.trim()
const isSqlServer = dbService.type === 'sqlserver'
if (this.dbType === 'sqlserver') {
const sqlString = `
SELECT ID, MaterialCode, ManagerName
FROM ${tableName}
WHERE MaterialCode = @materialCode
`
const placeholder = isSqlServer ? '@p0' : '?'
const sqlString = `
SELECT ID, MaterialCode, ManagerName
FROM ${tableName}
WHERE MaterialCode = ${placeholder}
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
materialCode: { value: code, type: sql.NVarChar }
})
const result = await dbService.query(sqlString, [code])
if (result.rows.length === 0) {
return null
}
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
}
} else {
const sqlString = `
SELECT ID, MaterialCode, ManagerName
FROM ${tableName}
WHERE MaterialCode = ?
`
const result = await (dbService as MySqlService).query(sqlString, [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
}
const row = result.rows[0]
return {
id: row.ID as number,
materialCode: row.MaterialCode as string,
managerName: row.ManagerName as string
}
} catch (error) {
log.error('Get record by material code error', {
@@ -469,28 +383,16 @@ export class MaterialsToBeDeletedDAO {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const code = materialCode.trim()
const isSqlServer = dbService.type === 'sqlserver'
if (this.dbType === 'sqlserver') {
const sqlString = `
DELETE FROM ${tableName}
WHERE MaterialCode = @materialCode
`
const placeholder = isSqlServer ? '@p0' : '?'
const sqlString = `
DELETE FROM ${tableName}
WHERE MaterialCode = ${placeholder}
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
materialCode: { value: code, type: sql.NVarChar }
})
return result.rowCount > 0
} else {
const sqlString = `
DELETE FROM ${tableName}
WHERE MaterialCode = ?
`
const result = await (dbService as MySqlService).query(sqlString, [code])
return result.rowCount > 0
}
const result = await dbService.query(sqlString, [code])
return result.rowCount > 0
} catch (error) {
log.error('Delete by material code error', {
error: error instanceof Error ? error.message : String(error)
@@ -508,28 +410,16 @@ export class MaterialsToBeDeletedDAO {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
if (this.dbType === 'sqlserver') {
const sqlString = `
DELETE FROM ${tableName}
WHERE ManagerName = @managerName
`
const placeholder = isSqlServer ? '@p0' : '?'
const sqlString = `
DELETE FROM ${tableName}
WHERE ManagerName = ${placeholder}
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
managerName: { value: managerName, type: sql.NVarChar }
})
return result.rowCount
} else {
const sqlString = `
DELETE FROM ${tableName}
WHERE ManagerName = ?
`
const result = await (dbService as MySqlService).query(sqlString, [managerName])
return result.rowCount
}
const result = await dbService.query(sqlString, [managerName])
return result.rowCount
} catch (error) {
log.error('Delete by manager error', {
error: error instanceof Error ? error.message : String(error)
@@ -548,12 +438,7 @@ export class MaterialsToBeDeletedDAO {
const tableName = this.getTableName()
const sqlString = `DELETE FROM ${tableName}`
const result =
this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sqlString)
: await (dbService as MySqlService).query(sqlString)
const result = await dbService.query(sqlString)
return result.rowCount
} catch (error) {
log.error('Delete all materials error', {
@@ -579,36 +464,22 @@ export class MaterialsToBeDeletedDAO {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
for (let i = 0; i < materialCodes.length; i += batchSize) {
const batch = materialCodes.slice(i, i + batchSize)
const placeholders = this.buildPlaceholders(batch.length, isSqlServer)
if (this.dbType === 'sqlserver') {
const placeholders = batch.map((_, idx) => `@p${idx}`).join(',')
const params: Record<string, { value: string; type: sql.ISqlType }> = {}
const sqlString = `
DELETE FROM ${tableName}
WHERE MaterialCode IN (${placeholders})
`
batch.forEach((code, idx) => {
params[`p${idx}`] = { value: code.trim(), type: sql.NVarChar }
})
const sqlString = `
DELETE FROM ${tableName}
WHERE MaterialCode IN (${placeholders})
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, params)
totalDeleted += result.rowCount
} else {
const placeholders = batch.map(() => '?').join(',')
const sqlString = `
DELETE FROM ${tableName}
WHERE MaterialCode IN (${placeholders})
`
const result = await (dbService as MySqlService).query(sqlString, batch)
totalDeleted += result.rowCount
}
const result = await dbService.query(
sqlString,
batch.map((c) => c.trim())
)
totalDeleted += result.rowCount
}
} catch (error) {
log.error('Delete by material codes error', {
@@ -631,30 +502,17 @@ export class MaterialsToBeDeletedDAO {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const code = materialCode.trim()
const isSqlServer = dbService.type === 'sqlserver'
if (this.dbType === 'sqlserver') {
const sqlString = `
SELECT COUNT(*) as count
FROM ${tableName}
WHERE MaterialCode = @materialCode
`
const placeholder = isSqlServer ? '@p0' : '?'
const sqlString = `
SELECT COUNT(*) as count
FROM ${tableName}
WHERE MaterialCode = ${placeholder}
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
materialCode: { value: code, type: sql.NVarChar }
})
return result.rows.length > 0 && (result.rows[0].count as number) > 0
} else {
const sqlString = `
SELECT COUNT(*) as count
FROM ${tableName}
WHERE MaterialCode = ?
`
const result = await (dbService as MySqlService).query(sqlString, [code])
return result.rows.length > 0 && (result.rows[0].count as number) > 0
}
const result = await dbService.query(sqlString, [code])
return result.rows.length > 0 && (result.rows[0].count as number) > 0
} catch (error) {
log.error('Material exists error', {
error: error instanceof Error ? error.message : String(error)
@@ -673,11 +531,7 @@ export class MaterialsToBeDeletedDAO {
const tableName = this.getTableName()
const sqlString = `SELECT COUNT(*) as count FROM ${tableName}`
const result =
this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sqlString)
: await (dbService as MySqlService).query(sqlString)
const result = await dbService.query(sqlString)
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
} catch (error) {
@@ -697,30 +551,17 @@ export class MaterialsToBeDeletedDAO {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
if (this.dbType === 'sqlserver') {
const sqlString = `
SELECT COUNT(*) as count
FROM ${tableName}
WHERE ManagerName = @managerName
`
const placeholder = isSqlServer ? '@p0' : '?'
const sqlString = `
SELECT COUNT(*) as count
FROM ${tableName}
WHERE ManagerName = ${placeholder}
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
managerName: { value: managerName, type: sql.NVarChar }
})
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
} else {
const sqlString = `
SELECT COUNT(*) as count
FROM ${tableName}
WHERE ManagerName = ?
`
const result = await (dbService as MySqlService).query(sqlString, [managerName])
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
}
const result = await dbService.query(sqlString, [managerName])
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
} catch (error) {
log.error('Count by manager error', {
error: error instanceof Error ? error.message : String(error)
@@ -747,11 +588,7 @@ export class MaterialsToBeDeletedDAO {
WHERE MaterialCode IS NOT NULL
`
const statsResult =
this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(statsSql)
: await (dbService as MySqlService).query(statsSql)
const statsResult = await dbService.query(statsSql)
const stats = statsResult.rows[0] || {}
// Get materials per manager
@@ -763,11 +600,7 @@ export class MaterialsToBeDeletedDAO {
ORDER BY count DESC
`
const managerResult =
this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(managerSql)
: await (dbService as MySqlService).query(managerSql)
const managerResult = await dbService.query(managerSql)
const materialsPerManager = managerResult.rows.map((row) => ({
[row.ManagerName as string]: row.count as number
}))
@@ -793,13 +626,9 @@ export class MaterialsToBeDeletedDAO {
* Disconnect from database
*/
async disconnect(): Promise<void> {
if (this.mysqlService) {
await this.mysqlService.disconnect()
this.mysqlService = null
}
if (this.sqlServerService) {
await this.sqlServerService.disconnect()
this.sqlServerService = null
if (this.dbService) {
await this.dbService.disconnect()
this.dbService = null
}
}
}

View File

@@ -1,20 +1,17 @@
import mysql from 'mysql2/promise'
import type {
IDatabaseService,
DatabaseType,
QueryResult,
MySqlConfig
} from '../../types/database.types'
export interface MySqlConfig {
host: string
port: number
user: string
password: string
database: string
}
export type { MySqlConfig } from '../../types/database.types'
export interface MySqlQueryResult {
rows: Record<string, unknown>[]
columns: string[]
rowCount: number
}
export class MySqlService implements IDatabaseService {
/** Database type identifier */
readonly type: DatabaseType = 'mysql'
export class MySqlService {
private connection: mysql.Connection | null = null
private config: MySqlConfig
@@ -72,7 +69,7 @@ export class MySqlService {
/**
* Execute a query and return results
*/
async query(sql: string, params?: any[]): Promise<MySqlQueryResult> {
async query(sql: string, params?: any[]): Promise<QueryResult> {
if (!this.connection) {
throw new Error('Not connected to MySQL. Call connect() first.')
}

View File

@@ -1,24 +1,17 @@
import sql from 'mssql'
import type {
IDatabaseService,
DatabaseType,
QueryResult,
SqlServerConfig
} from '../../types/database.types'
export interface SqlServerConfig {
server: string
port: number
user: string
password: string
database: string
options?: {
encrypt?: boolean
trustServerCertificate?: boolean
}
}
export type { SqlServerConfig } from '../../types/database.types'
export interface SqlServerQueryResult {
rows: Record<string, unknown>[]
columns: string[]
rowCount: number
}
export class SqlServerService implements IDatabaseService {
/** Database type identifier */
readonly type: DatabaseType = 'sqlserver'
export class SqlServerService {
private pool: sql.ConnectionPool | null = null
private config: SqlServerConfig
@@ -79,8 +72,10 @@ export class SqlServerService {
/**
* Execute a query and return results
* @param sqlString - SQL query string with @p0, @p1, ... placeholders
* @param params - Query parameters as an array (converted to @p0, @p1, ...)
*/
async query(sqlString: string, params?: Record<string, unknown>): Promise<SqlServerQueryResult> {
async query(sqlString: string, params?: any[]): Promise<QueryResult> {
if (!this.pool) {
throw new Error('Not connected to SQL Server. Call connect() first.')
}
@@ -88,11 +83,11 @@ export class SqlServerService {
try {
const request = this.pool.request()
// Add parameters if provided
if (params) {
for (const [key, value] of Object.entries(params)) {
request.input(key, value)
}
// Add parameters if provided - convert array to @p0, @p1, ... format
if (params && params.length > 0) {
params.forEach((value, index) => {
request.input(`p${index}`, value)
})
}
const result = await request.query(sqlString)
@@ -113,12 +108,14 @@ export class SqlServerService {
}
/**
* Execute a prepared statement with parameters
* Execute a prepared statement with named parameters
* @param sqlString - SQL query string with @paramName placeholders
* @param params - Parameters as an object with { value, type? } structure
*/
async queryWithParams(
sqlString: string,
params: Record<string, { value: unknown; type?: sql.ISqlType }>
): Promise<SqlServerQueryResult> {
): Promise<QueryResult> {
if (!this.pool) {
throw new Error('Not connected to SQL Server. Call connect() first.')
}
@@ -154,8 +151,9 @@ export class SqlServerService {
/**
* Execute multiple queries in a transaction
* @param queries - Array of queries with array-based parameters
*/
async transaction(queries: { sql: string; params?: Record<string, unknown> }[]): Promise<void> {
async transaction(queries: { sql: string; params?: any[] }[]): Promise<void> {
if (!this.pool) {
throw new Error('Not connected to SQL Server. Call connect() first.')
}
@@ -168,10 +166,11 @@ export class SqlServerService {
for (const { sql: sqlString, params } of queries) {
const request = new sql.Request(transaction)
if (params) {
for (const [key, value] of Object.entries(params)) {
request.input(key, value)
}
// Add parameters if provided - convert array to @p0, @p1, ... format
if (params && params.length > 0) {
params.forEach((value, index) => {
request.input(`p${index}`, value)
})
}
await request.query(sqlString)

View File

@@ -2,7 +2,7 @@
* Order Number Resolver Service
*
* Automatically recognizes productionID and 生产订单号 (production order number),
* and converts them via MySQL database lookup.
* and converts them via database lookup.
*
* - productionID format: 2 digits + 1 letter + serial number (e.g., "22A1", "22A1234")
* - 生产订单号 format: SC + 14 digits (e.g., "SC70202602120085")
@@ -11,7 +11,7 @@
* Fields: 总排号 (productionID), 生产订单号 (production order number)
*/
import { MySqlService } from '../database/mysql'
import type { IDatabaseService } from '../database'
/**
* Order mapping result
@@ -71,10 +71,10 @@ export const DB_CONFIG = {
* Order Number Resolver Service
*/
export class OrderNumberResolver {
private mysqlService: MySqlService
private dbService: IDatabaseService
constructor(mysqlService: MySqlService) {
this.mysqlService = mysqlService
constructor(dbService: IDatabaseService) {
this.dbService = dbService
}
/**
@@ -202,7 +202,7 @@ export class OrderNumberResolver {
private async resolveProductionIds(productionIds: string[]): Promise<OrderMapping[]> {
const mappings: OrderMapping[] = []
if (!this.mysqlService.isConnected()) {
if (!this.dbService.isConnected()) {
// Database not connected, return all as failed
for (const pid of productionIds) {
mappings.push({
@@ -217,15 +217,21 @@ export class OrderNumberResolver {
}
try {
// Build query with placeholders
const placeholders = productionIds.map(() => '?').join(', ')
// Build query - use different placeholder style based on database type
const isSqlServer = this.dbService.type === 'sqlserver'
const placeholders = isSqlServer
? productionIds.map((_, idx) => `@p${idx}`).join(', ')
: productionIds.map(() => '?').join(', ')
// Use appropriate quoting for table/field names
const quote = isSqlServer ? '' : '`'
const query = `
SELECT \`${DB_CONFIG.FIELD_PRODUCTION_ID}\`, \`${DB_CONFIG.FIELD_ORDER_NUMBER}\`
FROM \`${DB_CONFIG.TABLE_NAME}\`
WHERE \`${DB_CONFIG.FIELD_PRODUCTION_ID}\` IN (${placeholders})
SELECT ${quote}${DB_CONFIG.FIELD_PRODUCTION_ID}${quote}, ${quote}${DB_CONFIG.FIELD_ORDER_NUMBER}${quote}
FROM ${quote}${DB_CONFIG.TABLE_NAME}${quote}
WHERE ${quote}${DB_CONFIG.FIELD_PRODUCTION_ID}${quote} IN (${placeholders})
`
const result = await this.mysqlService.query(query, productionIds)
const result = await this.dbService.query(query, productionIds)
// Create a map for quick lookup
const resultMap = new Map<string, string>()
@@ -281,19 +287,26 @@ export class OrderNumberResolver {
* @returns List of valid order numbers
*/
private async verifyOrderNumbers(orderNumbers: string[]): Promise<string[]> {
if (!this.mysqlService.isConnected()) {
if (!this.dbService.isConnected()) {
return orderNumbers // Skip verification if not connected
}
try {
const placeholders = orderNumbers.map(() => '?').join(', ')
// Build query - use different placeholder style based on database type
const isSqlServer = this.dbService.type === 'sqlserver'
const placeholders = isSqlServer
? orderNumbers.map((_, idx) => `@p${idx}`).join(', ')
: orderNumbers.map(() => '?').join(', ')
// Use appropriate quoting for table/field names
const quote = isSqlServer ? '' : '`'
const query = `
SELECT \`${DB_CONFIG.FIELD_ORDER_NUMBER}\`
FROM \`${DB_CONFIG.TABLE_NAME}\`
WHERE \`${DB_CONFIG.FIELD_ORDER_NUMBER}\` IN (${placeholders})
SELECT ${quote}${DB_CONFIG.FIELD_ORDER_NUMBER}${quote}
FROM ${quote}${DB_CONFIG.TABLE_NAME}${quote}
WHERE ${quote}${DB_CONFIG.FIELD_ORDER_NUMBER}${quote} IN (${placeholders})
`
const result = await this.mysqlService.query(query, orderNumbers)
const result = await this.dbService.query(query, orderNumbers)
return result.rows.map((row) => row[DB_CONFIG.FIELD_ORDER_NUMBER] as string)
} catch (error) {
console.warn('[OrderResolver] Failed to verify order numbers:', error)