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:
Misaka
2026-03-05 21:52:54 +08:00
parent 3d2127b660
commit 5977254180
15 changed files with 1654 additions and 53 deletions

View File

@@ -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
})

View File

@@ -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
})

View File

@@ -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')
}

View 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 []
}
}
)
}