feat: migrate ERP configuration from .env to per-user database storage
- Moved ERP credentials (URL, username, password) from environment variables to dbo_BIPUsers table - Each user now has their own ERP configuration stored in the database - Added UserErpConfigService for managing per-user ERP settings - Updated cleaner and extractor handlers to fetch ERP config from database instead of .env - Removed ERP fields from ConfigManager UI editable fields - Added new IPC handlers and preload APIs for user ERP config management - Includes migration script to transfer existing .env ERP settings to database - Added migration guide documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import type {
|
||||
ExportResultItem,
|
||||
ExportResultResponse
|
||||
} from '../types/cleaner.types'
|
||||
import { UserErpConfigService } from '../services/user/user-erp-config-service'
|
||||
|
||||
const log = createLogger('CleanerHandler')
|
||||
|
||||
@@ -75,6 +76,27 @@ async function getDatabaseService(): Promise<MySqlService | SqlServerService> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ERP configuration for current user
|
||||
*/
|
||||
async function getErpConfig(): Promise<{
|
||||
url: string
|
||||
username: string
|
||||
password: string
|
||||
}> {
|
||||
const erpConfigService = UserErpConfigService.getInstance()
|
||||
const config = await erpConfigService.getCurrentUserErpConfig()
|
||||
|
||||
if (!config || !config.url || !config.username || !config.password) {
|
||||
throw new ValidationError(
|
||||
'ERP 配置不完整。请在设置中配置 ERP URL、用户名和密码',
|
||||
'VAL_MISSING_REQUIRED'
|
||||
)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
export function registerCleanerHandlers(): void {
|
||||
ipcMain.handle(
|
||||
'cleaner:run',
|
||||
@@ -85,24 +107,18 @@ export function registerCleanerHandlers(): void {
|
||||
return withErrorHandling(async () => {
|
||||
let authService: ErpAuthService | null = null
|
||||
let dbService: MySqlService | SqlServerService | null = null
|
||||
let erpConfigService: UserErpConfigService | null = null
|
||||
|
||||
try {
|
||||
const erpUrl = process.env.ERP_URL || ''
|
||||
const erpUsername = process.env.ERP_USERNAME || ''
|
||||
const erpPassword = process.env.ERP_PASSWORD || ''
|
||||
// Get ERP configuration from database for current user
|
||||
log.info('Fetching ERP configuration from database...')
|
||||
const erpConfig = await getErpConfig()
|
||||
|
||||
log.info('Config check', {
|
||||
url: erpUrl ? 'configured' : 'EMPTY',
|
||||
username: erpUsername ? 'configured' : 'EMPTY'
|
||||
log.info('ERP config retrieved', {
|
||||
url: erpConfig.url ? 'configured' : 'EMPTY',
|
||||
username: erpConfig.username ? 'configured' : 'EMPTY'
|
||||
})
|
||||
|
||||
if (!erpUrl || !erpUsername || !erpPassword) {
|
||||
throw new ValidationError(
|
||||
'ERP 配置不完整。请检查 .env 文件中的 ERP_URL, ERP_USERNAME, ERP_PASSWORD',
|
||||
'VAL_MISSING_REQUIRED'
|
||||
)
|
||||
}
|
||||
|
||||
const dbType = process.env.DB_TYPE?.toLowerCase()
|
||||
log.info(
|
||||
`Connecting to ${dbType === 'sqlserver' || dbType === 'mssql' ? 'SQL Server' : 'MySQL'} for order resolution...`
|
||||
@@ -138,9 +154,9 @@ export function registerCleanerHandlers(): void {
|
||||
log.info('Resolved order numbers', { count: validOrderNumbers.length })
|
||||
|
||||
authService = new ErpAuthService({
|
||||
url: erpUrl,
|
||||
username: erpUsername,
|
||||
password: erpPassword,
|
||||
url: erpConfig.url,
|
||||
username: erpConfig.username,
|
||||
password: erpConfig.password,
|
||||
headless: input.headless ?? true
|
||||
})
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { createLogger } from '../services/logger'
|
||||
import { withErrorHandling, type IpcResult } from './index'
|
||||
import { ErpConnectionError, ValidationError, DatabaseQueryError } from '../types/errors'
|
||||
import type { ExtractorInput, ExtractorResult, ExtractionProgress } from '../types/extractor.types'
|
||||
import { UserErpConfigService } from '../services/user/user-erp-config-service'
|
||||
|
||||
const log = createLogger('ExtractorHandler')
|
||||
|
||||
@@ -36,6 +37,27 @@ function sendLog(windowId: number, level: string, message: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ERP configuration for current user
|
||||
*/
|
||||
async function getErpConfig(): Promise<{
|
||||
url: string
|
||||
username: string
|
||||
password: string
|
||||
}> {
|
||||
const erpConfigService = UserErpConfigService.getInstance()
|
||||
const config = await erpConfigService.getCurrentUserErpConfig()
|
||||
|
||||
if (!config || !config.url || !config.username || !config.password) {
|
||||
throw new ValidationError(
|
||||
'ERP 配置不完整。请在设置中配置 ERP URL、用户名和密码',
|
||||
'VAL_MISSING_REQUIRED'
|
||||
)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* Register IPC handlers for extractor service
|
||||
*/
|
||||
@@ -48,25 +70,18 @@ export function registerExtractorHandlers(): void {
|
||||
return withErrorHandling(async () => {
|
||||
let authService: ErpAuthService | null = null
|
||||
let dbService: IDatabaseService | null = null
|
||||
let erpConfigService: UserErpConfigService | null = null
|
||||
|
||||
try {
|
||||
// Check environment variables
|
||||
const erpUrl = process.env.ERP_URL || ''
|
||||
const erpUsername = process.env.ERP_USERNAME || ''
|
||||
const erpPassword = process.env.ERP_PASSWORD || ''
|
||||
// Get ERP configuration from database for current user
|
||||
log.info('Fetching ERP configuration from database...')
|
||||
const erpConfig = await getErpConfig()
|
||||
|
||||
log.info('Config check', {
|
||||
url: erpUrl ? 'configured' : 'EMPTY',
|
||||
username: erpUsername ? 'configured' : 'EMPTY'
|
||||
log.info('ERP config retrieved', {
|
||||
url: erpConfig.url ? 'configured' : 'EMPTY',
|
||||
username: erpConfig.username ? 'configured' : 'EMPTY'
|
||||
})
|
||||
|
||||
if (!erpUrl || !erpUsername || !erpPassword) {
|
||||
throw new ValidationError(
|
||||
'ERP 配置不完整。请检查 .env 文件中的 ERP_URL, ERP_USERNAME, ERP_PASSWORD',
|
||||
'VAL_MISSING_REQUIRED'
|
||||
)
|
||||
}
|
||||
|
||||
// Create database service using factory
|
||||
log.info('Connecting to database for order resolution...')
|
||||
sendProgress(windowId, '连接数据库...', 3.33, {
|
||||
@@ -115,9 +130,9 @@ export function registerExtractorHandlers(): void {
|
||||
|
||||
// Create auth service and login
|
||||
authService = new ErpAuthService({
|
||||
url: erpUrl,
|
||||
username: erpUsername,
|
||||
password: erpPassword,
|
||||
url: erpConfig.url,
|
||||
username: erpConfig.username,
|
||||
password: erpConfig.password,
|
||||
headless: true
|
||||
})
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { registerAuthHandlers } from './auth-handler'
|
||||
import { registerValidationHandlers } from './validation-handler'
|
||||
import { registerSettingsHandlers } from './settings-handler'
|
||||
import { registerMaterialTypeHandlers } from './material-type-handler'
|
||||
import { registerUserErpConfigHandlers } from './user-erp-config-handler'
|
||||
import { createLogger } from '../services/logger'
|
||||
import { getErrorMessage, getErrorCode, isBaseError } from '../types/errors'
|
||||
|
||||
@@ -75,5 +76,6 @@ export function registerIpcHandlers(): void {
|
||||
registerValidationHandlers()
|
||||
registerSettingsHandlers()
|
||||
registerMaterialTypeHandlers()
|
||||
registerUserErpConfigHandlers()
|
||||
log.info('All IPC handlers registered')
|
||||
}
|
||||
|
||||
191
src/main/ipc/user-erp-config-handler.ts
Normal file
191
src/main/ipc/user-erp-config-handler.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* IPC handlers for User ERP Configuration
|
||||
*
|
||||
* Provides APIs for the renderer process to:
|
||||
* - Get current user's ERP configuration
|
||||
* - Update current user's ERP configuration
|
||||
* - Test ERP connection with provided credentials
|
||||
*/
|
||||
|
||||
import { ipcMain } from 'electron'
|
||||
import { UserErpConfigService } from '../services/user/user-erp-config-service'
|
||||
import { ErpAuthService } from '../services/erp/erp-auth'
|
||||
import { createLogger } from '../services/logger'
|
||||
import type { UserInfo } from '../types/user.types'
|
||||
|
||||
const log = createLogger('UserErpConfigHandler')
|
||||
|
||||
/**
|
||||
* ERP Configuration request
|
||||
*/
|
||||
export interface ErpConfigRequest {
|
||||
url: string
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
/**
|
||||
* ERP Configuration response
|
||||
*/
|
||||
export interface ErpConfigResponse {
|
||||
success: boolean
|
||||
config?: ErpConfigRequest
|
||||
error?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection test result
|
||||
*/
|
||||
export interface ConnectionTestResult {
|
||||
success: boolean
|
||||
message?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Register IPC handlers for user ERP configuration
|
||||
*/
|
||||
export function registerUserErpConfigHandlers(): void {
|
||||
const erpConfigService = UserErpConfigService.getInstance()
|
||||
|
||||
/**
|
||||
* Get current user's ERP configuration
|
||||
*/
|
||||
ipcMain.handle('user-erp-config:getCurrent', async (): Promise<ErpConfigResponse> => {
|
||||
try {
|
||||
log.info('Fetching current user ERP config')
|
||||
const config = await erpConfigService.getCurrentUserErpConfig()
|
||||
|
||||
if (!config) {
|
||||
return {
|
||||
success: false,
|
||||
error: '未找到 ERP 配置。请先配置 ERP 连接参数。'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
config: {
|
||||
url: config.url,
|
||||
username: config.username,
|
||||
password: config.password
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
log.error('Get current user ERP config failed', { error: message })
|
||||
return {
|
||||
success: false,
|
||||
error: `获取 ERP 配置失败:${message}`
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Update current user's ERP configuration
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'user-erp-config:update',
|
||||
async (_event, config: ErpConfigRequest): Promise<ErpConfigResponse> => {
|
||||
try {
|
||||
log.info('Updating current user ERP config', {
|
||||
url: config.url,
|
||||
username: config.username
|
||||
})
|
||||
|
||||
const success = await erpConfigService.updateCurrentUserErpConfig(config)
|
||||
|
||||
if (success) {
|
||||
log.info('ERP config updated successfully')
|
||||
return {
|
||||
success: true,
|
||||
config
|
||||
}
|
||||
} else {
|
||||
log.error('Failed to update ERP config')
|
||||
return {
|
||||
success: false,
|
||||
error: '更新 ERP 配置失败'
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
log.error('Update ERP config failed', { error: message })
|
||||
return {
|
||||
success: false,
|
||||
error: `更新 ERP 配置失败:${message}`
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Test ERP connection with provided credentials
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'user-erp-config:testConnection',
|
||||
async (_event, config: ErpConfigRequest): Promise<ConnectionTestResult> => {
|
||||
try {
|
||||
log.info('Testing ERP connection', { url: config.url, username: config.username })
|
||||
|
||||
if (!config.url || !config.username || !config.password) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'ERP 配置不完整,请确保 URL、用户名和密码都已填写'
|
||||
}
|
||||
}
|
||||
|
||||
const authService = new ErpAuthService({
|
||||
url: config.url,
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
headless: true
|
||||
})
|
||||
|
||||
try {
|
||||
await authService.login()
|
||||
await authService.close()
|
||||
|
||||
log.info('ERP connection test successful')
|
||||
return {
|
||||
success: true,
|
||||
message: 'ERP 连接测试成功'
|
||||
}
|
||||
} catch (error) {
|
||||
await authService.close().catch(() => {})
|
||||
throw error
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
log.error('ERP connection test failed', { error: message })
|
||||
return {
|
||||
success: false,
|
||||
message: `ERP 连接测试失败:${message}`
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Get all users' ERP configurations (admin only)
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'user-erp-config:getAll',
|
||||
async (): Promise<
|
||||
Array<{
|
||||
username: string
|
||||
erpUrl: string
|
||||
erpUsername: string
|
||||
}>
|
||||
> => {
|
||||
try {
|
||||
log.info('Fetching all users ERP config')
|
||||
const configs = await erpConfigService.getAllUsersErpConfig()
|
||||
log.info('Retrieved ERP configs for all users', { count: configs.length })
|
||||
return configs
|
||||
} catch (error) {
|
||||
log.error('Get all users ERP config failed', { error })
|
||||
return []
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user