feat: add TypeORM, logger, schemas, hooks and stores
- Add TypeORM integration with data-source, entities and repositories - Add logger service for structured logging - Add Zod validation schemas for auth, cleaner and extractor - Add custom React hooks (useAuth, useCleaner, useExtractor, useValidation) - Add Zustand stores (useAppStore, useUserStore) - Add UI components (Button, Modal, Toast) - Add error types and ErpBrowserManager - Refactor IPC handlers and services - Add unit tests for new modules Co-Authored-By: Claude (glm-5) <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,11 @@
|
||||
|
||||
import { ipcMain } from 'electron'
|
||||
import { SessionManager } from '../services/user/session-manager'
|
||||
import { createLogger } from '../services/logger'
|
||||
import type { UserInfo } from '../types/user.types'
|
||||
|
||||
const log = createLogger('AuthHandler')
|
||||
|
||||
/**
|
||||
* Login request
|
||||
*/
|
||||
@@ -75,110 +78,115 @@ export function registerAuthHandlers(): void {
|
||||
/**
|
||||
* Silent login by computer name
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'auth:silentLogin',
|
||||
async (): Promise<SilentLoginResponse> => {
|
||||
try {
|
||||
const success = await sessionManager.loginByComputerName()
|
||||
const userInfo = sessionManager.getUserInfo()
|
||||
ipcMain.handle('auth:silentLogin', async (): Promise<SilentLoginResponse> => {
|
||||
try {
|
||||
log.info('Attempting silent login')
|
||||
const success = await sessionManager.loginByComputerName()
|
||||
const userInfo = sessionManager.getUserInfo()
|
||||
|
||||
if (success && userInfo) {
|
||||
// Check if admin needs user selection
|
||||
const requiresUserSelection = userInfo.userType === 'Admin'
|
||||
if (success && userInfo) {
|
||||
// Check if admin needs user selection
|
||||
const requiresUserSelection = userInfo.userType === 'Admin'
|
||||
|
||||
return {
|
||||
success: true,
|
||||
userInfo,
|
||||
requiresUserSelection
|
||||
}
|
||||
}
|
||||
log.info('Silent login successful', {
|
||||
username: userInfo.username,
|
||||
userType: userInfo.userType,
|
||||
requiresUserSelection
|
||||
})
|
||||
|
||||
return {
|
||||
success: false,
|
||||
requiresUserSelection: false
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
return {
|
||||
success: false,
|
||||
error: `无感登录失败:${message}`
|
||||
success: true,
|
||||
userInfo,
|
||||
requiresUserSelection
|
||||
}
|
||||
}
|
||||
|
||||
log.warn('Silent login failed - no matching user')
|
||||
return {
|
||||
success: false,
|
||||
requiresUserSelection: false
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
log.error('Silent login error', { error: message })
|
||||
return {
|
||||
success: false,
|
||||
error: `无感登录失败:${message}`
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Login with username and password
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'auth:login',
|
||||
async (_event, request: LoginRequest): Promise<LoginResponse> => {
|
||||
try {
|
||||
const { username, password } = request
|
||||
|
||||
if (!username || !password) {
|
||||
return {
|
||||
success: false,
|
||||
error: '请输入用户名和密码'
|
||||
}
|
||||
}
|
||||
|
||||
const success = await sessionManager.login(username, password)
|
||||
const userInfo = sessionManager.getUserInfo()
|
||||
|
||||
if (success && userInfo) {
|
||||
return {
|
||||
success: true,
|
||||
userInfo
|
||||
}
|
||||
}
|
||||
ipcMain.handle('auth:login', async (_event, request: LoginRequest): Promise<LoginResponse> => {
|
||||
try {
|
||||
const { username, password } = request
|
||||
|
||||
if (!username || !password) {
|
||||
log.warn('Login attempt with missing credentials')
|
||||
return {
|
||||
success: false,
|
||||
error: '用户名或密码错误'
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
return {
|
||||
success: false,
|
||||
error: `登录失败:${message}`
|
||||
error: '请输入用户名和密码'
|
||||
}
|
||||
}
|
||||
|
||||
log.info('Login attempt', { username })
|
||||
const success = await sessionManager.login(username, password)
|
||||
const userInfo = sessionManager.getUserInfo()
|
||||
|
||||
if (success && userInfo) {
|
||||
log.info('Login successful', { username, userType: userInfo.userType })
|
||||
return {
|
||||
success: true,
|
||||
userInfo
|
||||
}
|
||||
}
|
||||
|
||||
log.warn('Login failed - invalid credentials', { username })
|
||||
return {
|
||||
success: false,
|
||||
error: '用户名或密码错误'
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
log.error('Login error', { error: message })
|
||||
return {
|
||||
success: false,
|
||||
error: `登录失败:${message}`
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Logout
|
||||
*/
|
||||
ipcMain.handle('auth:logout', async (): Promise<void> => {
|
||||
const userInfo = sessionManager.getUserInfo()
|
||||
log.info('User logout', { username: userInfo?.username })
|
||||
sessionManager.logout()
|
||||
})
|
||||
|
||||
/**
|
||||
* Get current user
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'auth:getCurrentUser',
|
||||
async (): Promise<CurrentUserResponse> => {
|
||||
const isAuthenticated = sessionManager.isAuthenticated()
|
||||
const userInfo = sessionManager.getUserInfo()
|
||||
ipcMain.handle('auth:getCurrentUser', async (): Promise<CurrentUserResponse> => {
|
||||
const isAuthenticated = sessionManager.isAuthenticated()
|
||||
const userInfo = sessionManager.getUserInfo()
|
||||
|
||||
return {
|
||||
isAuthenticated,
|
||||
userInfo: userInfo ?? undefined
|
||||
}
|
||||
return {
|
||||
isAuthenticated,
|
||||
userInfo: userInfo ?? undefined
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Get all users (for admin user selection)
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'auth:getAllUsers',
|
||||
async (): Promise<UserInfo[]> => {
|
||||
return await sessionManager.getAllUsers()
|
||||
}
|
||||
)
|
||||
ipcMain.handle('auth:getAllUsers', async (): Promise<UserInfo[]> => {
|
||||
log.debug('Fetching all users for admin selection')
|
||||
return await sessionManager.getAllUsers()
|
||||
})
|
||||
|
||||
/**
|
||||
* Switch user (admin only)
|
||||
@@ -187,22 +195,26 @@ export function registerAuthHandlers(): void {
|
||||
'auth:switchUser',
|
||||
async (_event, userInfo: UserInfo): Promise<UserSelectionResponse> => {
|
||||
try {
|
||||
log.info('User switch attempt', { targetUser: userInfo.username })
|
||||
const success = sessionManager.switchUser(userInfo)
|
||||
|
||||
if (success) {
|
||||
const newUser = sessionManager.getUserInfo()
|
||||
log.info('User switch successful', { newUsername: newUser?.username })
|
||||
return {
|
||||
success: true,
|
||||
userInfo: newUser ?? undefined
|
||||
}
|
||||
}
|
||||
|
||||
log.warn('User switch failed')
|
||||
return {
|
||||
success: false,
|
||||
error: '用户切换失败'
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
log.error('User switch error', { error: message })
|
||||
return {
|
||||
success: false,
|
||||
error: `用户切换失败:${message}`
|
||||
@@ -214,10 +226,7 @@ export function registerAuthHandlers(): void {
|
||||
/**
|
||||
* Check if current user is admin
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'auth:isAdmin',
|
||||
async (): Promise<boolean> => {
|
||||
return sessionManager.isAdmin()
|
||||
}
|
||||
)
|
||||
ipcMain.handle('auth:isAdmin', async (): Promise<boolean> => {
|
||||
return sessionManager.isAdmin()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,125 +3,153 @@ import { ErpAuthService } from '../services/erp/erp-auth'
|
||||
import { CleanerService } from '../services/erp/cleaner'
|
||||
import { OrderNumberResolver } from '../services/erp/order-resolver'
|
||||
import { MySqlService } from '../services/database/mysql'
|
||||
import { createLogger } from '../services/logger'
|
||||
import { withErrorHandling, type IpcResult } from './index'
|
||||
import { ErpConnectionError, ValidationError, DatabaseQueryError } from '../types/errors'
|
||||
import type { CleanerInput, CleanerResult } from '../types/cleaner.types'
|
||||
|
||||
const log = createLogger('CleanerHandler')
|
||||
|
||||
/**
|
||||
* Register IPC handlers for cleaner service
|
||||
*/
|
||||
export function registerCleanerHandlers(): void {
|
||||
ipcMain.handle(
|
||||
'cleaner:run',
|
||||
async (
|
||||
_event,
|
||||
input: CleanerInput
|
||||
): Promise<{ success: boolean; data?: CleanerResult; error?: string }> => {
|
||||
let authService: ErpAuthService | null = null
|
||||
let mysqlService: MySqlService | null = null
|
||||
async (_event, input: CleanerInput): Promise<IpcResult<CleanerResult>> => {
|
||||
return withErrorHandling(async () => {
|
||||
let authService: ErpAuthService | null = null
|
||||
let mysqlService: MySqlService | null = null
|
||||
|
||||
try {
|
||||
// Check environment variables
|
||||
const erpUrl = process.env.ERP_URL || ''
|
||||
const erpUsername = process.env.ERP_USERNAME || ''
|
||||
const erpPassword = process.env.ERP_PASSWORD || ''
|
||||
try {
|
||||
// Check environment variables
|
||||
const erpUrl = process.env.ERP_URL || ''
|
||||
const erpUsername = process.env.ERP_USERNAME || ''
|
||||
const erpPassword = process.env.ERP_PASSWORD || ''
|
||||
|
||||
console.log('[Cleaner] Config:', {
|
||||
url: erpUrl ? '***' : 'EMPTY',
|
||||
username: erpUsername ? '***' : 'EMPTY'
|
||||
})
|
||||
log.info('Config check', {
|
||||
url: erpUrl ? 'configured' : 'EMPTY',
|
||||
username: erpUsername ? 'configured' : 'EMPTY'
|
||||
})
|
||||
|
||||
if (!erpUrl || !erpUsername || !erpPassword) {
|
||||
throw new Error(
|
||||
'ERP 配置不完整。请检查 .env 文件中的 ERP_URL, ERP_USERNAME, ERP_PASSWORD'
|
||||
)
|
||||
}
|
||||
if (!erpUrl || !erpUsername || !erpPassword) {
|
||||
throw new ValidationError(
|
||||
'ERP 配置不完整。请检查 .env 文件中的 ERP_URL, ERP_USERNAME, ERP_PASSWORD',
|
||||
'VAL_MISSING_REQUIRED'
|
||||
)
|
||||
}
|
||||
|
||||
// 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 || ''
|
||||
}
|
||||
// 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 || ''
|
||||
}
|
||||
|
||||
console.log('[Cleaner] Connecting to MySQL for order resolution...')
|
||||
mysqlService = new MySqlService(mysqlConfig)
|
||||
await mysqlService.connect()
|
||||
log.info('Connecting to MySQL for order resolution...')
|
||||
mysqlService = new MySqlService(mysqlConfig)
|
||||
|
||||
const resolver = new OrderNumberResolver(mysqlService)
|
||||
const mappings = await resolver.resolve(input.orderNumbers)
|
||||
|
||||
// Get valid order numbers and warnings
|
||||
const validOrderNumbers = resolver.getValidOrderNumbers(mappings)
|
||||
const warnings = resolver.getWarnings(mappings)
|
||||
|
||||
if (warnings.length > 0) {
|
||||
console.warn('[Cleaner] Resolution warnings:', warnings)
|
||||
}
|
||||
|
||||
if (validOrderNumbers.length === 0) {
|
||||
throw new Error('没有有效的生产订单号可处理。请检查输入的格式或数据库连接。')
|
||||
}
|
||||
|
||||
console.log('[Cleaner] Resolved order numbers:', validOrderNumbers)
|
||||
|
||||
// Create auth service and login
|
||||
authService = new ErpAuthService({
|
||||
url: erpUrl,
|
||||
username: erpUsername,
|
||||
password: erpPassword,
|
||||
headless: true
|
||||
})
|
||||
|
||||
console.log('[Cleaner] Logging in to ERP...')
|
||||
await authService.login()
|
||||
console.log('[Cleaner] Login successful')
|
||||
|
||||
// Create cleaner service and run cleaning with resolved order numbers
|
||||
const cleaner = new CleanerService(authService)
|
||||
|
||||
const modifiedInput: CleanerInput = {
|
||||
...input,
|
||||
orderNumbers: validOrderNumbers,
|
||||
onProgress: input.onProgress
|
||||
}
|
||||
|
||||
console.log('[Cleaner] Starting cleaning:', modifiedInput)
|
||||
const result = await cleaner.clean(modifiedInput)
|
||||
console.log('[Cleaner] Cleaning completed:', result)
|
||||
|
||||
// Add warnings to result errors if any
|
||||
if (warnings.length > 0) {
|
||||
result.errors = [...warnings, ...result.errors]
|
||||
}
|
||||
|
||||
return { success: true, data: result }
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
console.error('[Cleaner] Error:', message)
|
||||
console.error('[Cleaner] Stack:', error instanceof Error ? error.stack : 'N/A')
|
||||
return { success: false, error: `清理失败:${message}` }
|
||||
} finally {
|
||||
// Clean up: close browser
|
||||
if (authService) {
|
||||
try {
|
||||
await authService.close()
|
||||
console.log('[Cleaner] Browser closed')
|
||||
} catch (closeError) {
|
||||
console.warn('[Cleaner] Error closing browser:', closeError)
|
||||
await mysqlService.connect()
|
||||
} catch (error) {
|
||||
throw new DatabaseQueryError(
|
||||
'MySQL 连接失败',
|
||||
'DB_CONNECTION_FAILED',
|
||||
error instanceof Error ? error : undefined
|
||||
)
|
||||
}
|
||||
|
||||
const resolver = new OrderNumberResolver(mysqlService)
|
||||
const mappings = await resolver.resolve(input.orderNumbers)
|
||||
|
||||
// Get valid order numbers and warnings
|
||||
const validOrderNumbers = resolver.getValidOrderNumbers(mappings)
|
||||
const warnings = resolver.getWarnings(mappings)
|
||||
|
||||
if (warnings.length > 0) {
|
||||
log.warn('Resolution warnings', { warnings })
|
||||
}
|
||||
|
||||
if (validOrderNumbers.length === 0) {
|
||||
throw new ValidationError(
|
||||
'没有有效的生产订单号可处理。请检查输入的格式或数据库连接。',
|
||||
'VAL_INVALID_INPUT'
|
||||
)
|
||||
}
|
||||
|
||||
log.info('Resolved order numbers', { count: validOrderNumbers.length })
|
||||
|
||||
// Create auth service and login
|
||||
authService = new ErpAuthService({
|
||||
url: erpUrl,
|
||||
username: erpUsername,
|
||||
password: erpPassword,
|
||||
headless: true
|
||||
})
|
||||
|
||||
log.info('Logging in to ERP...')
|
||||
try {
|
||||
await authService.login()
|
||||
} catch (error) {
|
||||
throw new ErpConnectionError(
|
||||
'ERP 登录失败',
|
||||
'ERP_LOGIN_FAILED',
|
||||
error instanceof Error ? error : undefined
|
||||
)
|
||||
}
|
||||
log.info('Login successful')
|
||||
|
||||
// Create cleaner service and run cleaning with resolved order numbers
|
||||
const cleaner = new CleanerService(authService)
|
||||
|
||||
const modifiedInput: CleanerInput = {
|
||||
...input,
|
||||
orderNumbers: validOrderNumbers,
|
||||
onProgress: input.onProgress
|
||||
}
|
||||
|
||||
log.info('Starting cleaning', { orderCount: validOrderNumbers.length })
|
||||
const result = await cleaner.clean(modifiedInput)
|
||||
|
||||
// Add warnings to result errors if any
|
||||
if (warnings.length > 0) {
|
||||
result.errors = [...warnings, ...result.errors]
|
||||
}
|
||||
|
||||
log.info('Cleaning completed', {
|
||||
processedCount: result.ordersProcessed,
|
||||
errorCount: result.errors.length
|
||||
})
|
||||
|
||||
return result
|
||||
} finally {
|
||||
// Clean up: close browser
|
||||
if (authService) {
|
||||
try {
|
||||
await authService.close()
|
||||
log.debug('Browser closed')
|
||||
} catch (closeError) {
|
||||
log.warn('Error closing browser', {
|
||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up: disconnect MySQL
|
||||
if (mysqlService) {
|
||||
try {
|
||||
await mysqlService.disconnect()
|
||||
log.debug('MySQL disconnected')
|
||||
} catch (closeError) {
|
||||
log.warn('Error disconnecting MySQL', {
|
||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up: disconnect MySQL
|
||||
if (mysqlService) {
|
||||
try {
|
||||
await mysqlService.disconnect()
|
||||
console.log('[Cleaner] MySQL disconnected')
|
||||
} catch (closeError) {
|
||||
console.warn('[Cleaner] Error disconnecting MySQL:', closeError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 'cleaner:run')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { MySqlService } from '../services/database/mysql'
|
||||
import { SqlServerService } from '../services/database/sql-server'
|
||||
import { createLogger } from '../services/logger'
|
||||
import { DatabaseQueryError, ValidationError } from '../types/errors'
|
||||
import type {
|
||||
MySqlConfig,
|
||||
MySqlQueryResult,
|
||||
@@ -8,6 +10,8 @@ import type {
|
||||
SqlServerQueryResult
|
||||
} from '../types/ipc-api.types'
|
||||
|
||||
const log = createLogger('DatabaseHandler')
|
||||
|
||||
// Store MySQL service instances per window/connection
|
||||
const mysqlServices = new Map<string, MySqlService>()
|
||||
|
||||
@@ -64,34 +68,47 @@ export function registerDatabaseHandlers(): void {
|
||||
ipcMain.handle('database:mysql:connect', async (event, config: MySqlConfig): Promise<void> => {
|
||||
try {
|
||||
// Use window ID as connection identifier
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const windowId = (event.sender as { id: number }).id.toString()
|
||||
log.info('Connecting to MySQL', { windowId })
|
||||
const service = new MySqlService(config)
|
||||
await service.connect()
|
||||
setMySqlService(windowId, service)
|
||||
log.info('MySQL connected', { windowId })
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to connect to MySQL'
|
||||
throw new Error(message)
|
||||
log.error('MySQL connection failed', { error: message })
|
||||
throw new DatabaseQueryError(
|
||||
message,
|
||||
'DB_CONNECTION_FAILED',
|
||||
error instanceof Error ? error : undefined
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// Disconnect from MySQL
|
||||
ipcMain.handle('database:mysql:disconnect', async (event): Promise<void> => {
|
||||
try {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const windowId = (event.sender as { id: number }).id.toString()
|
||||
const service = getMySqlService(windowId)
|
||||
if (service) {
|
||||
await service.disconnect()
|
||||
deleteMySqlService(windowId)
|
||||
log.info('MySQL disconnected', { windowId })
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to disconnect from MySQL'
|
||||
throw new Error(message)
|
||||
log.error('MySQL disconnect failed', { error: message })
|
||||
throw new DatabaseQueryError(
|
||||
message,
|
||||
'DB_CONNECTION_FAILED',
|
||||
error instanceof Error ? error : undefined
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// Check if MySQL is connected
|
||||
ipcMain.handle('database:mysql:isConnected', async (event): Promise<boolean> => {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const windowId = (event.sender as { id: number }).id.toString()
|
||||
const service = getMySqlService(windowId)
|
||||
return service ? service.isConnected() : false
|
||||
})
|
||||
@@ -99,19 +116,28 @@ export function registerDatabaseHandlers(): void {
|
||||
// Execute MySQL query
|
||||
ipcMain.handle(
|
||||
'database:mysql:query',
|
||||
async (event, sql: string, params?: any[]): Promise<MySqlQueryResult> => {
|
||||
async (event, sql: string, params?: unknown[]): Promise<MySqlQueryResult> => {
|
||||
try {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const windowId = (event.sender as { id: number }).id.toString()
|
||||
const service = getMySqlService(windowId)
|
||||
|
||||
if (!service) {
|
||||
throw new Error('Not connected to MySQL. Call connect() first.')
|
||||
throw new ValidationError(
|
||||
'Not connected to MySQL. Call connect() first.',
|
||||
'VAL_INVALID_INPUT'
|
||||
)
|
||||
}
|
||||
|
||||
log.debug('Executing MySQL query', { windowId, sql: sql.substring(0, 100) })
|
||||
return await service.query(sql, params)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'MySQL query failed'
|
||||
throw new Error(message)
|
||||
log.error('MySQL query failed', { error: message })
|
||||
throw new DatabaseQueryError(
|
||||
message,
|
||||
'DB_QUERY_FAILED',
|
||||
error instanceof Error ? error : undefined
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -121,13 +147,20 @@ export function registerDatabaseHandlers(): void {
|
||||
'database:sqlserver:connect',
|
||||
async (event, config: SqlServerConfig): Promise<void> => {
|
||||
try {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const windowId = (event.sender as { id: number }).id.toString()
|
||||
log.info('Connecting to SQL Server', { windowId })
|
||||
const service = new SqlServerService(config)
|
||||
await service.connect()
|
||||
setSqlServerService(windowId, service)
|
||||
log.info('SQL Server connected', { windowId })
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to connect to SQL Server'
|
||||
throw new Error(message)
|
||||
log.error('SQL Server connection failed', { error: message })
|
||||
throw new DatabaseQueryError(
|
||||
message,
|
||||
'DB_CONNECTION_FAILED',
|
||||
error instanceof Error ? error : undefined
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -135,22 +168,28 @@ export function registerDatabaseHandlers(): void {
|
||||
// Disconnect from SQL Server
|
||||
ipcMain.handle('database:sqlserver:disconnect', async (event): Promise<void> => {
|
||||
try {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const windowId = (event.sender as { id: number }).id.toString()
|
||||
const service = getSqlServerService(windowId)
|
||||
if (service) {
|
||||
await service.disconnect()
|
||||
deleteSqlServerService(windowId)
|
||||
log.info('SQL Server disconnected', { windowId })
|
||||
}
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : 'Failed to disconnect from SQL Server'
|
||||
throw new Error(message)
|
||||
log.error('SQL Server disconnect failed', { error: message })
|
||||
throw new DatabaseQueryError(
|
||||
message,
|
||||
'DB_CONNECTION_FAILED',
|
||||
error instanceof Error ? error : undefined
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// Check if SQL Server is connected
|
||||
ipcMain.handle('database:sqlserver:isConnected', async (event): Promise<boolean> => {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const windowId = (event.sender as { id: number }).id.toString()
|
||||
const service = getSqlServerService(windowId)
|
||||
return service ? service.isConnected() : false
|
||||
})
|
||||
@@ -164,17 +203,26 @@ export function registerDatabaseHandlers(): void {
|
||||
params?: Record<string, unknown>
|
||||
): Promise<SqlServerQueryResult> => {
|
||||
try {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const windowId = (event.sender as { id: number }).id.toString()
|
||||
const service = getSqlServerService(windowId)
|
||||
|
||||
if (!service) {
|
||||
throw new Error('Not connected to SQL Server. Call connect() first.')
|
||||
throw new ValidationError(
|
||||
'Not connected to SQL Server. Call connect() first.',
|
||||
'VAL_INVALID_INPUT'
|
||||
)
|
||||
}
|
||||
|
||||
log.debug('Executing SQL Server query', { windowId, sql: sqlString.substring(0, 100) })
|
||||
return await service.query(sqlString, params)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'SQL Server query failed'
|
||||
throw new Error(message)
|
||||
log.error('SQL Server query failed', { error: message })
|
||||
throw new DatabaseQueryError(
|
||||
message,
|
||||
'DB_QUERY_FAILED',
|
||||
error instanceof Error ? error : undefined
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -3,125 +3,152 @@ 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 { createLogger } from '../services/logger'
|
||||
import { withErrorHandling, type IpcResult } from './index'
|
||||
import { ErpConnectionError, ValidationError, DatabaseQueryError } from '../types/errors'
|
||||
import type { ExtractorInput, ExtractorResult } from '../types/extractor.types'
|
||||
|
||||
const log = createLogger('ExtractorHandler')
|
||||
|
||||
/**
|
||||
* Register IPC handlers for extractor service
|
||||
*/
|
||||
export function registerExtractorHandlers(): void {
|
||||
ipcMain.handle(
|
||||
'extractor:run',
|
||||
async (
|
||||
_event,
|
||||
input: ExtractorInput
|
||||
): Promise<{ success: boolean; data?: ExtractorResult; error?: string }> => {
|
||||
let authService: ErpAuthService | null = null
|
||||
let mysqlService: MySqlService | null = null
|
||||
async (_event, input: ExtractorInput): Promise<IpcResult<ExtractorResult>> => {
|
||||
return withErrorHandling(async () => {
|
||||
let authService: ErpAuthService | null = null
|
||||
let mysqlService: MySqlService | null = null
|
||||
|
||||
try {
|
||||
// Check environment variables
|
||||
const erpUrl = process.env.ERP_URL || ''
|
||||
const erpUsername = process.env.ERP_USERNAME || ''
|
||||
const erpPassword = process.env.ERP_PASSWORD || ''
|
||||
try {
|
||||
// Check environment variables
|
||||
const erpUrl = process.env.ERP_URL || ''
|
||||
const erpUsername = process.env.ERP_USERNAME || ''
|
||||
const erpPassword = process.env.ERP_PASSWORD || ''
|
||||
|
||||
console.log('[Extractor] Config:', {
|
||||
url: erpUrl ? '***' : 'EMPTY',
|
||||
username: erpUsername ? '***' : 'EMPTY'
|
||||
})
|
||||
log.info('Config check', {
|
||||
url: erpUrl ? 'configured' : 'EMPTY',
|
||||
username: erpUsername ? 'configured' : 'EMPTY'
|
||||
})
|
||||
|
||||
if (!erpUrl || !erpUsername || !erpPassword) {
|
||||
throw new Error(
|
||||
'ERP 配置不完整。请检查 .env 文件中的 ERP_URL, ERP_USERNAME, ERP_PASSWORD'
|
||||
)
|
||||
}
|
||||
if (!erpUrl || !erpUsername || !erpPassword) {
|
||||
throw new ValidationError(
|
||||
'ERP 配置不完整。请检查 .env 文件中的 ERP_URL, ERP_USERNAME, ERP_PASSWORD',
|
||||
'VAL_MISSING_REQUIRED'
|
||||
)
|
||||
}
|
||||
|
||||
// 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 || ''
|
||||
}
|
||||
// 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 || ''
|
||||
}
|
||||
|
||||
console.log('[Extractor] Connecting to MySQL for order resolution...')
|
||||
mysqlService = new MySqlService(mysqlConfig)
|
||||
await mysqlService.connect()
|
||||
log.info('Connecting to MySQL for order resolution...')
|
||||
mysqlService = new MySqlService(mysqlConfig)
|
||||
|
||||
const resolver = new OrderNumberResolver(mysqlService)
|
||||
const mappings = await resolver.resolve(input.orderNumbers)
|
||||
|
||||
// Get valid order numbers and warnings
|
||||
const validOrderNumbers = resolver.getValidOrderNumbers(mappings)
|
||||
const warnings = resolver.getWarnings(mappings)
|
||||
|
||||
if (warnings.length > 0) {
|
||||
console.warn('[Extractor] Resolution warnings:', warnings)
|
||||
}
|
||||
|
||||
if (validOrderNumbers.length === 0) {
|
||||
throw new Error('没有有效的生产订单号可处理。请检查输入的格式或数据库连接。')
|
||||
}
|
||||
|
||||
console.log('[Extractor] Resolved order numbers:', validOrderNumbers)
|
||||
|
||||
// Create auth service and login
|
||||
authService = new ErpAuthService({
|
||||
url: erpUrl,
|
||||
username: erpUsername,
|
||||
password: erpPassword,
|
||||
headless: true
|
||||
})
|
||||
|
||||
console.log('[Extractor] Logging in to ERP...')
|
||||
await authService.login()
|
||||
console.log('[Extractor] Login successful')
|
||||
|
||||
// Create extractor service and run extraction with resolved order numbers
|
||||
const extractor = new ExtractorService(authService)
|
||||
console.log('[Extractor] Starting extraction for orders:', validOrderNumbers)
|
||||
|
||||
const modifiedInput: ExtractorInput = {
|
||||
...input,
|
||||
orderNumbers: validOrderNumbers
|
||||
}
|
||||
|
||||
const result = await extractor.extract(modifiedInput)
|
||||
|
||||
// Add warnings to result errors if any
|
||||
if (warnings.length > 0) {
|
||||
result.errors = [...warnings, ...result.errors]
|
||||
}
|
||||
|
||||
console.log('[Extractor] Extraction completed:', result)
|
||||
|
||||
return { success: true, data: result }
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
console.error('[Extractor] Error:', message)
|
||||
console.error('[Extractor] Stack:', error instanceof Error ? error.stack : 'N/A')
|
||||
return { success: false, error: `提取失败:${message}` }
|
||||
} finally {
|
||||
// Clean up: close browser
|
||||
if (authService) {
|
||||
try {
|
||||
await authService.close()
|
||||
console.log('[Extractor] Browser closed')
|
||||
} catch (closeError) {
|
||||
console.warn('[Extractor] Error closing browser:', closeError)
|
||||
await mysqlService.connect()
|
||||
} catch (error) {
|
||||
throw new DatabaseQueryError(
|
||||
'MySQL 连接失败',
|
||||
'DB_CONNECTION_FAILED',
|
||||
error instanceof Error ? error : undefined
|
||||
)
|
||||
}
|
||||
|
||||
const resolver = new OrderNumberResolver(mysqlService)
|
||||
const mappings = await resolver.resolve(input.orderNumbers)
|
||||
|
||||
// Get valid order numbers and warnings
|
||||
const validOrderNumbers = resolver.getValidOrderNumbers(mappings)
|
||||
const warnings = resolver.getWarnings(mappings)
|
||||
|
||||
if (warnings.length > 0) {
|
||||
log.warn('Resolution warnings', { warnings })
|
||||
}
|
||||
|
||||
if (validOrderNumbers.length === 0) {
|
||||
throw new ValidationError(
|
||||
'没有有效的生产订单号可处理。请检查输入的格式或数据库连接。',
|
||||
'VAL_INVALID_INPUT'
|
||||
)
|
||||
}
|
||||
|
||||
log.info('Resolved order numbers', { count: validOrderNumbers.length })
|
||||
|
||||
// Create auth service and login
|
||||
authService = new ErpAuthService({
|
||||
url: erpUrl,
|
||||
username: erpUsername,
|
||||
password: erpPassword,
|
||||
headless: true
|
||||
})
|
||||
|
||||
log.info('Logging in to ERP...')
|
||||
try {
|
||||
await authService.login()
|
||||
} catch (error) {
|
||||
throw new ErpConnectionError(
|
||||
'ERP 登录失败',
|
||||
'ERP_LOGIN_FAILED',
|
||||
error instanceof Error ? error : undefined
|
||||
)
|
||||
}
|
||||
log.info('Login successful')
|
||||
|
||||
// Create extractor service and run extraction with resolved order numbers
|
||||
const extractor = new ExtractorService(authService)
|
||||
log.info('Starting extraction', { orderCount: validOrderNumbers.length })
|
||||
|
||||
const modifiedInput: ExtractorInput = {
|
||||
...input,
|
||||
orderNumbers: validOrderNumbers
|
||||
}
|
||||
|
||||
const result = await extractor.extract(modifiedInput)
|
||||
|
||||
// Add warnings to result errors if any
|
||||
if (warnings.length > 0) {
|
||||
result.errors = [...warnings, ...result.errors]
|
||||
}
|
||||
|
||||
log.info('Extraction completed', {
|
||||
rowCount: result.recordCount,
|
||||
errorCount: result.errors.length
|
||||
})
|
||||
|
||||
return result
|
||||
} finally {
|
||||
// Clean up: close browser
|
||||
if (authService) {
|
||||
try {
|
||||
await authService.close()
|
||||
log.debug('Browser closed')
|
||||
} catch (closeError) {
|
||||
log.warn('Error closing browser', {
|
||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up: disconnect MySQL
|
||||
if (mysqlService) {
|
||||
try {
|
||||
await mysqlService.disconnect()
|
||||
log.debug('MySQL disconnected')
|
||||
} catch (closeError) {
|
||||
log.warn('Error disconnecting MySQL', {
|
||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up: disconnect MySQL
|
||||
if (mysqlService) {
|
||||
try {
|
||||
await mysqlService.disconnect()
|
||||
console.log('[Extractor] MySQL disconnected')
|
||||
} catch (closeError) {
|
||||
console.warn('[Extractor] Error disconnecting MySQL:', closeError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 'extractor:run')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import * as fs from 'fs/promises'
|
||||
import * as path from 'path'
|
||||
import { createLogger } from '../services/logger'
|
||||
|
||||
const log = createLogger('FileHandler')
|
||||
|
||||
/**
|
||||
* Register IPC handlers for file operations
|
||||
@@ -9,9 +12,11 @@ export function registerFileHandlers(): void {
|
||||
// Read file content
|
||||
ipcMain.handle('file:read', async (_event, filePath: string): Promise<string> => {
|
||||
try {
|
||||
log.debug('Reading file', { filePath })
|
||||
return await fs.readFile(filePath, 'utf-8')
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to read file'
|
||||
log.error('Failed to read file', { filePath, error: message })
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
@@ -19,12 +24,14 @@ export function registerFileHandlers(): void {
|
||||
// Write content to file
|
||||
ipcMain.handle('file:write', async (_event, filePath: string, content: string): Promise<void> => {
|
||||
try {
|
||||
log.debug('Writing file', { filePath })
|
||||
// Ensure directory exists
|
||||
const dir = path.dirname(filePath)
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
await fs.writeFile(filePath, content, 'utf-8')
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to write file'
|
||||
log.error('Failed to write file', { filePath, error: message })
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
@@ -42,6 +49,7 @@ export function registerFileHandlers(): void {
|
||||
// List files in directory
|
||||
ipcMain.handle('file:list', async (_event, dirPath: string): Promise<string[]> => {
|
||||
try {
|
||||
log.debug('Listing directory', { dirPath })
|
||||
const entries = await fs.readdir(dirPath, { withFileTypes: true })
|
||||
return entries
|
||||
.filter((entry) => entry.isFile())
|
||||
@@ -49,6 +57,7 @@ export function registerFileHandlers(): void {
|
||||
.sort()
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to list directory'
|
||||
log.error('Failed to list directory', { dirPath, error: message })
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -11,11 +11,60 @@ import { registerResolverHandlers } from './resolver-handler'
|
||||
import { registerAuthHandlers } from './auth-handler'
|
||||
import { registerValidationHandlers } from './validation-handler'
|
||||
import { registerSettingsHandlers } from './settings-handler'
|
||||
import { createLogger } from '../services/logger'
|
||||
import { getErrorMessage, getErrorCode, isBaseError } from '../types/errors'
|
||||
|
||||
const log = createLogger('IPC')
|
||||
|
||||
/**
|
||||
* Standard result type for all IPC handlers
|
||||
*/
|
||||
export interface IpcResult<T = unknown> {
|
||||
success: boolean
|
||||
data?: T
|
||||
error?: string
|
||||
code?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Higher-order function to wrap IPC handlers with consistent error handling
|
||||
* @param handler - The async handler function to wrap
|
||||
* @param context - The context name for logging
|
||||
* @returns A wrapped handler that returns IpcResult
|
||||
*/
|
||||
export function withErrorHandling<T>(
|
||||
handler: () => Promise<T>,
|
||||
context: string
|
||||
): Promise<IpcResult<T>> {
|
||||
return handler()
|
||||
.then((data) => {
|
||||
log.debug(`[${context}] Handler completed successfully`)
|
||||
return { success: true, data }
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
const message = getErrorMessage(error)
|
||||
const code = getErrorCode(error)
|
||||
|
||||
if (isBaseError(error)) {
|
||||
log.error(`[${context}] ${error.name}: ${message}`, { code, cause: error.cause?.message })
|
||||
} else {
|
||||
log.error(`[${context}] Error: ${message}`, { code })
|
||||
}
|
||||
|
||||
// Include stack trace in development
|
||||
if (process.env.NODE_ENV !== 'production' && error instanceof Error) {
|
||||
log.debug(`[${context}] Stack trace:`, { stack: error.stack })
|
||||
}
|
||||
|
||||
return { success: false, error: message, code }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all IPC handlers
|
||||
*/
|
||||
export function registerIpcHandlers(): void {
|
||||
log.info('Registering IPC handlers...')
|
||||
registerFileHandlers()
|
||||
registerExtractorHandlers()
|
||||
registerCleanerHandlers()
|
||||
@@ -24,4 +73,5 @@ export function registerIpcHandlers(): void {
|
||||
registerAuthHandlers()
|
||||
registerValidationHandlers()
|
||||
registerSettingsHandlers()
|
||||
log.info('All IPC handlers registered')
|
||||
}
|
||||
|
||||
@@ -9,8 +9,12 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { MySqlService } from '../services/database/mysql'
|
||||
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')
|
||||
|
||||
/**
|
||||
* Resolver input from renderer
|
||||
*/
|
||||
@@ -69,6 +73,7 @@ export function registerResolverHandlers(): void {
|
||||
}
|
||||
|
||||
// Create MySQL service
|
||||
log.info('Connecting to MySQL for resolution', { inputCount: input.inputs.length })
|
||||
mysqlService = new MySqlService(mysqlConfig)
|
||||
await mysqlService.connect()
|
||||
|
||||
@@ -81,6 +86,12 @@ export function registerResolverHandlers(): void {
|
||||
const warnings = resolver.getWarnings(mappings)
|
||||
const stats = resolver.getStats(mappings)
|
||||
|
||||
log.info('Resolution completed', {
|
||||
inputCount: input.inputs.length,
|
||||
validCount: validOrderNumbers.length,
|
||||
warningCount: warnings.length
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
mappings,
|
||||
@@ -90,6 +101,7 @@ export function registerResolverHandlers(): void {
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
log.error('Resolution failed', { error: message })
|
||||
return {
|
||||
success: false,
|
||||
error: `解析失败:${message}`
|
||||
@@ -99,8 +111,11 @@ export function registerResolverHandlers(): void {
|
||||
if (mysqlService) {
|
||||
try {
|
||||
await mysqlService.disconnect()
|
||||
log.debug('MySQL disconnected')
|
||||
} catch (closeError) {
|
||||
console.warn('[Resolver] Error disconnecting MySQL:', closeError)
|
||||
log.warn('Error disconnecting MySQL', {
|
||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,7 +127,10 @@ export function registerResolverHandlers(): void {
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'resolver:validateFormat',
|
||||
async (_event, inputs: string[]): Promise<{
|
||||
async (
|
||||
_event,
|
||||
inputs: string[]
|
||||
): Promise<{
|
||||
success: boolean
|
||||
results?: Array<{ input: string; type: 'productionId' | 'orderNumber' | 'unknown' }>
|
||||
error?: string
|
||||
@@ -122,14 +140,17 @@ export function registerResolverHandlers(): void {
|
||||
isConnected: () => false
|
||||
} as MySqlService)
|
||||
|
||||
const results = inputs.map(input => ({
|
||||
const results = inputs.map((input) => ({
|
||||
input,
|
||||
type: resolver.recognizeType(input)
|
||||
}))
|
||||
|
||||
log.debug('Format validation completed', { inputCount: inputs.length })
|
||||
|
||||
return { success: true, results }
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
log.error('Format validation failed', { error: message })
|
||||
return {
|
||||
success: false,
|
||||
error: `验证失败:${message}`
|
||||
|
||||
@@ -14,6 +14,7 @@ import { SessionManager } from '../services/user/session-manager'
|
||||
import { ErpAuthService } from '../services/erp/erp-auth'
|
||||
import { MySqlService } from '../services/database/mysql'
|
||||
import { SqlServerService } from '../services/database/sql-server'
|
||||
import { createLogger } from '../services/logger'
|
||||
import type {
|
||||
SettingsData,
|
||||
UserType,
|
||||
@@ -21,14 +22,13 @@ import type {
|
||||
SaveSettingsResult
|
||||
} from '../types/settings.types'
|
||||
|
||||
const log = createLogger('SettingsHandler')
|
||||
|
||||
/**
|
||||
* Filter settings by user type
|
||||
* Admin users get all settings, User users get limited settings
|
||||
*/
|
||||
function filterSettingsByUserType(
|
||||
settings: SettingsData,
|
||||
userType: UserType
|
||||
): SettingsData {
|
||||
function filterSettingsByUserType(settings: SettingsData, userType: UserType): SettingsData {
|
||||
if (userType === 'Admin') {
|
||||
return settings // Return all settings for Admin
|
||||
}
|
||||
@@ -72,6 +72,7 @@ export function registerSettingsHandlers(): void {
|
||||
*/
|
||||
ipcMain.handle('settings:getSettings', async (): Promise<SettingsData> => {
|
||||
const userType = (sessionManager.getUserType() as UserType) || 'Guest'
|
||||
log.debug('Getting settings', { userType })
|
||||
const settings = configManager.getAllSettings()
|
||||
return filterSettingsByUserType(settings, userType)
|
||||
})
|
||||
@@ -83,14 +84,18 @@ export function registerSettingsHandlers(): void {
|
||||
'settings:saveSettings',
|
||||
async (_event, settings: SettingsData): Promise<SaveSettingsResult> => {
|
||||
try {
|
||||
log.info('Saving settings')
|
||||
const success = await configManager.saveAllSettings(settings)
|
||||
if (success) {
|
||||
log.info('Settings saved successfully')
|
||||
return { success: true }
|
||||
} else {
|
||||
log.warn('Failed to save settings')
|
||||
return { success: false, error: '保存设置失败' }
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
log.error('Error saving settings', { error: message })
|
||||
return { success: false, error: `保存设置失败:${message}` }
|
||||
}
|
||||
}
|
||||
@@ -99,157 +104,166 @@ export function registerSettingsHandlers(): void {
|
||||
/**
|
||||
* Reset to default settings (Admin only)
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'settings:resetDefaults',
|
||||
async (): Promise<SaveSettingsResult> => {
|
||||
try {
|
||||
const userType = sessionManager.getUserType()
|
||||
if (userType !== 'Admin') {
|
||||
return { success: false, error: '只有管理员可以恢复默认设置' }
|
||||
}
|
||||
|
||||
configManager.resetToDefaults()
|
||||
const success = await configManager.save()
|
||||
if (success) {
|
||||
return { success: true }
|
||||
} else {
|
||||
return { success: false, error: '恢复默认设置失败' }
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
return { success: false, error: `恢复默认设置失败:${message}` }
|
||||
ipcMain.handle('settings:resetDefaults', async (): Promise<SaveSettingsResult> => {
|
||||
try {
|
||||
const userType = sessionManager.getUserType()
|
||||
if (userType !== 'Admin') {
|
||||
log.warn('Non-admin user attempted to reset defaults', { userType })
|
||||
return { success: false, error: '只有管理员可以恢复默认设置' }
|
||||
}
|
||||
|
||||
log.info('Resetting settings to defaults')
|
||||
configManager.resetToDefaults()
|
||||
const success = await configManager.save()
|
||||
if (success) {
|
||||
log.info('Settings reset to defaults successfully')
|
||||
return { success: true }
|
||||
} else {
|
||||
log.warn('Failed to reset settings to defaults')
|
||||
return { success: false, error: '恢复默认设置失败' }
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
log.error('Error resetting settings', { error: message })
|
||||
return { success: false, error: `恢复默认设置失败:${message}` }
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Test ERP connection
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'settings:testErpConnection',
|
||||
async (): Promise<ConnectionTestResult> => {
|
||||
try {
|
||||
const settings = configManager.getAllSettings()
|
||||
const erpConfig = settings.erp
|
||||
ipcMain.handle('settings:testErpConnection', async (): Promise<ConnectionTestResult> => {
|
||||
try {
|
||||
log.info('Testing ERP connection')
|
||||
const settings = configManager.getAllSettings()
|
||||
const erpConfig = settings.erp
|
||||
|
||||
if (!erpConfig.url || !erpConfig.username || !erpConfig.password) {
|
||||
return {
|
||||
success: false,
|
||||
message: '请先配置 ERP URL、用户名和密码'
|
||||
}
|
||||
}
|
||||
|
||||
// Create ERP auth service and try to login
|
||||
const erpAuthService = new ErpAuthService(erpConfig)
|
||||
|
||||
try {
|
||||
await erpAuthService.login()
|
||||
// Login successful, close browser
|
||||
await erpAuthService.close()
|
||||
return {
|
||||
success: true,
|
||||
message: 'ERP 连接测试成功!'
|
||||
}
|
||||
} catch (loginError) {
|
||||
const errorMessage = loginError instanceof Error ? loginError.message : '登录失败'
|
||||
return {
|
||||
success: false,
|
||||
message: `ERP 连接测试失败:${errorMessage}`
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
if (!erpConfig.url || !erpConfig.username || !erpConfig.password) {
|
||||
log.warn('ERP connection test failed - missing configuration')
|
||||
return {
|
||||
success: false,
|
||||
message: `ERP 连接测试失败:${message}`
|
||||
message: '请先配置 ERP URL、用户名和密码'
|
||||
}
|
||||
}
|
||||
|
||||
// Create ERP auth service and try to login
|
||||
const erpAuthService = new ErpAuthService(erpConfig)
|
||||
|
||||
try {
|
||||
await erpAuthService.login()
|
||||
// Login successful, close browser
|
||||
await erpAuthService.close()
|
||||
log.info('ERP connection test successful')
|
||||
return {
|
||||
success: true,
|
||||
message: 'ERP 连接测试成功!'
|
||||
}
|
||||
} catch (loginError) {
|
||||
const errorMessage = loginError instanceof Error ? loginError.message : '登录失败'
|
||||
log.error('ERP login failed', { error: errorMessage })
|
||||
return {
|
||||
success: false,
|
||||
message: `ERP 连接测试失败:${errorMessage}`
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
log.error('ERP connection test error', { error: message })
|
||||
return {
|
||||
success: false,
|
||||
message: `ERP 连接测试失败:${message}`
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Test database connection
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'settings:testDbConnection',
|
||||
async (): Promise<ConnectionTestResult> => {
|
||||
try {
|
||||
const settings = configManager.getAllSettings()
|
||||
const dbConfig = settings.database
|
||||
ipcMain.handle('settings:testDbConnection', async (): Promise<ConnectionTestResult> => {
|
||||
try {
|
||||
log.info('Testing database connection')
|
||||
const settings = configManager.getAllSettings()
|
||||
const dbConfig = settings.database
|
||||
|
||||
if (dbConfig.dbType === 'mysql') {
|
||||
// Test MySQL connection
|
||||
if (!dbConfig.mysqlHost || !dbConfig.database || !dbConfig.username) {
|
||||
return {
|
||||
success: false,
|
||||
message: '请先配置 MySQL 主机、数据库名和用户名'
|
||||
}
|
||||
}
|
||||
|
||||
const mysqlService = new MySqlService({
|
||||
host: dbConfig.mysqlHost,
|
||||
port: dbConfig.mysqlPort,
|
||||
user: dbConfig.username,
|
||||
password: dbConfig.password,
|
||||
database: dbConfig.database
|
||||
})
|
||||
|
||||
try {
|
||||
await mysqlService.connect()
|
||||
await mysqlService.disconnect()
|
||||
return {
|
||||
success: true,
|
||||
message: 'MySQL 数据库连接测试成功!'
|
||||
}
|
||||
} catch (connError) {
|
||||
const errorMessage = connError instanceof Error ? connError.message : '连接失败'
|
||||
return {
|
||||
success: false,
|
||||
message: `MySQL 数据库连接测试失败:${errorMessage}`
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Test SQL Server connection
|
||||
if (!dbConfig.server || !dbConfig.database || !dbConfig.username) {
|
||||
return {
|
||||
success: false,
|
||||
message: '请先配置 SQL Server 服务器、数据库名和用户名'
|
||||
}
|
||||
}
|
||||
|
||||
const sqlServerService = new SqlServerService({
|
||||
server: dbConfig.server,
|
||||
port: 1433, // Default SQL Server port
|
||||
user: dbConfig.username,
|
||||
password: dbConfig.password,
|
||||
database: dbConfig.database,
|
||||
options: {
|
||||
trustServerCertificate: true
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
await sqlServerService.connect()
|
||||
await sqlServerService.disconnect()
|
||||
return {
|
||||
success: true,
|
||||
message: 'SQL Server 数据库连接测试成功!'
|
||||
}
|
||||
} catch (connError) {
|
||||
const errorMessage = connError instanceof Error ? connError.message : '连接失败'
|
||||
return {
|
||||
success: false,
|
||||
message: `SQL Server 数据库连接测试失败:${errorMessage}`
|
||||
}
|
||||
if (dbConfig.dbType === 'mysql') {
|
||||
// Test MySQL connection
|
||||
if (!dbConfig.mysqlHost || !dbConfig.database || !dbConfig.username) {
|
||||
log.warn('MySQL connection test failed - missing configuration')
|
||||
return {
|
||||
success: false,
|
||||
message: '请先配置 MySQL 主机、数据库名和用户名'
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
return {
|
||||
success: false,
|
||||
message: `数据库连接测试失败:${message}`
|
||||
|
||||
const mysqlService = new MySqlService({
|
||||
host: dbConfig.mysqlHost,
|
||||
port: dbConfig.mysqlPort,
|
||||
user: dbConfig.username,
|
||||
password: dbConfig.password,
|
||||
database: dbConfig.database
|
||||
})
|
||||
|
||||
try {
|
||||
await mysqlService.connect()
|
||||
await mysqlService.disconnect()
|
||||
log.info('MySQL connection test successful')
|
||||
return {
|
||||
success: true,
|
||||
message: 'MySQL 数据库连接测试成功!'
|
||||
}
|
||||
} catch (connError) {
|
||||
const errorMessage = connError instanceof Error ? connError.message : '连接失败'
|
||||
log.error('MySQL connection failed', { error: errorMessage })
|
||||
return {
|
||||
success: false,
|
||||
message: `MySQL 数据库连接测试失败:${errorMessage}`
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Test SQL Server connection
|
||||
if (!dbConfig.server || !dbConfig.database || !dbConfig.username) {
|
||||
log.warn('SQL Server connection test failed - missing configuration')
|
||||
return {
|
||||
success: false,
|
||||
message: '请先配置 SQL Server 服务器、数据库名和用户名'
|
||||
}
|
||||
}
|
||||
|
||||
const sqlServerService = new SqlServerService({
|
||||
server: dbConfig.server,
|
||||
port: 1433, // Default SQL Server port
|
||||
user: dbConfig.username,
|
||||
password: dbConfig.password,
|
||||
database: dbConfig.database,
|
||||
options: {
|
||||
trustServerCertificate: true
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
await sqlServerService.connect()
|
||||
await sqlServerService.disconnect()
|
||||
log.info('SQL Server connection test successful')
|
||||
return {
|
||||
success: true,
|
||||
message: 'SQL Server 数据库连接测试成功!'
|
||||
}
|
||||
} catch (connError) {
|
||||
const errorMessage = connError instanceof Error ? connError.message : '连接失败'
|
||||
log.error('SQL Server connection failed', { error: errorMessage })
|
||||
return {
|
||||
success: false,
|
||||
message: `SQL Server 数据库连接测试失败:${errorMessage}`
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
log.error('Database connection test error', { error: message })
|
||||
return {
|
||||
success: false,
|
||||
message: `数据库连接测试失败:${message}`
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { MySqlService } from '../services/database/mysql'
|
||||
import { SqlServerService } from '../services/database/sql-server'
|
||||
import { MaterialsToBeDeletedDAO } from '../services/database/materials-to-be-deleted-dao'
|
||||
import { DiscreteMaterialPlanDAO } from '../services/database/discrete-material-plan-dao'
|
||||
import { createLogger } from '../services/logger'
|
||||
import type {
|
||||
ValidationRequest,
|
||||
ValidationResponse,
|
||||
@@ -22,6 +23,8 @@ import type {
|
||||
MaterialRecordSummary
|
||||
} from '../types/validation.types'
|
||||
|
||||
const log = createLogger('ValidationHandler')
|
||||
|
||||
/**
|
||||
* Shared state for Production IDs from extractor page
|
||||
* This is a simple in-memory store for sharing Production IDs between pages
|
||||
@@ -32,7 +35,7 @@ const sharedProductionIds = new Set<string>()
|
||||
* Set shared Production IDs
|
||||
*/
|
||||
export function setSharedProductionIds(ids: string[]): void {
|
||||
ids.forEach(id => sharedProductionIds.add(id))
|
||||
ids.forEach((id) => sharedProductionIds.add(id))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,11 +113,11 @@ function getTableName(mysqlTableName: string): string {
|
||||
*/
|
||||
function readProductionIds(filePath: string): string[] {
|
||||
const fs = require('fs')
|
||||
const content = fs.readFileSync(filePath, 'utf-8')
|
||||
const content = fs.readFileSync(filePath, 'utf-8') as string
|
||||
return content
|
||||
.split('\n')
|
||||
.map(line => line.trim())
|
||||
.filter(line => line.length > 0)
|
||||
.map((line: string) => line.trim())
|
||||
.filter((line: string) => line.length > 0)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,10 +176,11 @@ async function getSourceNumbersFromInputs(
|
||||
FROM ${contractTableName}
|
||||
WHERE 总排号 IN (${placeholders})
|
||||
`
|
||||
const contractResult = await (dbService as SqlServerService).queryWithParams(contractSql, params)
|
||||
const dbOrderNumbers = contractResult.rows.map(
|
||||
row => row.生产订单号 as string
|
||||
const contractResult = await (dbService as SqlServerService).queryWithParams(
|
||||
contractSql,
|
||||
params
|
||||
)
|
||||
const dbOrderNumbers = contractResult.rows.map((row) => row.生产订单号 as string)
|
||||
orderNumbers.push(...dbOrderNumbers)
|
||||
} else {
|
||||
const placeholders = productionIds.map(() => '?').join(',')
|
||||
@@ -186,9 +190,7 @@ async function getSourceNumbersFromInputs(
|
||||
WHERE 总排号 IN (${placeholders})
|
||||
`
|
||||
const contractResult = await (dbService as MySqlService).query(contractSql, productionIds)
|
||||
const dbOrderNumbers = contractResult.rows.map(
|
||||
row => row.生产订单号 as string
|
||||
)
|
||||
const dbOrderNumbers = contractResult.rows.map((row) => row.生产订单号 as string)
|
||||
orderNumbers.push(...dbOrderNumbers)
|
||||
}
|
||||
}
|
||||
@@ -208,14 +210,11 @@ export function registerValidationHandlers(): void {
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'validation:validate',
|
||||
async (
|
||||
_event,
|
||||
request: ValidationRequest
|
||||
): Promise<ValidationResponse> => {
|
||||
async (_event, request: ValidationRequest): Promise<ValidationResponse> => {
|
||||
let dbService: MySqlService | SqlServerService | null = null
|
||||
|
||||
try {
|
||||
console.log('[Validation] Starting validation:', request)
|
||||
log.info('Starting validation', { mode: request.mode })
|
||||
|
||||
// Connect to database
|
||||
dbService = await getValidationDatabaseService()
|
||||
@@ -229,7 +228,7 @@ export function registerValidationHandlers(): void {
|
||||
if (request.useSharedProductionIds) {
|
||||
// Use shared Production IDs from extractor page
|
||||
const sharedIds = getSharedProductionIds()
|
||||
console.log(`[Validation] Using ${sharedIds.length} shared Production IDs`)
|
||||
log.info(`Using ${sharedIds.length} shared Production IDs`)
|
||||
|
||||
if (sharedIds.length === 0) {
|
||||
return {
|
||||
@@ -244,19 +243,13 @@ export function registerValidationHandlers(): void {
|
||||
}
|
||||
|
||||
sourceNumbers = await getSourceNumbersFromInputs(sharedIds, dbService)
|
||||
console.log(
|
||||
`[Validation] Got ${sourceNumbers.length} source numbers from shared Production IDs`
|
||||
)
|
||||
log.info(`Got ${sourceNumbers.length} source numbers from shared Production IDs`)
|
||||
} else if (request.productionIdFile) {
|
||||
// Read from file
|
||||
const inputs = readProductionIds(request.productionIdFile)
|
||||
console.log(
|
||||
`[Validation] Read ${inputs.length} inputs from file`
|
||||
)
|
||||
log.info(`Read ${inputs.length} inputs from file`)
|
||||
sourceNumbers = await getSourceNumbersFromInputs(inputs, dbService)
|
||||
console.log(
|
||||
`[Validation] Got ${sourceNumbers.length} source numbers`
|
||||
)
|
||||
log.info(`Got ${sourceNumbers.length} source numbers`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,9 +263,7 @@ export function registerValidationHandlers(): void {
|
||||
materialRecords = await materialDao.queryAllDistinctByMaterialCode()
|
||||
} else if (sourceNumbers && sourceNumbers.length > 0) {
|
||||
// Filtered query by source numbers
|
||||
materialRecords = await materialDao.queryBySourceNumbersDistinct(
|
||||
sourceNumbers
|
||||
)
|
||||
materialRecords = await materialDao.queryBySourceNumbersDistinct(sourceNumbers)
|
||||
}
|
||||
|
||||
if (materialRecords.length === 0) {
|
||||
@@ -298,7 +289,7 @@ export function registerValidationHandlers(): void {
|
||||
? await (dbService as SqlServerService).query(typeKeywordSql)
|
||||
: await (dbService as MySqlService).query(typeKeywordSql)
|
||||
|
||||
const typeKeywords = typeKeywordResult.rows.map(row => ({
|
||||
const typeKeywords = typeKeywordResult.rows.map((row) => ({
|
||||
materialName: row.MaterialName as string,
|
||||
managerName: row.ManagerName as string
|
||||
}))
|
||||
@@ -316,10 +307,7 @@ export function registerValidationHandlers(): void {
|
||||
|
||||
const markedCodesDict = new Map<string, string>()
|
||||
for (const row of markedResult.rows) {
|
||||
markedCodesDict.set(
|
||||
row.MaterialCode as string,
|
||||
row.ManagerName as string
|
||||
)
|
||||
markedCodesDict.set(row.MaterialCode as string, row.ManagerName as string)
|
||||
}
|
||||
|
||||
// Match materials
|
||||
@@ -338,10 +326,7 @@ export function registerValidationHandlers(): void {
|
||||
// Priority 2: Match with MaterialsTypeToBeDeleted (MaterialName contains)
|
||||
if (!managerName) {
|
||||
for (const typeKeyword of typeKeywords) {
|
||||
if (
|
||||
typeKeyword.materialName &&
|
||||
materialName.includes(typeKeyword.materialName)
|
||||
) {
|
||||
if (typeKeyword.materialName && materialName.includes(typeKeyword.materialName)) {
|
||||
matchedTypeKeyword = typeKeyword.materialName
|
||||
managerName = typeKeyword.managerName
|
||||
break
|
||||
@@ -360,8 +345,8 @@ export function registerValidationHandlers(): void {
|
||||
})
|
||||
}
|
||||
|
||||
const markedCount = results.filter(r => r.isMarkedForDeletion).length
|
||||
const matchedCount = results.filter(r => r.managerName).length
|
||||
const markedCount = results.filter((r) => r.isMarkedForDeletion).length
|
||||
const matchedCount = results.filter((r) => r.managerName).length
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -374,7 +359,9 @@ export function registerValidationHandlers(): void {
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
console.error('[Validation] Validation error:', error)
|
||||
log.error('Validation error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return {
|
||||
success: false,
|
||||
error: `Validation failed: ${message}`
|
||||
@@ -384,7 +371,9 @@ export function registerValidationHandlers(): void {
|
||||
try {
|
||||
await dbService.disconnect()
|
||||
} catch (closeError) {
|
||||
console.warn('[Validation] Error disconnecting database:', closeError)
|
||||
log.warn('Error disconnecting database', {
|
||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -398,10 +387,7 @@ export function registerValidationHandlers(): void {
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'materials:upsertBatch',
|
||||
async (
|
||||
_event,
|
||||
request: MaterialUpsertBatchRequest
|
||||
): Promise<MaterialOperationResponse> => {
|
||||
async (_event, request: MaterialUpsertBatchRequest): Promise<MaterialOperationResponse> => {
|
||||
try {
|
||||
const dao = new MaterialsToBeDeletedDAO()
|
||||
const stats = await dao.upsertBatch(request.materials)
|
||||
@@ -412,7 +398,9 @@ export function registerValidationHandlers(): void {
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
console.error('[Materials] Upsert batch error:', error)
|
||||
log.error('Upsert batch error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return {
|
||||
success: false,
|
||||
error: `Upsert failed: ${message}`
|
||||
@@ -426,10 +414,7 @@ export function registerValidationHandlers(): void {
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'materials:delete',
|
||||
async (
|
||||
_event,
|
||||
request: MaterialDeleteRequest
|
||||
): Promise<MaterialOperationResponse> => {
|
||||
async (_event, request: MaterialDeleteRequest): Promise<MaterialOperationResponse> => {
|
||||
try {
|
||||
const dao = new MaterialsToBeDeletedDAO()
|
||||
const count = await dao.deleteByMaterialCodes(request.materialCodes)
|
||||
@@ -440,7 +425,7 @@ export function registerValidationHandlers(): void {
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
console.error('[Materials] Delete error:', error)
|
||||
log.error('Delete error', { error: error instanceof Error ? error.message : String(error) })
|
||||
return {
|
||||
success: false,
|
||||
error: `Delete failed: ${message}`
|
||||
@@ -452,29 +437,25 @@ export function registerValidationHandlers(): void {
|
||||
/**
|
||||
* Get unique manager names
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'materials:getManagers',
|
||||
async (_event): Promise<{ managers: string[] }> => {
|
||||
try {
|
||||
const dao = new MaterialsToBeDeletedDAO()
|
||||
const managers = await dao.getManagers()
|
||||
return { managers }
|
||||
} catch (error) {
|
||||
console.error('[Materials] Get managers error:', error)
|
||||
return { managers: [] }
|
||||
}
|
||||
ipcMain.handle('materials:getManagers', async (_event): Promise<{ managers: string[] }> => {
|
||||
try {
|
||||
const dao = new MaterialsToBeDeletedDAO()
|
||||
const managers = await dao.getManagers()
|
||||
return { managers }
|
||||
} catch (error) {
|
||||
log.error('Get managers error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return { managers: [] }
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Get materials by manager
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'materials:getByManager',
|
||||
async (
|
||||
_event,
|
||||
managerName: string
|
||||
): Promise<{ materials: MaterialRecordSummary[] }> => {
|
||||
async (_event, managerName: string): Promise<{ materials: MaterialRecordSummary[] }> => {
|
||||
let dbService: MySqlService | SqlServerService | null = null
|
||||
|
||||
try {
|
||||
@@ -512,25 +493,16 @@ export function registerValidationHandlers(): void {
|
||||
WHERE MaterialCode = ?
|
||||
LIMIT 1
|
||||
`
|
||||
detailResult = await (dbService as MySqlService).query(detailSql, [
|
||||
mat.materialCode
|
||||
])
|
||||
detailResult = await (dbService as MySqlService).query(detailSql, [mat.materialCode])
|
||||
}
|
||||
|
||||
enrichedMaterials.push({
|
||||
materialCode: mat.materialCode,
|
||||
materialName:
|
||||
detailResult.rows.length > 0
|
||||
? (detailResult.rows[0].MaterialName as string)
|
||||
: '',
|
||||
detailResult.rows.length > 0 ? (detailResult.rows[0].MaterialName as string) : '',
|
||||
specification:
|
||||
detailResult.rows.length > 0
|
||||
? (detailResult.rows[0].Specification as string)
|
||||
: '',
|
||||
model:
|
||||
detailResult.rows.length > 0
|
||||
? (detailResult.rows[0].Model as string)
|
||||
: '',
|
||||
detailResult.rows.length > 0 ? (detailResult.rows[0].Specification as string) : '',
|
||||
model: detailResult.rows.length > 0 ? (detailResult.rows[0].Model as string) : '',
|
||||
managerName: mat.managerName,
|
||||
isMarked: markedCodes.has(mat.materialCode)
|
||||
})
|
||||
@@ -538,14 +510,18 @@ export function registerValidationHandlers(): void {
|
||||
|
||||
return { materials: enrichedMaterials }
|
||||
} catch (error) {
|
||||
console.error('[Materials] Get by manager error:', error)
|
||||
log.error('Get by manager error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return { materials: [] }
|
||||
} finally {
|
||||
if (dbService) {
|
||||
try {
|
||||
await dbService.disconnect()
|
||||
} catch (closeError) {
|
||||
console.warn('[Materials] Error disconnecting database:', closeError)
|
||||
log.warn('Error disconnecting database', {
|
||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -592,25 +568,16 @@ export function registerValidationHandlers(): void {
|
||||
WHERE MaterialCode = ?
|
||||
LIMIT 1
|
||||
`
|
||||
detailResult = await (dbService as MySqlService).query(detailSql, [
|
||||
mat.materialCode
|
||||
])
|
||||
detailResult = await (dbService as MySqlService).query(detailSql, [mat.materialCode])
|
||||
}
|
||||
|
||||
enrichedMaterials.push({
|
||||
materialCode: mat.materialCode,
|
||||
materialName:
|
||||
detailResult.rows.length > 0
|
||||
? (detailResult.rows[0].MaterialName as string)
|
||||
: '',
|
||||
detailResult.rows.length > 0 ? (detailResult.rows[0].MaterialName as string) : '',
|
||||
specification:
|
||||
detailResult.rows.length > 0
|
||||
? (detailResult.rows[0].Specification as string)
|
||||
: '',
|
||||
model:
|
||||
detailResult.rows.length > 0
|
||||
? (detailResult.rows[0].Model as string)
|
||||
: '',
|
||||
detailResult.rows.length > 0 ? (detailResult.rows[0].Specification as string) : '',
|
||||
model: detailResult.rows.length > 0 ? (detailResult.rows[0].Model as string) : '',
|
||||
managerName: mat.managerName,
|
||||
isMarked: markedCodes.has(mat.materialCode)
|
||||
})
|
||||
@@ -618,14 +585,18 @@ export function registerValidationHandlers(): void {
|
||||
|
||||
return { materials: enrichedMaterials }
|
||||
} catch (error) {
|
||||
console.error('[Materials] Get all error:', error)
|
||||
log.error('Get all error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return { materials: [] }
|
||||
} finally {
|
||||
if (dbService) {
|
||||
try {
|
||||
await dbService.disconnect()
|
||||
} catch (closeError) {
|
||||
console.warn('[Materials] Error disconnecting database:', closeError)
|
||||
log.warn('Error disconnecting database', {
|
||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -635,19 +606,18 @@ export function registerValidationHandlers(): void {
|
||||
/**
|
||||
* Get statistics
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'materials:getStatistics',
|
||||
async (_event): Promise<{ stats: any }> => {
|
||||
try {
|
||||
const dao = new MaterialsToBeDeletedDAO()
|
||||
const stats = await dao.getStatistics()
|
||||
return { stats }
|
||||
} catch (error) {
|
||||
console.error('[Materials] Get statistics error:', error)
|
||||
return { stats: null }
|
||||
}
|
||||
ipcMain.handle('materials:getStatistics', async (_event): Promise<{ stats: any }> => {
|
||||
try {
|
||||
const dao = new MaterialsToBeDeletedDAO()
|
||||
const stats = await dao.getStatistics()
|
||||
return { stats }
|
||||
} catch (error) {
|
||||
log.error('Get statistics error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return { stats: null }
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Set shared Production IDs from extractor page
|
||||
@@ -655,7 +625,7 @@ export function registerValidationHandlers(): void {
|
||||
ipcMain.handle(
|
||||
'validation:setSharedProductionIds',
|
||||
async (_event, productionIds: string[]): Promise<void> => {
|
||||
console.log(`[Validation] Received ${productionIds.length} shared Production IDs`)
|
||||
log.info(`Received ${productionIds.length} shared Production IDs`)
|
||||
setSharedProductionIds(productionIds)
|
||||
}
|
||||
)
|
||||
@@ -676,14 +646,18 @@ export function registerValidationHandlers(): void {
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'validation:getCleanerData',
|
||||
async (_event): Promise<{
|
||||
async (
|
||||
_event
|
||||
): Promise<{
|
||||
success: boolean
|
||||
orderNumbers?: string[]
|
||||
materialCodes?: string[]
|
||||
error?: string
|
||||
}> => {
|
||||
let dbService: MySqlService | SqlServerService | null = null
|
||||
const sessionManager = (await import('../services/user/session-manager')).SessionManager.getInstance()
|
||||
const sessionManager = (
|
||||
await import('../services/user/session-manager')
|
||||
).SessionManager.getInstance()
|
||||
|
||||
try {
|
||||
// Get current user
|
||||
@@ -700,7 +674,7 @@ export function registerValidationHandlers(): void {
|
||||
const dbType = process.env.DB_TYPE?.toLowerCase()
|
||||
const isSqlServer = dbType === 'sqlserver' || dbType === 'mssql'
|
||||
|
||||
console.log(`[CleanerData] User: ${username}, isAdmin: ${isAdmin}`)
|
||||
log.info(`User: ${username}, isAdmin: ${isAdmin}`)
|
||||
|
||||
// Connect to database
|
||||
dbService = await getValidationDatabaseService()
|
||||
@@ -710,9 +684,9 @@ export function registerValidationHandlers(): void {
|
||||
let orderNumbers: string[] = []
|
||||
|
||||
if (sharedIds.length > 0) {
|
||||
console.log(`[CleanerData] Using ${sharedIds.length} shared Production IDs`)
|
||||
log.info(`Using ${sharedIds.length} shared Production IDs`)
|
||||
orderNumbers = await getSourceNumbersFromInputs(sharedIds, dbService)
|
||||
console.log(`[CleanerData] Got ${orderNumbers.length} order numbers`)
|
||||
log.info(`Got ${orderNumbers.length} order numbers`)
|
||||
}
|
||||
|
||||
// 2. Get material codes from MaterialsToBeDeleted table
|
||||
@@ -730,10 +704,8 @@ export function registerValidationHandlers(): void {
|
||||
? await (dbService as SqlServerService).query(allCodesSql)
|
||||
: await (dbService as MySqlService).query(allCodesSql)
|
||||
|
||||
materialCodes = result.rows
|
||||
.map(row => row.MaterialCode as string)
|
||||
.filter(Boolean)
|
||||
console.log(`[CleanerData] Admin user: got ${materialCodes.length} materials`)
|
||||
materialCodes = result.rows.map((row) => row.MaterialCode as string).filter(Boolean)
|
||||
log.info(`Admin user: got ${materialCodes.length} materials`)
|
||||
} else {
|
||||
// Regular users only see their own materials
|
||||
if (isSqlServer) {
|
||||
@@ -746,9 +718,7 @@ export function registerValidationHandlers(): void {
|
||||
const result = await (dbService as SqlServerService).queryWithParams(userMaterialsSql, {
|
||||
username: { value: username, type: sql.NVarChar }
|
||||
})
|
||||
materialCodes = result.rows
|
||||
.map(row => row.MaterialCode as string)
|
||||
.filter(Boolean)
|
||||
materialCodes = result.rows.map((row) => row.MaterialCode as string).filter(Boolean)
|
||||
} else {
|
||||
const userMaterialsSql = `
|
||||
SELECT MaterialCode
|
||||
@@ -756,11 +726,9 @@ export function registerValidationHandlers(): void {
|
||||
WHERE ManagerName = ? AND MaterialCode IS NOT NULL
|
||||
`
|
||||
const result = await (dbService as MySqlService).query(userMaterialsSql, [username])
|
||||
materialCodes = result.rows
|
||||
.map(row => row.MaterialCode as string)
|
||||
.filter(Boolean)
|
||||
materialCodes = result.rows.map((row) => row.MaterialCode as string).filter(Boolean)
|
||||
}
|
||||
console.log(`[CleanerData] Regular user: got ${materialCodes.length} materials`)
|
||||
log.info(`Regular user: got ${materialCodes.length} materials`)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -770,7 +738,9 @@ export function registerValidationHandlers(): void {
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
console.error('[CleanerData] Error:', error)
|
||||
log.error('CleanerData error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return {
|
||||
success: false,
|
||||
error: `获取清理数据失败:${message}`
|
||||
@@ -780,7 +750,9 @@ export function registerValidationHandlers(): void {
|
||||
try {
|
||||
await dbService.disconnect()
|
||||
} catch (closeError) {
|
||||
console.warn('[CleanerData] Error disconnecting database:', closeError)
|
||||
log.warn('Error disconnecting database', {
|
||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user