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:
@@ -214,7 +214,18 @@ export function registerDatabaseHandlers(): void {
|
||||
}
|
||||
|
||||
log.debug('Executing SQL Server query', { windowId, sql: sqlString.substring(0, 100) })
|
||||
return await service.query(sqlString, params)
|
||||
|
||||
// Use queryWithParams for named parameters, or query for no params
|
||||
if (params && Object.keys(params).length > 0) {
|
||||
// Convert to the format expected by queryWithParams
|
||||
const typedParams: Record<string, { value: unknown }> = {}
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
typedParams[key] = { value }
|
||||
}
|
||||
return await service.queryWithParams(sqlString, typedParams)
|
||||
} else {
|
||||
return await service.query(sqlString)
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'SQL Server query failed'
|
||||
log.error('SQL Server query failed', { error: message })
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ipcMain } from 'electron'
|
||||
import { ErpAuthService } from '../services/erp/erp-auth'
|
||||
import { ExtractorService } from '../services/erp/extractor'
|
||||
import { OrderNumberResolver } from '../services/erp/order-resolver'
|
||||
import { MySqlService } from '../services/database/mysql'
|
||||
import { create, type IDatabaseService } from '../services/database'
|
||||
import { createLogger } from '../services/logger'
|
||||
import { withErrorHandling, type IpcResult } from './index'
|
||||
import { ErpConnectionError, ValidationError, DatabaseQueryError } from '../types/errors'
|
||||
@@ -19,7 +19,7 @@ export function registerExtractorHandlers(): void {
|
||||
async (_event, input: ExtractorInput): Promise<IpcResult<ExtractorResult>> => {
|
||||
return withErrorHandling(async () => {
|
||||
let authService: ErpAuthService | null = null
|
||||
let mysqlService: MySqlService | null = null
|
||||
let dbService: IDatabaseService | null = null
|
||||
|
||||
try {
|
||||
// Check environment variables
|
||||
@@ -39,29 +39,20 @@ export function registerExtractorHandlers(): void {
|
||||
)
|
||||
}
|
||||
|
||||
// Resolve order numbers (convert productionIDs to 生产订单号)
|
||||
const mysqlConfig = {
|
||||
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 || ''
|
||||
}
|
||||
|
||||
log.info('Connecting to MySQL for order resolution...')
|
||||
mysqlService = new MySqlService(mysqlConfig)
|
||||
|
||||
// Create database service using factory
|
||||
log.info('Connecting to database for order resolution...')
|
||||
try {
|
||||
await mysqlService.connect()
|
||||
dbService = await create()
|
||||
} catch (error) {
|
||||
throw new DatabaseQueryError(
|
||||
'MySQL 连接失败',
|
||||
'数据库连接失败',
|
||||
'DB_CONNECTION_FAILED',
|
||||
error instanceof Error ? error : undefined
|
||||
)
|
||||
}
|
||||
|
||||
const resolver = new OrderNumberResolver(mysqlService)
|
||||
// Resolve order numbers (convert productionIDs to 生产订单号)
|
||||
const resolver = new OrderNumberResolver(dbService)
|
||||
const mappings = await resolver.resolve(input.orderNumbers)
|
||||
|
||||
// Get valid order numbers and warnings
|
||||
@@ -136,13 +127,13 @@ export function registerExtractorHandlers(): void {
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up: disconnect MySQL
|
||||
if (mysqlService) {
|
||||
// Clean up: disconnect database
|
||||
if (dbService) {
|
||||
try {
|
||||
await mysqlService.disconnect()
|
||||
log.debug('MySQL disconnected')
|
||||
await dbService.disconnect()
|
||||
log.debug('Database disconnected')
|
||||
} catch (closeError) {
|
||||
log.warn('Error disconnecting MySQL', {
|
||||
log.warn('Error disconnecting database', {
|
||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
*/
|
||||
|
||||
import { ipcMain } from 'electron'
|
||||
import { MySqlService } from '../services/database/mysql'
|
||||
import { create, type IDatabaseService } from '../services/database'
|
||||
import { OrderNumberResolver } from '../services/erp/order-resolver'
|
||||
import { createLogger } from '../services/logger'
|
||||
import { DatabaseQueryError } from '../types/errors'
|
||||
import type { OrderMapping, ResolutionStats } from '../services/erp/order-resolver'
|
||||
|
||||
const log = createLogger('ResolverHandler')
|
||||
@@ -21,14 +20,6 @@ const log = createLogger('ResolverHandler')
|
||||
export interface ResolverInput {
|
||||
/** List of order numbers/productionIDs to resolve */
|
||||
inputs: string[]
|
||||
/** MySQL configuration (optional, uses default if not provided) */
|
||||
mysqlConfig?: {
|
||||
host: string
|
||||
port: number
|
||||
user: string
|
||||
password: string
|
||||
database: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,25 +51,15 @@ export function registerResolverHandlers(): void {
|
||||
ipcMain.handle(
|
||||
'resolver:resolve',
|
||||
async (_event, input: ResolverInput): Promise<ResolverResponse> => {
|
||||
let mysqlService: MySqlService | null = null
|
||||
let dbService: IDatabaseService | null = null
|
||||
|
||||
try {
|
||||
// Use provided config or environment variables
|
||||
const mysqlConfig = input.mysqlConfig || {
|
||||
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 MySQL service
|
||||
log.info('Connecting to MySQL for resolution', { inputCount: input.inputs.length })
|
||||
mysqlService = new MySqlService(mysqlConfig)
|
||||
await mysqlService.connect()
|
||||
// Create database service using factory
|
||||
log.info('Connecting to database for resolution', { inputCount: input.inputs.length })
|
||||
dbService = await create()
|
||||
|
||||
// Create resolver and resolve inputs
|
||||
const resolver = new OrderNumberResolver(mysqlService)
|
||||
const resolver = new OrderNumberResolver(dbService)
|
||||
const mappings = await resolver.resolve(input.inputs)
|
||||
|
||||
// Get valid order numbers and warnings
|
||||
@@ -107,13 +88,13 @@ export function registerResolverHandlers(): void {
|
||||
error: `解析失败:${message}`
|
||||
}
|
||||
} finally {
|
||||
// Clean up MySQL connection
|
||||
if (mysqlService) {
|
||||
// Clean up database connection
|
||||
if (dbService) {
|
||||
try {
|
||||
await mysqlService.disconnect()
|
||||
log.debug('MySQL disconnected')
|
||||
await dbService.disconnect()
|
||||
log.debug('Database disconnected')
|
||||
} catch (closeError) {
|
||||
log.warn('Error disconnecting MySQL', {
|
||||
log.warn('Error disconnecting database', {
|
||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||
})
|
||||
}
|
||||
@@ -136,9 +117,11 @@ export function registerResolverHandlers(): void {
|
||||
error?: string
|
||||
}> => {
|
||||
try {
|
||||
// Create a mock resolver without database connection
|
||||
const resolver = new OrderNumberResolver({
|
||||
isConnected: () => false
|
||||
} as MySqlService)
|
||||
isConnected: () => false,
|
||||
type: 'mysql'
|
||||
} as IDatabaseService)
|
||||
|
||||
const results = inputs.map((input) => ({
|
||||
input,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
182
src/main/services/database/index.ts
Normal file
182
src/main/services/database/index.ts
Normal 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'
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.')
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
104
src/main/types/database.types.ts
Normal file
104
src/main/types/database.types.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Database Type Definitions
|
||||
*
|
||||
* Provides abstract interfaces for database operations,
|
||||
* supporting both MySQL and SQL Server databases.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Supported database types
|
||||
*/
|
||||
export type DatabaseType = 'mysql' | 'sqlserver'
|
||||
|
||||
/**
|
||||
* Standard query result interface
|
||||
*/
|
||||
export interface QueryResult {
|
||||
/** Query result rows */
|
||||
rows: Record<string, unknown>[]
|
||||
/** Column names from the query */
|
||||
columns: string[]
|
||||
/** Number of rows affected or returned */
|
||||
rowCount: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Database service interface
|
||||
*
|
||||
* Defines the common interface that all database services must implement.
|
||||
* This allows for database-agnostic operations throughout the application.
|
||||
*/
|
||||
export interface IDatabaseService {
|
||||
/** Database type identifier */
|
||||
readonly type: DatabaseType
|
||||
|
||||
/**
|
||||
* Connect to the database
|
||||
*/
|
||||
connect(): Promise<void>
|
||||
|
||||
/**
|
||||
* Disconnect from the database
|
||||
*/
|
||||
disconnect(): Promise<void>
|
||||
|
||||
/**
|
||||
* Check if connected to the database
|
||||
*/
|
||||
isConnected(): boolean
|
||||
|
||||
/**
|
||||
* Execute a query and return results
|
||||
* @param sql - SQL query string
|
||||
* @param params - Query parameters as an array
|
||||
*/
|
||||
query(sql: string, params?: any[]): Promise<QueryResult>
|
||||
|
||||
/**
|
||||
* Execute multiple queries in a transaction
|
||||
* @param queries - Array of queries with optional parameters
|
||||
*/
|
||||
transaction(queries: { sql: string; params?: any[] }[]): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Base database configuration interface
|
||||
*/
|
||||
export interface DatabaseConfig {
|
||||
/** Database server host */
|
||||
host?: string
|
||||
/** Database server port */
|
||||
port?: number
|
||||
/** Database username */
|
||||
user?: string
|
||||
/** Database password */
|
||||
password?: string
|
||||
/** Database name */
|
||||
database?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL-specific configuration
|
||||
*/
|
||||
export interface MySqlConfig extends DatabaseConfig {
|
||||
host: string
|
||||
port: number
|
||||
user: string
|
||||
password: string
|
||||
database: string
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL Server-specific configuration
|
||||
*/
|
||||
export interface SqlServerConfig extends DatabaseConfig {
|
||||
server: string
|
||||
port: number
|
||||
user: string
|
||||
password: string
|
||||
database: string
|
||||
options?: {
|
||||
encrypt?: boolean
|
||||
trustServerCertificate?: boolean
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user