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) })
|
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) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : 'SQL Server query failed'
|
const message = error instanceof Error ? error.message : 'SQL Server query failed'
|
||||||
log.error('SQL Server query failed', { error: message })
|
log.error('SQL Server query failed', { error: message })
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ipcMain } from 'electron'
|
|||||||
import { ErpAuthService } from '../services/erp/erp-auth'
|
import { ErpAuthService } from '../services/erp/erp-auth'
|
||||||
import { ExtractorService } from '../services/erp/extractor'
|
import { ExtractorService } from '../services/erp/extractor'
|
||||||
import { OrderNumberResolver } from '../services/erp/order-resolver'
|
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 { createLogger } from '../services/logger'
|
||||||
import { withErrorHandling, type IpcResult } from './index'
|
import { withErrorHandling, type IpcResult } from './index'
|
||||||
import { ErpConnectionError, ValidationError, DatabaseQueryError } from '../types/errors'
|
import { ErpConnectionError, ValidationError, DatabaseQueryError } from '../types/errors'
|
||||||
@@ -19,7 +19,7 @@ export function registerExtractorHandlers(): void {
|
|||||||
async (_event, input: ExtractorInput): Promise<IpcResult<ExtractorResult>> => {
|
async (_event, input: ExtractorInput): Promise<IpcResult<ExtractorResult>> => {
|
||||||
return withErrorHandling(async () => {
|
return withErrorHandling(async () => {
|
||||||
let authService: ErpAuthService | null = null
|
let authService: ErpAuthService | null = null
|
||||||
let mysqlService: MySqlService | null = null
|
let dbService: IDatabaseService | null = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check environment variables
|
// Check environment variables
|
||||||
@@ -39,29 +39,20 @@ export function registerExtractorHandlers(): void {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve order numbers (convert productionIDs to 生产订单号)
|
// Create database service using factory
|
||||||
const mysqlConfig = {
|
log.info('Connecting to database for order resolution...')
|
||||||
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)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await mysqlService.connect()
|
dbService = await create()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseQueryError(
|
throw new DatabaseQueryError(
|
||||||
'MySQL 连接失败',
|
'数据库连接失败',
|
||||||
'DB_CONNECTION_FAILED',
|
'DB_CONNECTION_FAILED',
|
||||||
error instanceof Error ? error : undefined
|
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)
|
const mappings = await resolver.resolve(input.orderNumbers)
|
||||||
|
|
||||||
// Get valid order numbers and warnings
|
// Get valid order numbers and warnings
|
||||||
@@ -136,13 +127,13 @@ export function registerExtractorHandlers(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up: disconnect MySQL
|
// Clean up: disconnect database
|
||||||
if (mysqlService) {
|
if (dbService) {
|
||||||
try {
|
try {
|
||||||
await mysqlService.disconnect()
|
await dbService.disconnect()
|
||||||
log.debug('MySQL disconnected')
|
log.debug('Database disconnected')
|
||||||
} catch (closeError) {
|
} catch (closeError) {
|
||||||
log.warn('Error disconnecting MySQL', {
|
log.warn('Error disconnecting database', {
|
||||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ipcMain } from 'electron'
|
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 { OrderNumberResolver } from '../services/erp/order-resolver'
|
||||||
import { createLogger } from '../services/logger'
|
import { createLogger } from '../services/logger'
|
||||||
import { DatabaseQueryError } from '../types/errors'
|
|
||||||
import type { OrderMapping, ResolutionStats } from '../services/erp/order-resolver'
|
import type { OrderMapping, ResolutionStats } from '../services/erp/order-resolver'
|
||||||
|
|
||||||
const log = createLogger('ResolverHandler')
|
const log = createLogger('ResolverHandler')
|
||||||
@@ -21,14 +20,6 @@ const log = createLogger('ResolverHandler')
|
|||||||
export interface ResolverInput {
|
export interface ResolverInput {
|
||||||
/** List of order numbers/productionIDs to resolve */
|
/** List of order numbers/productionIDs to resolve */
|
||||||
inputs: string[]
|
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(
|
ipcMain.handle(
|
||||||
'resolver:resolve',
|
'resolver:resolve',
|
||||||
async (_event, input: ResolverInput): Promise<ResolverResponse> => {
|
async (_event, input: ResolverInput): Promise<ResolverResponse> => {
|
||||||
let mysqlService: MySqlService | null = null
|
let dbService: IDatabaseService | null = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use provided config or environment variables
|
// Create database service using factory
|
||||||
const mysqlConfig = input.mysqlConfig || {
|
log.info('Connecting to database for resolution', { inputCount: input.inputs.length })
|
||||||
host: process.env.DB_MYSQL_HOST || 'localhost',
|
dbService = await create()
|
||||||
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 resolver and resolve inputs
|
// Create resolver and resolve inputs
|
||||||
const resolver = new OrderNumberResolver(mysqlService)
|
const resolver = new OrderNumberResolver(dbService)
|
||||||
const mappings = await resolver.resolve(input.inputs)
|
const mappings = await resolver.resolve(input.inputs)
|
||||||
|
|
||||||
// Get valid order numbers and warnings
|
// Get valid order numbers and warnings
|
||||||
@@ -107,13 +88,13 @@ export function registerResolverHandlers(): void {
|
|||||||
error: `解析失败:${message}`
|
error: `解析失败:${message}`
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up MySQL connection
|
// Clean up database connection
|
||||||
if (mysqlService) {
|
if (dbService) {
|
||||||
try {
|
try {
|
||||||
await mysqlService.disconnect()
|
await dbService.disconnect()
|
||||||
log.debug('MySQL disconnected')
|
log.debug('Database disconnected')
|
||||||
} catch (closeError) {
|
} catch (closeError) {
|
||||||
log.warn('Error disconnecting MySQL', {
|
log.warn('Error disconnecting database', {
|
||||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -136,9 +117,11 @@ export function registerResolverHandlers(): void {
|
|||||||
error?: string
|
error?: string
|
||||||
}> => {
|
}> => {
|
||||||
try {
|
try {
|
||||||
|
// Create a mock resolver without database connection
|
||||||
const resolver = new OrderNumberResolver({
|
const resolver = new OrderNumberResolver({
|
||||||
isConnected: () => false
|
isConnected: () => false,
|
||||||
} as MySqlService)
|
type: 'mysql'
|
||||||
|
} as IDatabaseService)
|
||||||
|
|
||||||
const results = inputs.map((input) => ({
|
const results = inputs.map((input) => ({
|
||||||
input,
|
input,
|
||||||
|
|||||||
@@ -8,9 +8,7 @@
|
|||||||
* - Statistics gathering
|
* - Statistics gathering
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MySqlService } from './mysql'
|
import { create, type IDatabaseService } from './index'
|
||||||
import { SqlServerService } from './sql-server'
|
|
||||||
import sql from 'mssql'
|
|
||||||
import { createLogger } from '../logger'
|
import { createLogger } from '../logger'
|
||||||
|
|
||||||
const log = createLogger('DiscreteMaterialPlanDAO')
|
const log = createLogger('DiscreteMaterialPlanDAO')
|
||||||
@@ -95,70 +93,37 @@ export const DISCRETE_MATERIAL_PLAN_CONFIG = {
|
|||||||
* DiscreteMaterialPlanDAO Class
|
* DiscreteMaterialPlanDAO Class
|
||||||
*/
|
*/
|
||||||
export class DiscreteMaterialPlanDAO {
|
export class DiscreteMaterialPlanDAO {
|
||||||
private mysqlService: MySqlService | null = null
|
private dbService: IDatabaseService | 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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the appropriate table name based on database type
|
* Get the appropriate table name based on database type
|
||||||
*/
|
*/
|
||||||
private getTableName(): string {
|
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_SQLSERVER
|
||||||
: DISCRETE_MATERIAL_PLAN_CONFIG.TABLE_NAME_MYSQL
|
: 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> {
|
private async getDatabaseService(): Promise<IDatabaseService> {
|
||||||
if (this.dbType === 'sqlserver') {
|
if (this.dbService && this.dbService.isConnected()) {
|
||||||
if (this.sqlServerService && this.sqlServerService.isConnected()) {
|
return this.dbService
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 ====================
|
// ==================== QUERY ALL ====================
|
||||||
@@ -173,11 +138,7 @@ export class DiscreteMaterialPlanDAO {
|
|||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
|
||||||
const sqlString = `SELECT * FROM ${tableName}`
|
const sqlString = `SELECT * FROM ${tableName}`
|
||||||
|
const result = await dbService.query(sqlString)
|
||||||
const result =
|
|
||||||
this.dbType === 'sqlserver'
|
|
||||||
? await (dbService as SqlServerService).query(sqlString)
|
|
||||||
: await (dbService as MySqlService).query(sqlString)
|
|
||||||
|
|
||||||
return result.rows
|
return result.rows
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -221,11 +182,7 @@ export class DiscreteMaterialPlanDAO {
|
|||||||
WHERE rn = 1
|
WHERE rn = 1
|
||||||
`
|
`
|
||||||
|
|
||||||
const result =
|
const result = await dbService.query(sqlString)
|
||||||
this.dbType === 'sqlserver'
|
|
||||||
? await (dbService as SqlServerService).query(sqlString)
|
|
||||||
: await (dbService as MySqlService).query(sqlString)
|
|
||||||
|
|
||||||
return result.rows
|
return result.rows
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Query all distinct by material code error', {
|
log.error('Query all distinct by material code error', {
|
||||||
@@ -250,40 +207,22 @@ export class DiscreteMaterialPlanDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
const batchSize = 2000
|
const batchSize = 2000
|
||||||
const allResults: any[] = []
|
const allResults: any[] = []
|
||||||
|
|
||||||
for (let i = 0; i < sourceNumbers.length; i += batchSize) {
|
for (let i = 0; i < sourceNumbers.length; i += batchSize) {
|
||||||
const batch = sourceNumbers.slice(i, i + batchSize)
|
const batch = sourceNumbers.slice(i, i + batchSize)
|
||||||
|
const placeholders = this.buildPlaceholders(batch.length, isSqlServer)
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
const sqlString = `
|
||||||
const placeholders = batch.map((_, idx) => `@p${idx}`).join(',')
|
SELECT *
|
||||||
const params: Record<string, { value: string; type: sql.ISqlType }> = {}
|
FROM ${tableName}
|
||||||
|
WHERE SourceNumber IN (${placeholders})
|
||||||
|
`
|
||||||
|
|
||||||
batch.forEach((num, idx) => {
|
const result = await dbService.query(sqlString, batch)
|
||||||
params[`p${idx}`] = { value: num, type: sql.NVarChar }
|
allResults.push(...result.rows)
|
||||||
})
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return allResults
|
return allResults
|
||||||
@@ -310,74 +249,39 @@ export class DiscreteMaterialPlanDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
const batchSize = 2000
|
const batchSize = 2000
|
||||||
const allResults: any[] = []
|
const allResults: any[] = []
|
||||||
|
|
||||||
for (let i = 0; i < sourceNumbers.length; i += batchSize) {
|
for (let i = 0; i < sourceNumbers.length; i += batchSize) {
|
||||||
const batch = sourceNumbers.slice(i, i + batchSize)
|
const batch = sourceNumbers.slice(i, i + batchSize)
|
||||||
|
const placeholders = this.buildPlaceholders(batch.length, isSqlServer)
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
const sqlString = `
|
||||||
const placeholders = batch.map((_, idx) => `@p${idx}`).join(',')
|
WITH RankedRecords AS (
|
||||||
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
|
|
||||||
)
|
|
||||||
SELECT
|
SELECT
|
||||||
Factory, MaterialStatus, PlanNumber, SourceNumber, MaterialType,
|
*,
|
||||||
ProductCode, ProductName, ProductUnit, ProductPlanQuantity,
|
ROW_NUMBER() OVER (
|
||||||
UseDepartment, Remark, Creator, CreateDate, Approver, ApproveDate,
|
PARTITION BY MaterialCode
|
||||||
SequenceNumber, MaterialCode, MaterialName, Specification, Model,
|
ORDER BY CreateDate ASC, SequenceNumber ASC
|
||||||
DrawingNumber, MaterialQuality, PlanQuantity, Unit, RequiredDate,
|
) AS rn
|
||||||
Warehouse, UnitUsage, CumulativeOutputQuantity, BOMVersion
|
FROM ${tableName}
|
||||||
FROM RankedRecords
|
WHERE SourceNumber IN (${placeholders})
|
||||||
WHERE rn = 1
|
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)
|
const result = await dbService.query(sqlString, batch)
|
||||||
allResults.push(...result.rows)
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return allResults
|
return allResults
|
||||||
@@ -398,30 +302,17 @@ export class DiscreteMaterialPlanDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
const placeholder = isSqlServer ? '@p0' : '?'
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM ${tableName}
|
FROM ${tableName}
|
||||||
WHERE SourceNumber = @sourceNumber
|
WHERE SourceNumber = ${placeholder}
|
||||||
`
|
`
|
||||||
|
|
||||||
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
|
const result = await dbService.query(sqlString, [sourceNumber])
|
||||||
sourceNumber: { value: sourceNumber, type: sql.NVarChar }
|
return result.rows
|
||||||
})
|
|
||||||
|
|
||||||
return result.rows
|
|
||||||
} else {
|
|
||||||
const sqlString = `
|
|
||||||
SELECT *
|
|
||||||
FROM ${tableName}
|
|
||||||
WHERE SourceNumber = ?
|
|
||||||
`
|
|
||||||
|
|
||||||
const result = await (dbService as MySqlService).query(sqlString, [sourceNumber])
|
|
||||||
|
|
||||||
return result.rows
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Query by source number error', {
|
log.error('Query by source number error', {
|
||||||
error: error instanceof Error ? error.message : String(error)
|
error: error instanceof Error ? error.message : String(error)
|
||||||
@@ -441,30 +332,17 @@ export class DiscreteMaterialPlanDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
const placeholder = isSqlServer ? '@p0' : '?'
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM ${tableName}
|
FROM ${tableName}
|
||||||
WHERE PlanNumber = @planNumber
|
WHERE PlanNumber = ${placeholder}
|
||||||
`
|
`
|
||||||
|
|
||||||
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
|
const result = await dbService.query(sqlString, [planNumber])
|
||||||
planNumber: { value: planNumber, type: sql.NVarChar }
|
return result.rows
|
||||||
})
|
|
||||||
|
|
||||||
return result.rows
|
|
||||||
} else {
|
|
||||||
const sqlString = `
|
|
||||||
SELECT *
|
|
||||||
FROM ${tableName}
|
|
||||||
WHERE PlanNumber = ?
|
|
||||||
`
|
|
||||||
|
|
||||||
const result = await (dbService as MySqlService).query(sqlString, [planNumber])
|
|
||||||
|
|
||||||
return result.rows
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Query by plan number error', {
|
log.error('Query by plan number error', {
|
||||||
error: error instanceof Error ? error.message : String(error)
|
error: error instanceof Error ? error.message : String(error)
|
||||||
@@ -486,35 +364,17 @@ export class DiscreteMaterialPlanDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
const placeholders = this.buildPlaceholders(planNumbers.length, isSqlServer)
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
const sqlString = `
|
||||||
const placeholders = planNumbers.map((_, idx) => `@p${idx}`).join(',')
|
SELECT *
|
||||||
const params: Record<string, { value: string; type: sql.ISqlType }> = {}
|
FROM ${tableName}
|
||||||
|
WHERE PlanNumber IN (${placeholders})
|
||||||
|
`
|
||||||
|
|
||||||
planNumbers.forEach((num, idx) => {
|
const result = await dbService.query(sqlString, planNumbers)
|
||||||
params[`p${idx}`] = { value: num, type: sql.NVarChar }
|
return result.rows
|
||||||
})
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Query by plan numbers error', {
|
log.error('Query by plan numbers error', {
|
||||||
error: error instanceof Error ? error.message : String(error)
|
error: error instanceof Error ? error.message : String(error)
|
||||||
@@ -535,11 +395,7 @@ export class DiscreteMaterialPlanDAO {
|
|||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
|
||||||
const sqlString = `SELECT COUNT(*) as count FROM ${tableName}`
|
const sqlString = `SELECT COUNT(*) as count FROM ${tableName}`
|
||||||
|
const result = await dbService.query(sqlString)
|
||||||
const result =
|
|
||||||
this.dbType === 'sqlserver'
|
|
||||||
? await (dbService as SqlServerService).query(sqlString)
|
|
||||||
: await (dbService as MySqlService).query(sqlString)
|
|
||||||
|
|
||||||
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -559,30 +415,17 @@ export class DiscreteMaterialPlanDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
const placeholder = isSqlServer ? '@p0' : '?'
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
SELECT COUNT(*) as count
|
SELECT COUNT(*) as count
|
||||||
FROM ${tableName}
|
FROM ${tableName}
|
||||||
WHERE PlanNumber = @planNumber
|
WHERE PlanNumber = ${placeholder}
|
||||||
`
|
`
|
||||||
|
|
||||||
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
|
const result = await dbService.query(sqlString, [planNumber])
|
||||||
planNumber: { value: planNumber, type: sql.NVarChar }
|
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
||||||
})
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Count by plan number error', {
|
log.error('Count by plan number error', {
|
||||||
error: error instanceof Error ? error.message : String(error)
|
error: error instanceof Error ? error.message : String(error)
|
||||||
@@ -600,38 +443,20 @@ export class DiscreteMaterialPlanDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
|
||||||
if (sourceNumbers && sourceNumbers.length > 0) {
|
if (sourceNumbers && sourceNumbers.length > 0) {
|
||||||
if (this.dbType === 'sqlserver') {
|
const placeholders = this.buildPlaceholders(sourceNumbers.length, isSqlServer)
|
||||||
const placeholders = sourceNumbers.map((_, idx) => `@p${idx}`).join(',')
|
|
||||||
const params: Record<string, { value: string; type: sql.ISqlType }> = {}
|
|
||||||
|
|
||||||
sourceNumbers.forEach((num, idx) => {
|
const sqlString = `
|
||||||
params[`p${idx}`] = { value: num, type: sql.NVarChar }
|
SELECT DISTINCT MaterialName
|
||||||
})
|
FROM ${tableName}
|
||||||
|
WHERE SourceNumber IN (${placeholders})
|
||||||
|
AND MaterialName IS NOT NULL
|
||||||
|
`
|
||||||
|
|
||||||
const sqlString = `
|
const result = await dbService.query(sqlString, sourceNumbers)
|
||||||
SELECT DISTINCT MaterialName
|
return result.rows.map((row) => row.MaterialName as string).filter(Boolean)
|
||||||
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)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
SELECT DISTINCT MaterialName
|
SELECT DISTINCT MaterialName
|
||||||
@@ -639,11 +464,7 @@ export class DiscreteMaterialPlanDAO {
|
|||||||
WHERE MaterialName IS NOT NULL
|
WHERE MaterialName IS NOT NULL
|
||||||
`
|
`
|
||||||
|
|
||||||
const result =
|
const result = await dbService.query(sqlString)
|
||||||
this.dbType === 'sqlserver'
|
|
||||||
? await (dbService as SqlServerService).query(sqlString)
|
|
||||||
: await (dbService as MySqlService).query(sqlString)
|
|
||||||
|
|
||||||
return result.rows.map((row) => row.MaterialName as string).filter(Boolean)
|
return result.rows.map((row) => row.MaterialName as string).filter(Boolean)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -673,11 +494,7 @@ export class DiscreteMaterialPlanDAO {
|
|||||||
FROM ${tableName}
|
FROM ${tableName}
|
||||||
`
|
`
|
||||||
|
|
||||||
const result =
|
const result = await dbService.query(sqlString)
|
||||||
this.dbType === 'sqlserver'
|
|
||||||
? await (dbService as SqlServerService).query(sqlString)
|
|
||||||
: await (dbService as MySqlService).query(sqlString)
|
|
||||||
|
|
||||||
return result.rows.length > 0 ? result.rows[0] : {}
|
return result.rows.length > 0 ? result.rows[0] : {}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Get statistics error', {
|
log.error('Get statistics error', {
|
||||||
@@ -691,13 +508,9 @@ export class DiscreteMaterialPlanDAO {
|
|||||||
* Disconnect from database
|
* Disconnect from database
|
||||||
*/
|
*/
|
||||||
async disconnect(): Promise<void> {
|
async disconnect(): Promise<void> {
|
||||||
if (this.mysqlService) {
|
if (this.dbService) {
|
||||||
await this.mysqlService.disconnect()
|
await this.dbService.disconnect()
|
||||||
this.mysqlService = null
|
this.dbService = null
|
||||||
}
|
|
||||||
if (this.sqlServerService) {
|
|
||||||
await this.sqlServerService.disconnect()
|
|
||||||
this.sqlServerService = 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
|
* - Statistics gathering
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MySqlService } from './mysql'
|
import { create, type IDatabaseService } from './index'
|
||||||
import { SqlServerService } from './sql-server'
|
|
||||||
import sql from 'mssql'
|
|
||||||
import { createLogger } from '../logger'
|
import { createLogger } from '../logger'
|
||||||
|
|
||||||
const log = createLogger('MaterialsToBeDeletedDAO')
|
const log = createLogger('MaterialsToBeDeletedDAO')
|
||||||
@@ -59,70 +57,37 @@ export const MATERIALS_TO_BE_DELETED_CONFIG = {
|
|||||||
* MaterialsToBeDeleted DAO Class
|
* MaterialsToBeDeleted DAO Class
|
||||||
*/
|
*/
|
||||||
export class MaterialsToBeDeletedDAO {
|
export class MaterialsToBeDeletedDAO {
|
||||||
private mysqlService: MySqlService | null = null
|
private dbService: IDatabaseService | 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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the appropriate table name based on database type
|
* Get the appropriate table name based on database type
|
||||||
*/
|
*/
|
||||||
private getTableName(): string {
|
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_SQLSERVER
|
||||||
: MATERIALS_TO_BE_DELETED_CONFIG.TABLE_NAME_MYSQL
|
: 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> {
|
private async getDatabaseService(): Promise<IDatabaseService> {
|
||||||
if (this.dbType === 'sqlserver') {
|
if (this.dbService && this.dbService.isConnected()) {
|
||||||
if (this.sqlServerService && this.sqlServerService.isConnected()) {
|
return this.dbService
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) ====================
|
// ==================== UPSERT (MERGE) ====================
|
||||||
@@ -144,28 +109,28 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const code = materialCode.trim()
|
const code = materialCode.trim()
|
||||||
const manager = managerName?.trim() || null
|
const manager = managerName?.trim() || null
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
if (isSqlServer) {
|
||||||
|
// SQL Server MERGE statement
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
MERGE ${tableName} AS target
|
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
|
ON target.MaterialCode = source.MaterialCode
|
||||||
WHEN MATCHED THEN UPDATE SET ManagerName = source.ManagerName
|
WHEN MATCHED THEN UPDATE SET ManagerName = source.ManagerName
|
||||||
WHEN NOT MATCHED THEN INSERT (MaterialCode, ManagerName) VALUES (source.MaterialCode, source.ManagerName);
|
WHEN NOT MATCHED THEN INSERT (MaterialCode, ManagerName) VALUES (source.MaterialCode, source.ManagerName);
|
||||||
`
|
`
|
||||||
|
|
||||||
await (dbService as SqlServerService).queryWithParams(sqlString, {
|
await dbService.query(sqlString, [code, manager])
|
||||||
materialCode: { value: code, type: sql.NVarChar },
|
|
||||||
managerName: { value: manager, type: sql.NVarChar }
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
|
// MySQL ON DUPLICATE KEY UPDATE
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
INSERT INTO ${tableName} (MaterialCode, ManagerName)
|
INSERT INTO ${tableName} (MaterialCode, ManagerName)
|
||||||
VALUES (?, ?)
|
VALUES (?, ?)
|
||||||
ON DUPLICATE KEY UPDATE ManagerName = VALUES(ManagerName)
|
ON DUPLICATE KEY UPDATE ManagerName = VALUES(ManagerName)
|
||||||
`
|
`
|
||||||
|
|
||||||
await (dbService as MySqlService).query(sqlString, [code, manager])
|
await dbService.query(sqlString, [code, manager])
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -198,6 +163,7 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
|
||||||
for (const material of materials) {
|
for (const material of materials) {
|
||||||
const materialCode = material.materialCode?.trim()
|
const materialCode = material.materialCode?.trim()
|
||||||
@@ -209,27 +175,26 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.dbType === 'sqlserver') {
|
if (isSqlServer) {
|
||||||
|
// SQL Server MERGE statement
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
MERGE ${tableName} AS target
|
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
|
ON target.MaterialCode = source.MaterialCode
|
||||||
WHEN MATCHED THEN UPDATE SET ManagerName = source.ManagerName
|
WHEN MATCHED THEN UPDATE SET ManagerName = source.ManagerName
|
||||||
WHEN NOT MATCHED THEN INSERT (MaterialCode, ManagerName) VALUES (source.MaterialCode, source.ManagerName);
|
WHEN NOT MATCHED THEN INSERT (MaterialCode, ManagerName) VALUES (source.MaterialCode, source.ManagerName);
|
||||||
`
|
`
|
||||||
|
|
||||||
await (dbService as SqlServerService).queryWithParams(sqlString, {
|
await dbService.query(sqlString, [materialCode, managerName || null])
|
||||||
materialCode: { value: materialCode, type: sql.NVarChar },
|
|
||||||
managerName: { value: managerName || null, type: sql.NVarChar }
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
|
// MySQL ON DUPLICATE KEY UPDATE
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
INSERT INTO ${tableName} (MaterialCode, ManagerName)
|
INSERT INTO ${tableName} (MaterialCode, ManagerName)
|
||||||
VALUES (?, ?)
|
VALUES (?, ?)
|
||||||
ON DUPLICATE KEY UPDATE ManagerName = VALUES(ManagerName)
|
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++
|
stats.success++
|
||||||
@@ -268,11 +233,7 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
WHERE MaterialCode IS NOT NULL
|
WHERE MaterialCode IS NOT NULL
|
||||||
`
|
`
|
||||||
|
|
||||||
const result =
|
const result = await dbService.query(sqlString)
|
||||||
this.dbType === 'sqlserver'
|
|
||||||
? await (dbService as SqlServerService).query(sqlString)
|
|
||||||
: await (dbService as MySqlService).query(sqlString)
|
|
||||||
|
|
||||||
return new Set(result.rows.map((row) => row.MaterialCode as string).filter(Boolean))
|
return new Set(result.rows.map((row) => row.MaterialCode as string).filter(Boolean))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Get all material codes error', {
|
log.error('Get all material codes error', {
|
||||||
@@ -298,11 +259,7 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
ORDER BY ManagerName, MaterialCode
|
ORDER BY ManagerName, MaterialCode
|
||||||
`
|
`
|
||||||
|
|
||||||
const result =
|
const result = await dbService.query(sqlString)
|
||||||
this.dbType === 'sqlserver'
|
|
||||||
? await (dbService as SqlServerService).query(sqlString)
|
|
||||||
: await (dbService as MySqlService).query(sqlString)
|
|
||||||
|
|
||||||
return result.rows.map((row) => ({
|
return result.rows.map((row) => ({
|
||||||
id: row.ID as number,
|
id: row.ID as number,
|
||||||
materialCode: row.MaterialCode as string,
|
materialCode: row.MaterialCode as string,
|
||||||
@@ -325,40 +282,22 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
const placeholder = isSqlServer ? '@p0' : '?'
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
SELECT ID, MaterialCode, ManagerName
|
SELECT ID, MaterialCode, ManagerName
|
||||||
FROM ${tableName}
|
FROM ${tableName}
|
||||||
WHERE ManagerName = @managerName AND MaterialCode IS NOT NULL
|
WHERE ManagerName = ${placeholder} AND MaterialCode IS NOT NULL
|
||||||
ORDER BY MaterialCode
|
ORDER BY MaterialCode
|
||||||
`
|
`
|
||||||
|
|
||||||
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
|
const result = await dbService.query(sqlString, [managerName])
|
||||||
managerName: { value: managerName, type: sql.NVarChar }
|
return result.rows.map((row) => ({
|
||||||
})
|
id: row.ID as number,
|
||||||
|
materialCode: row.MaterialCode as string,
|
||||||
return result.rows.map((row) => ({
|
managerName: row.ManagerName as string
|
||||||
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
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Get materials by manager error', {
|
log.error('Get materials by manager error', {
|
||||||
error: error instanceof Error ? error.message : String(error)
|
error: error instanceof Error ? error.message : String(error)
|
||||||
@@ -383,11 +322,7 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
ORDER BY ManagerName
|
ORDER BY ManagerName
|
||||||
`
|
`
|
||||||
|
|
||||||
const result =
|
const result = await dbService.query(sqlString)
|
||||||
this.dbType === 'sqlserver'
|
|
||||||
? await (dbService as SqlServerService).query(sqlString)
|
|
||||||
: await (dbService as MySqlService).query(sqlString)
|
|
||||||
|
|
||||||
return result.rows.map((row) => row.ManagerName as string).filter(Boolean)
|
return result.rows.map((row) => row.ManagerName as string).filter(Boolean)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Get managers error', {
|
log.error('Get managers error', {
|
||||||
@@ -407,47 +342,26 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const code = materialCode.trim()
|
const code = materialCode.trim()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
const placeholder = isSqlServer ? '@p0' : '?'
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
SELECT ID, MaterialCode, ManagerName
|
SELECT ID, MaterialCode, ManagerName
|
||||||
FROM ${tableName}
|
FROM ${tableName}
|
||||||
WHERE MaterialCode = @materialCode
|
WHERE MaterialCode = ${placeholder}
|
||||||
`
|
`
|
||||||
|
|
||||||
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
|
const result = await dbService.query(sqlString, [code])
|
||||||
materialCode: { value: code, type: sql.NVarChar }
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result.rows.length === 0) {
|
if (result.rows.length === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const row = result.rows[0]
|
const row = result.rows[0]
|
||||||
return {
|
return {
|
||||||
id: row.ID as number,
|
id: row.ID as number,
|
||||||
materialCode: row.MaterialCode as string,
|
materialCode: row.MaterialCode as string,
|
||||||
managerName: row.ManagerName 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Get record by material code error', {
|
log.error('Get record by material code error', {
|
||||||
@@ -469,28 +383,16 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const code = materialCode.trim()
|
const code = materialCode.trim()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
const placeholder = isSqlServer ? '@p0' : '?'
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
DELETE FROM ${tableName}
|
DELETE FROM ${tableName}
|
||||||
WHERE MaterialCode = @materialCode
|
WHERE MaterialCode = ${placeholder}
|
||||||
`
|
`
|
||||||
|
|
||||||
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
|
const result = await dbService.query(sqlString, [code])
|
||||||
materialCode: { value: code, type: sql.NVarChar }
|
return result.rowCount > 0
|
||||||
})
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Delete by material code error', {
|
log.error('Delete by material code error', {
|
||||||
error: error instanceof Error ? error.message : String(error)
|
error: error instanceof Error ? error.message : String(error)
|
||||||
@@ -508,28 +410,16 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
const placeholder = isSqlServer ? '@p0' : '?'
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
DELETE FROM ${tableName}
|
DELETE FROM ${tableName}
|
||||||
WHERE ManagerName = @managerName
|
WHERE ManagerName = ${placeholder}
|
||||||
`
|
`
|
||||||
|
|
||||||
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
|
const result = await dbService.query(sqlString, [managerName])
|
||||||
managerName: { value: managerName, type: sql.NVarChar }
|
return result.rowCount
|
||||||
})
|
|
||||||
|
|
||||||
return result.rowCount
|
|
||||||
} else {
|
|
||||||
const sqlString = `
|
|
||||||
DELETE FROM ${tableName}
|
|
||||||
WHERE ManagerName = ?
|
|
||||||
`
|
|
||||||
|
|
||||||
const result = await (dbService as MySqlService).query(sqlString, [managerName])
|
|
||||||
|
|
||||||
return result.rowCount
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Delete by manager error', {
|
log.error('Delete by manager error', {
|
||||||
error: error instanceof Error ? error.message : String(error)
|
error: error instanceof Error ? error.message : String(error)
|
||||||
@@ -548,12 +438,7 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
|
||||||
const sqlString = `DELETE FROM ${tableName}`
|
const sqlString = `DELETE FROM ${tableName}`
|
||||||
|
const result = await dbService.query(sqlString)
|
||||||
const result =
|
|
||||||
this.dbType === 'sqlserver'
|
|
||||||
? await (dbService as SqlServerService).query(sqlString)
|
|
||||||
: await (dbService as MySqlService).query(sqlString)
|
|
||||||
|
|
||||||
return result.rowCount
|
return result.rowCount
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Delete all materials error', {
|
log.error('Delete all materials error', {
|
||||||
@@ -579,36 +464,22 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
|
||||||
for (let i = 0; i < materialCodes.length; i += batchSize) {
|
for (let i = 0; i < materialCodes.length; i += batchSize) {
|
||||||
const batch = materialCodes.slice(i, i + batchSize)
|
const batch = materialCodes.slice(i, i + batchSize)
|
||||||
|
const placeholders = this.buildPlaceholders(batch.length, isSqlServer)
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
const sqlString = `
|
||||||
const placeholders = batch.map((_, idx) => `@p${idx}`).join(',')
|
DELETE FROM ${tableName}
|
||||||
const params: Record<string, { value: string; type: sql.ISqlType }> = {}
|
WHERE MaterialCode IN (${placeholders})
|
||||||
|
`
|
||||||
|
|
||||||
batch.forEach((code, idx) => {
|
const result = await dbService.query(
|
||||||
params[`p${idx}`] = { value: code.trim(), type: sql.NVarChar }
|
sqlString,
|
||||||
})
|
batch.map((c) => c.trim())
|
||||||
|
)
|
||||||
const sqlString = `
|
totalDeleted += result.rowCount
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Delete by material codes error', {
|
log.error('Delete by material codes error', {
|
||||||
@@ -631,30 +502,17 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
const code = materialCode.trim()
|
const code = materialCode.trim()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
const placeholder = isSqlServer ? '@p0' : '?'
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
SELECT COUNT(*) as count
|
SELECT COUNT(*) as count
|
||||||
FROM ${tableName}
|
FROM ${tableName}
|
||||||
WHERE MaterialCode = @materialCode
|
WHERE MaterialCode = ${placeholder}
|
||||||
`
|
`
|
||||||
|
|
||||||
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
|
const result = await dbService.query(sqlString, [code])
|
||||||
materialCode: { value: code, type: sql.NVarChar }
|
return result.rows.length > 0 && (result.rows[0].count as number) > 0
|
||||||
})
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Material exists error', {
|
log.error('Material exists error', {
|
||||||
error: error instanceof Error ? error.message : String(error)
|
error: error instanceof Error ? error.message : String(error)
|
||||||
@@ -673,11 +531,7 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
|
||||||
const sqlString = `SELECT COUNT(*) as count FROM ${tableName}`
|
const sqlString = `SELECT COUNT(*) as count FROM ${tableName}`
|
||||||
|
const result = await dbService.query(sqlString)
|
||||||
const result =
|
|
||||||
this.dbType === 'sqlserver'
|
|
||||||
? await (dbService as SqlServerService).query(sqlString)
|
|
||||||
: await (dbService as MySqlService).query(sqlString)
|
|
||||||
|
|
||||||
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -697,30 +551,17 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
try {
|
try {
|
||||||
const dbService = await this.getDatabaseService()
|
const dbService = await this.getDatabaseService()
|
||||||
const tableName = this.getTableName()
|
const tableName = this.getTableName()
|
||||||
|
const isSqlServer = dbService.type === 'sqlserver'
|
||||||
|
|
||||||
if (this.dbType === 'sqlserver') {
|
const placeholder = isSqlServer ? '@p0' : '?'
|
||||||
const sqlString = `
|
const sqlString = `
|
||||||
SELECT COUNT(*) as count
|
SELECT COUNT(*) as count
|
||||||
FROM ${tableName}
|
FROM ${tableName}
|
||||||
WHERE ManagerName = @managerName
|
WHERE ManagerName = ${placeholder}
|
||||||
`
|
`
|
||||||
|
|
||||||
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
|
const result = await dbService.query(sqlString, [managerName])
|
||||||
managerName: { value: managerName, type: sql.NVarChar }
|
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
||||||
})
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Count by manager error', {
|
log.error('Count by manager error', {
|
||||||
error: error instanceof Error ? error.message : String(error)
|
error: error instanceof Error ? error.message : String(error)
|
||||||
@@ -747,11 +588,7 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
WHERE MaterialCode IS NOT NULL
|
WHERE MaterialCode IS NOT NULL
|
||||||
`
|
`
|
||||||
|
|
||||||
const statsResult =
|
const statsResult = await dbService.query(statsSql)
|
||||||
this.dbType === 'sqlserver'
|
|
||||||
? await (dbService as SqlServerService).query(statsSql)
|
|
||||||
: await (dbService as MySqlService).query(statsSql)
|
|
||||||
|
|
||||||
const stats = statsResult.rows[0] || {}
|
const stats = statsResult.rows[0] || {}
|
||||||
|
|
||||||
// Get materials per manager
|
// Get materials per manager
|
||||||
@@ -763,11 +600,7 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
ORDER BY count DESC
|
ORDER BY count DESC
|
||||||
`
|
`
|
||||||
|
|
||||||
const managerResult =
|
const managerResult = await dbService.query(managerSql)
|
||||||
this.dbType === 'sqlserver'
|
|
||||||
? await (dbService as SqlServerService).query(managerSql)
|
|
||||||
: await (dbService as MySqlService).query(managerSql)
|
|
||||||
|
|
||||||
const materialsPerManager = managerResult.rows.map((row) => ({
|
const materialsPerManager = managerResult.rows.map((row) => ({
|
||||||
[row.ManagerName as string]: row.count as number
|
[row.ManagerName as string]: row.count as number
|
||||||
}))
|
}))
|
||||||
@@ -793,13 +626,9 @@ export class MaterialsToBeDeletedDAO {
|
|||||||
* Disconnect from database
|
* Disconnect from database
|
||||||
*/
|
*/
|
||||||
async disconnect(): Promise<void> {
|
async disconnect(): Promise<void> {
|
||||||
if (this.mysqlService) {
|
if (this.dbService) {
|
||||||
await this.mysqlService.disconnect()
|
await this.dbService.disconnect()
|
||||||
this.mysqlService = null
|
this.dbService = null
|
||||||
}
|
|
||||||
if (this.sqlServerService) {
|
|
||||||
await this.sqlServerService.disconnect()
|
|
||||||
this.sqlServerService = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
import mysql from 'mysql2/promise'
|
import mysql from 'mysql2/promise'
|
||||||
|
import type {
|
||||||
|
IDatabaseService,
|
||||||
|
DatabaseType,
|
||||||
|
QueryResult,
|
||||||
|
MySqlConfig
|
||||||
|
} from '../../types/database.types'
|
||||||
|
|
||||||
export interface MySqlConfig {
|
export type { MySqlConfig } from '../../types/database.types'
|
||||||
host: string
|
|
||||||
port: number
|
|
||||||
user: string
|
|
||||||
password: string
|
|
||||||
database: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MySqlQueryResult {
|
export class MySqlService implements IDatabaseService {
|
||||||
rows: Record<string, unknown>[]
|
/** Database type identifier */
|
||||||
columns: string[]
|
readonly type: DatabaseType = 'mysql'
|
||||||
rowCount: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MySqlService {
|
|
||||||
private connection: mysql.Connection | null = null
|
private connection: mysql.Connection | null = null
|
||||||
private config: MySqlConfig
|
private config: MySqlConfig
|
||||||
|
|
||||||
@@ -72,7 +69,7 @@ export class MySqlService {
|
|||||||
/**
|
/**
|
||||||
* Execute a query and return results
|
* 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) {
|
if (!this.connection) {
|
||||||
throw new Error('Not connected to MySQL. Call connect() first.')
|
throw new Error('Not connected to MySQL. Call connect() first.')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,17 @@
|
|||||||
import sql from 'mssql'
|
import sql from 'mssql'
|
||||||
|
import type {
|
||||||
|
IDatabaseService,
|
||||||
|
DatabaseType,
|
||||||
|
QueryResult,
|
||||||
|
SqlServerConfig
|
||||||
|
} from '../../types/database.types'
|
||||||
|
|
||||||
export interface SqlServerConfig {
|
export type { SqlServerConfig } from '../../types/database.types'
|
||||||
server: string
|
|
||||||
port: number
|
|
||||||
user: string
|
|
||||||
password: string
|
|
||||||
database: string
|
|
||||||
options?: {
|
|
||||||
encrypt?: boolean
|
|
||||||
trustServerCertificate?: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SqlServerQueryResult {
|
export class SqlServerService implements IDatabaseService {
|
||||||
rows: Record<string, unknown>[]
|
/** Database type identifier */
|
||||||
columns: string[]
|
readonly type: DatabaseType = 'sqlserver'
|
||||||
rowCount: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SqlServerService {
|
|
||||||
private pool: sql.ConnectionPool | null = null
|
private pool: sql.ConnectionPool | null = null
|
||||||
private config: SqlServerConfig
|
private config: SqlServerConfig
|
||||||
|
|
||||||
@@ -79,8 +72,10 @@ export class SqlServerService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a query and return results
|
* 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) {
|
if (!this.pool) {
|
||||||
throw new Error('Not connected to SQL Server. Call connect() first.')
|
throw new Error('Not connected to SQL Server. Call connect() first.')
|
||||||
}
|
}
|
||||||
@@ -88,11 +83,11 @@ export class SqlServerService {
|
|||||||
try {
|
try {
|
||||||
const request = this.pool.request()
|
const request = this.pool.request()
|
||||||
|
|
||||||
// Add parameters if provided
|
// Add parameters if provided - convert array to @p0, @p1, ... format
|
||||||
if (params) {
|
if (params && params.length > 0) {
|
||||||
for (const [key, value] of Object.entries(params)) {
|
params.forEach((value, index) => {
|
||||||
request.input(key, value)
|
request.input(`p${index}`, value)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await request.query(sqlString)
|
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(
|
async queryWithParams(
|
||||||
sqlString: string,
|
sqlString: string,
|
||||||
params: Record<string, { value: unknown; type?: sql.ISqlType }>
|
params: Record<string, { value: unknown; type?: sql.ISqlType }>
|
||||||
): Promise<SqlServerQueryResult> {
|
): Promise<QueryResult> {
|
||||||
if (!this.pool) {
|
if (!this.pool) {
|
||||||
throw new Error('Not connected to SQL Server. Call connect() first.')
|
throw new Error('Not connected to SQL Server. Call connect() first.')
|
||||||
}
|
}
|
||||||
@@ -154,8 +151,9 @@ export class SqlServerService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute multiple queries in a transaction
|
* 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) {
|
if (!this.pool) {
|
||||||
throw new Error('Not connected to SQL Server. Call connect() first.')
|
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) {
|
for (const { sql: sqlString, params } of queries) {
|
||||||
const request = new sql.Request(transaction)
|
const request = new sql.Request(transaction)
|
||||||
|
|
||||||
if (params) {
|
// Add parameters if provided - convert array to @p0, @p1, ... format
|
||||||
for (const [key, value] of Object.entries(params)) {
|
if (params && params.length > 0) {
|
||||||
request.input(key, value)
|
params.forEach((value, index) => {
|
||||||
}
|
request.input(`p${index}`, value)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await request.query(sqlString)
|
await request.query(sqlString)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Order Number Resolver Service
|
* Order Number Resolver Service
|
||||||
*
|
*
|
||||||
* Automatically recognizes productionID and 生产订单号 (production order number),
|
* 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")
|
* - productionID format: 2 digits + 1 letter + serial number (e.g., "22A1", "22A1234")
|
||||||
* - 生产订单号 format: SC + 14 digits (e.g., "SC70202602120085")
|
* - 生产订单号 format: SC + 14 digits (e.g., "SC70202602120085")
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
* Fields: 总排号 (productionID), 生产订单号 (production order number)
|
* Fields: 总排号 (productionID), 生产订单号 (production order number)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MySqlService } from '../database/mysql'
|
import type { IDatabaseService } from '../database'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Order mapping result
|
* Order mapping result
|
||||||
@@ -71,10 +71,10 @@ export const DB_CONFIG = {
|
|||||||
* Order Number Resolver Service
|
* Order Number Resolver Service
|
||||||
*/
|
*/
|
||||||
export class OrderNumberResolver {
|
export class OrderNumberResolver {
|
||||||
private mysqlService: MySqlService
|
private dbService: IDatabaseService
|
||||||
|
|
||||||
constructor(mysqlService: MySqlService) {
|
constructor(dbService: IDatabaseService) {
|
||||||
this.mysqlService = mysqlService
|
this.dbService = dbService
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -202,7 +202,7 @@ export class OrderNumberResolver {
|
|||||||
private async resolveProductionIds(productionIds: string[]): Promise<OrderMapping[]> {
|
private async resolveProductionIds(productionIds: string[]): Promise<OrderMapping[]> {
|
||||||
const mappings: OrderMapping[] = []
|
const mappings: OrderMapping[] = []
|
||||||
|
|
||||||
if (!this.mysqlService.isConnected()) {
|
if (!this.dbService.isConnected()) {
|
||||||
// Database not connected, return all as failed
|
// Database not connected, return all as failed
|
||||||
for (const pid of productionIds) {
|
for (const pid of productionIds) {
|
||||||
mappings.push({
|
mappings.push({
|
||||||
@@ -217,15 +217,21 @@ export class OrderNumberResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Build query with placeholders
|
// Build query - use different placeholder style based on database type
|
||||||
const placeholders = productionIds.map(() => '?').join(', ')
|
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 = `
|
const query = `
|
||||||
SELECT \`${DB_CONFIG.FIELD_PRODUCTION_ID}\`, \`${DB_CONFIG.FIELD_ORDER_NUMBER}\`
|
SELECT ${quote}${DB_CONFIG.FIELD_PRODUCTION_ID}${quote}, ${quote}${DB_CONFIG.FIELD_ORDER_NUMBER}${quote}
|
||||||
FROM \`${DB_CONFIG.TABLE_NAME}\`
|
FROM ${quote}${DB_CONFIG.TABLE_NAME}${quote}
|
||||||
WHERE \`${DB_CONFIG.FIELD_PRODUCTION_ID}\` IN (${placeholders})
|
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
|
// Create a map for quick lookup
|
||||||
const resultMap = new Map<string, string>()
|
const resultMap = new Map<string, string>()
|
||||||
@@ -281,19 +287,26 @@ export class OrderNumberResolver {
|
|||||||
* @returns List of valid order numbers
|
* @returns List of valid order numbers
|
||||||
*/
|
*/
|
||||||
private async verifyOrderNumbers(orderNumbers: string[]): Promise<string[]> {
|
private async verifyOrderNumbers(orderNumbers: string[]): Promise<string[]> {
|
||||||
if (!this.mysqlService.isConnected()) {
|
if (!this.dbService.isConnected()) {
|
||||||
return orderNumbers // Skip verification if not connected
|
return orderNumbers // Skip verification if not connected
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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 = `
|
const query = `
|
||||||
SELECT \`${DB_CONFIG.FIELD_ORDER_NUMBER}\`
|
SELECT ${quote}${DB_CONFIG.FIELD_ORDER_NUMBER}${quote}
|
||||||
FROM \`${DB_CONFIG.TABLE_NAME}\`
|
FROM ${quote}${DB_CONFIG.TABLE_NAME}${quote}
|
||||||
WHERE \`${DB_CONFIG.FIELD_ORDER_NUMBER}\` IN (${placeholders})
|
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)
|
return result.rows.map((row) => row[DB_CONFIG.FIELD_ORDER_NUMBER] as string)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[OrderResolver] Failed to verify order numbers:', 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