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

171
docs/MIGRATION_GUIDE.md Normal file
View File

@@ -0,0 +1,171 @@
# BIPUsers 表 ERP 参数迁移指南
## 概述
本次迁移将 ERP 配置参数(`ERP_URL`, `ERP_USERNAME`, `ERP_PASSWORD`)从 `.env` 文件迁移到 `dbo_BIPUsers` 数据库表中,实现每个用户独立的 ERP 配置。
## 迁移步骤
### 步骤 1连接到 MySQL 数据库
使用你喜欢的 MySQL 客户端工具连接:
**方式 A: MySQL 命令行**
```bash
mysql -h 192.168.31.83 -P 3306 -u remote_user -p'3.1415926Beeke' BLD_DB
```
**方式 B: MySQL Workbench / Navicat / DBeaver**
- Host: `192.168.31.83`
- Port: `3306`
- Username: `remote_user`
- Password: `3.1415926Beeke`
- Database: `BLD_DB`
### 步骤 2执行迁移 SQL
运行以下 SQL 脚本添加新字段:
```sql
-- ============================================
-- BIPUsers 表迁移:添加 ERP 参数字段
-- ============================================
USE BLD_DB;
-- 1. 添加 ERP_URL 字段
ALTER TABLE dbo_BIPUsers ADD COLUMN IF NOT EXISTS ERP_URL VARCHAR(500) NULL COMMENT 'ERP 系统 URL';
-- 2. 添加 ERP_Username 字段
ALTER TABLE dbo_BIPUsers ADD COLUMN IF NOT EXISTS ERP_Username VARCHAR(255) NULL COMMENT 'ERP 用户名';
-- 3. 添加 ERP_Password 字段
ALTER TABLE dbo_BIPUsers ADD COLUMN IF NOT EXISTS ERP_Password VARCHAR(255) NULL COMMENT 'ERP 密码';
-- 4. 验证字段已添加
DESCRIBE dbo_BIPUsers;
```
**注意:** 如果你的 MySQL 版本不支持 `ADD COLUMN IF NOT EXISTS`,请使用:
```sql
USE BLD_DB;
ALTER TABLE dbo_BIPUsers ADD COLUMN ERP_URL VARCHAR(500) NULL COMMENT 'ERP 系统 URL';
ALTER TABLE dbo_BIPUsers ADD COLUMN ERP_Username VARCHAR(255) NULL COMMENT 'ERP 用户名';
ALTER TABLE dbo_BIPUsers ADD COLUMN ERP_Password VARCHAR(255) NULL COMMENT 'ERP 密码';
```
### 步骤 3初始化 ERP 配置
将所有现有用户的 ERP 配置设置为当前 `.env` 中的值:
```sql
-- 更新所有用户的 ERP 配置
UPDATE dbo_BIPUsers
SET
ERP_URL = 'https://68.11.34.30:8082/',
ERP_Username = '在这里填写你的 ERP 用户名',
ERP_Password = '在这里填写你的 ERP 密码'
WHERE ERP_URL IS NULL OR ERP_URL = '';
```
**请将上面的占位符替换为实际的 ERP 凭证!**
### 步骤 4验证迁移结果
```sql
-- 检查所有用户的 ERP 配置
SELECT
UserName,
UserType,
ERP_URL,
ERP_Username,
CreateTime
FROM dbo_BIPUsers
ORDER BY UserName;
```
## 迁移后配置
### 更新 .env 文件(可选)
迁移完成后,`.env` 文件中的 ERP 配置将不再使用,但为了向后兼容可以保留:
```bash
# ERP 配置(已废弃,仅用于向后兼容)
# ERP_URL=https://68.11.34.30:8082/
# ERP_USERNAME=your_username
# ERP_PASSWORD=your_password
```
### 在应用中配置用户 ERP 参数
迁移完成后,每个用户可以通过应用界面配置自己的 ERP 参数:
1. 登录应用
2. 进入设置页面
3. 配置个人 ERP 连接信息
4. 测试连接
5. 保存
## 故障排除
### 问题 1字段已存在错误
```
Error: Duplicate column name 'ERP_URL'
```
**解决方案:** 字段已经存在,跳过添加步骤,直接执行步骤 3 初始化数据。
### 问题 2连接被拒绝
```
Error: Access denied for user 'remote_user'@'%'
```
**解决方案:** 检查数据库用户权限,确保 `remote_user``ALTER``UPDATE` 权限。
### 问题 3连接超时
```
Error: connect ETIMEDOUT
```
**解决方案:**
- 检查网络连接
- 确认 MySQL 服务器正在运行
- 检查防火墙设置
## 回滚方案
如果需要回滚,可以删除新增的字段:
```sql
-- ⚠️ 警告:这将永久删除 ERP 配置数据
ALTER TABLE dbo_BIPUsers DROP COLUMN ERP_URL;
ALTER TABLE dbo_BIPUsers DROP COLUMN ERP_Username;
ALTER TABLE dbo_BIPUsers DROP COLUMN ERP_Password;
```
## 完成确认
迁移完成后,请确认以下事项:
- [ ] 三个新字段已成功添加到 `dbo_BIPUsers`
- [ ] 所有现有用户的 ERP 配置已初始化
- [ ] 应用程序可以正常启动
- [ ] 数据提取和物料清理功能正常工作
---
**迁移脚本文件:**
- `src/main/services/user/migration/add-erp-params-to-bipusers-mysql.sql` - 完整 SQL 脚本
- `src/main/services/user/migration/run-migration.ts` - TypeScript 自动迁移脚本(需要网络访问)
**创建时间:** 2026-03-05

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

View File

@@ -108,11 +108,13 @@ function deepMerge<T>(source: T, target: Partial<T>): T {
/**
* UI editable field whitelist
* Fields that can be modified through the settings UI
* Note: ERP fields are no longer editable here - they are managed per-user in the database
*/
const UI_EDITABLE_FIELDS: string[] = [
'erp.url',
'erp.username',
'erp.password'
// ERP fields removed - ERP config is now stored in dbo_BIPUsers table per user
// 'erp.url',
// 'erp.username',
// 'erp.password'
// Add more fields as UI expands
]
@@ -254,17 +256,14 @@ export class ConfigManager {
// Build .env content from cache
const lines: string[] = []
// ERP Configuration
// ERP Configuration - REMOVED
// ERP parameters are now stored in the database (dbo_BIPUsers table)
// This section is kept for backward compatibility but values are not used
lines.push('# ===========================')
lines.push('# ERP 系统配置')
lines.push('# ERP 系统配置(已迁移到数据库)')
lines.push('# ===========================')
lines.push(`ERP_URL=${this.configCache.get('ERP_URL') || DEFAULT_SETTINGS.erp.url}`)
lines.push(
`ERP_USERNAME=${this.configCache.get('ERP_USERNAME') || DEFAULT_SETTINGS.erp.username}`
)
lines.push(
`ERP_PASSWORD=${this.configCache.get('ERP_PASSWORD') || DEFAULT_SETTINGS.erp.password}`
)
lines.push('# ERP_URL, ERP_USERNAME, ERP_PASSWORD 已从 .env 移除')
lines.push('# 这些参数现在存储在 dbo_BIPUsers 表中,每个用户可以有自己的 ERP 配置')
lines.push('')
// Database Configuration - SQL Server
@@ -406,13 +405,16 @@ export class ConfigManager {
/**
* Get all settings as SettingsData object
* Note: ERP configuration is now stored in database, not .env
* The ERP values here are for UI display only and will not be used for actual ERP operations
*/
public getAllSettings(): SettingsData {
return {
erp: {
url: this.get('ERP_URL', DEFAULT_SETTINGS.erp.url),
username: this.get('ERP_USERNAME', DEFAULT_SETTINGS.erp.username),
password: this.get('ERP_PASSWORD', DEFAULT_SETTINGS.erp.password),
// ERP config is now from database, these are placeholder defaults for UI
url: DEFAULT_SETTINGS.erp.url,
username: DEFAULT_SETTINGS.erp.username,
password: DEFAULT_SETTINGS.erp.password,
headless: true,
ignoreHttpsErrors: true,
autoCloseBrowser: true
@@ -487,12 +489,11 @@ export class ConfigManager {
/**
* Save settings from SettingsData object
* Note: ERP settings are NOT saved to .env anymore - they are stored in the database
*/
public async saveAllSettings(settings: SettingsData): Promise<boolean> {
// ERP settings - use underscore uppercase keys to match .env file
this.set('ERP_URL', settings.erp.url)
this.set('ERP_USERNAME', settings.erp.username)
this.set('ERP_PASSWORD', settings.erp.password)
// ERP settings are now stored in the database (dbo_BIPUsers table)
// They are NOT saved to .env file anymore
// Database settings
this.set('DB_TYPE', settings.database.dbType)

View File

@@ -28,7 +28,11 @@ export const BIP_USERS_CONFIG = {
USER_TYPE: 'UserType',
PASSWORD: 'Password',
COMPUTER_NAME: 'ComputerName',
CREATE_TIME: 'CreateTime'
CREATE_TIME: 'CreateTime',
// ERP Configuration columns
ERP_URL: 'ERP_URL',
ERP_USERNAME: 'ERP_Username',
ERP_PASSWORD: 'ERP_Password'
}
} as const
@@ -480,6 +484,162 @@ export class BIPUsersDAO {
}
}
/**
* Get ERP configuration for a user
* @param username - The username to get ERP config for
* @returns ERP configuration object or null if not found
*/
async getUserErpConfig(username: string): Promise<{
url: string
username: string
password: string
} | null> {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const cols = BIP_USERS_CONFIG.COLUMNS
if (this.dbType === 'sqlserver') {
const sqlString = `
SELECT ${cols.ERP_URL}, ${cols.ERP_USERNAME}, ${cols.ERP_PASSWORD}
FROM ${tableName}
WHERE UserName = @username
`
const result = await (dbService as SqlServerService).queryWithParams(sqlString, {
username: { value: username, type: sql.NVarChar(255) }
})
if (result.rows.length > 0) {
const row = result.rows[0]
return {
url: (row[cols.ERP_URL] as string) || '',
username: (row[cols.ERP_USERNAME] as string) || '',
password: (row[cols.ERP_PASSWORD] as string) || ''
}
}
return null
} else {
const sqlString = `
SELECT ${cols.ERP_URL}, ${cols.ERP_USERNAME}, ${cols.ERP_PASSWORD}
FROM ${tableName}
WHERE UserName = ?
`
const result = await (dbService as MySqlService).query(sqlString, [username])
if (result.rows.length > 0) {
const row = result.rows[0]
return {
url: (row[cols.ERP_URL] as string) || '',
username: (row[cols.ERP_USERNAME] as string) || '',
password: (row[cols.ERP_PASSWORD] as string) || ''
}
}
return null
}
} catch (error) {
console.error('[BIPUsersDAO] Get user ERP config error:', error)
return null
}
}
/**
* Update ERP configuration for a user
* @param username - The username to update ERP config for
* @param erpUrl - The ERP URL
* @param erpUsername - The ERP username
* @param erpPassword - The ERP password
* @returns True if successful
*/
async updateUserErpConfig(
username: string,
erpUrl: string,
erpUsername: string,
erpPassword: string
): Promise<boolean> {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const cols = BIP_USERS_CONFIG.COLUMNS
if (this.dbType === 'sqlserver') {
const sqlString = `
UPDATE ${tableName}
SET ${cols.ERP_URL} = @erpUrl,
${cols.ERP_USERNAME} = @erpUsername,
${cols.ERP_PASSWORD} = @erpPassword
WHERE UserName = @username
`
await (dbService as SqlServerService).queryWithParams(sqlString, {
username: { value: username, type: sql.NVarChar(255) },
erpUrl: { value: erpUrl, type: sql.NVarChar(500) },
erpUsername: { value: erpUsername, type: sql.NVarChar(255) },
erpPassword: { value: erpPassword, type: sql.NVarChar(255) }
})
return true
} else {
const sqlString = `
UPDATE ${tableName}
SET ${cols.ERP_URL} = ?,
${cols.ERP_USERNAME} = ?,
${cols.ERP_PASSWORD} = ?
WHERE UserName = ?
`
await (dbService as MySqlService).query(sqlString, [
erpUrl,
erpUsername,
erpPassword,
username
])
return true
}
} catch (error) {
console.error('[BIPUsersDAO] Update user ERP config error:', error)
return false
}
}
/**
* Get ERP configuration for all users (for migration/audit purposes)
* @returns List of users with their ERP configurations
*/
async getAllUsersErpConfig(): Promise<
Array<{
username: string
erpUrl: string
erpUsername: string
}>
> {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const cols = BIP_USERS_CONFIG.COLUMNS
const sqlString = `
SELECT ${cols.USERNAME}, ${cols.ERP_URL}, ${cols.ERP_USERNAME}
FROM ${tableName}
ORDER BY ${cols.USERNAME}
`
const result =
this.dbType === 'sqlserver'
? await (dbService as SqlServerService).query(sqlString)
: await (dbService as MySqlService).query(sqlString)
return result.rows.map((row) => ({
username: row[cols.USERNAME] as string,
erpUrl: (row[cols.ERP_URL] as string) || '',
erpUsername: (row[cols.ERP_USERNAME] as string) || ''
}))
} catch (error) {
console.error('[BIPUsersDAO] Get all users ERP config error:', error)
return []
}
}
/**
* Disconnect from database
*/

View File

@@ -0,0 +1,316 @@
/**
* Migration Script: Add ERP parameters to BIPUsers table
*
* This script adds ERP_URL, ERP_Username, and ERP_Password columns
* to the dbo_BIPUsers table and initializes all existing users
* with the same ERP credentials from the current .env configuration.
*
* Usage:
* npx tsx src/main/services/user/migration/add-erp-params-migration.ts
*/
import * as fs from 'fs'
import * as path from 'path'
import { fileURLToPath } from 'url'
import { dirname } from 'path'
import { ConfigManager } from '../../config/config-manager'
import { MySqlService } from '../../database/mysql'
import { SqlServerService } from '../../database/sql-server'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
/**
* Migration configuration
*/
const MIGRATION_CONFIG = {
sqlFile: path.join(__dirname, 'add-erp-params-to-bipusers.sql'),
tableName: {
mysql: 'dbo_BIPUsers',
sqlserver: '[dbo].[BIPUsers]'
},
columns: ['ERP_URL', 'ERP_Username', 'ERP_Password']
}
/**
* Check if column exists in MySQL table
*/
async function checkColumnExistsMySQL(
mysqlService: MySqlService,
tableName: string,
columnName: string
): Promise<boolean> {
const sql = `
SELECT COUNT(*) as count
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME = ?
`
const result = await mysqlService.query(sql, [tableName, columnName])
return result.rows.length > 0 && (result.rows[0].count as number) > 0
}
/**
* Check if column exists in SQL Server table
*/
async function checkColumnExistsSqlServer(
sqlServerService: SqlServerService,
tableName: string,
columnName: string
): Promise<boolean> {
const sql = `
SELECT COUNT(*) as count
FROM sys.columns
WHERE object_id = OBJECT_ID(${tableName})
AND name = @columnName
`
const result = await sqlServerService.queryWithParams(sql, {
columnName: { value: columnName.replace('ERP_', ''), type: require('mssql').NVarChar(128) }
})
return result.rows.length > 0 && (result.rows[0].count as number) > 0
}
/**
* Add column to MySQL table
*/
async function addColumnMySQL(
mysqlService: MySqlService,
tableName: string,
columnName: string,
columnType: string
): Promise<void> {
const sql = `ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnType} NULL`
await mysqlService.query(sql)
console.log(` ✓ Added column ${columnName} to ${tableName}`)
}
/**
* Add column to SQL Server table
*/
async function addColumnSqlServer(
sqlServerService: SqlServerService,
tableName: string,
columnName: string,
columnType: string
): Promise<void> {
const sql = `ALTER TABLE ${tableName} ADD ${columnName} ${columnType} NULL`
await sqlServerService.query(sql)
console.log(` ✓ Added column ${columnName} to ${tableName}`)
}
/**
* Update all users with ERP credentials from .env
*/
async function initializeErpCredentialsMySQL(
mysqlService: MySqlService,
erpUrl: string,
erpUsername: string,
erpPassword: string
): Promise<number> {
const sql = `
UPDATE ${MIGRATION_CONFIG.tableName.mysql}
SET ERP_URL = ?, ERP_Username = ?, ERP_Password = ?
WHERE ERP_URL IS NULL OR ERP_URL = ''
`
const result = await mysqlService.query(sql, [erpUrl, erpUsername, erpPassword])
return result.rowCount
}
/**
* Update all users with ERP credentials from .env (SQL Server)
*/
async function initializeErpCredentialsSqlServer(
sqlServerService: SqlServerService,
erpUrl: string,
erpUsername: string,
erpPassword: string
): Promise<number> {
const sql = `
UPDATE ${MIGRATION_CONFIG.tableName.sqlserver}
SET ERP_URL = @erpUrl, ERP_Username = @erpUsername, ERP_Password = @erpPassword
WHERE ERP_URL IS NULL OR ERP_URL = ''
`
const result = await sqlServerService.queryWithParams(sql, {
erpUrl: { value: erpUrl, type: require('mssql').NVarChar(500) },
erpUsername: { value: erpUsername, type: require('mssql').NVarChar(255) },
erpPassword: { value: erpPassword, type: require('mssql').NVarChar(255) }
})
return result.rowCount
}
/**
* Run migration for MySQL
*/
async function runMySQLMigration(configManager: ConfigManager): Promise<void> {
console.log('\n📦 Running MySQL Migration...')
// Read database config from .env file with correct key names
const mysqlHost = configManager.get('DB_MYSQL_HOST', 'localhost')
const mysqlPort = configManager.getNumber('DB_MYSQL_PORT', 3306)
const mysqlUser = configManager.get('DB_USERNAME', 'root')
const mysqlPassword = configManager.get('DB_PASSWORD', '')
const mysqlDatabase = configManager.get('DB_NAME', '')
console.log(`Connecting to MySQL: ${mysqlHost}:${mysqlPort}/${mysqlDatabase}`)
const mysqlService = new MySqlService({
host: mysqlHost,
port: mysqlPort,
user: mysqlUser,
password: mysqlPassword,
database: mysqlDatabase
})
try {
await mysqlService.connect()
console.log('✓ Connected to MySQL')
const tableName = MIGRATION_CONFIG.tableName.mysql
// Check and add columns
for (const [columnName, columnType] of [
['ERP_URL', 'VARCHAR(500)'],
['ERP_Username', 'VARCHAR(255)'],
['ERP_Password', 'VARCHAR(255)']
] as const) {
const exists = await checkColumnExistsMySQL(mysqlService, tableName, columnName)
if (exists) {
console.log(` ✓ Column ${columnName} already exists`)
} else {
await addColumnMySQL(mysqlService, tableName, columnName, columnType)
}
}
// Initialize ERP credentials from .env
const erpUrl = configManager.get('ERP_URL', '')
const erpUsername = configManager.get('ERP_USERNAME', '')
const erpPassword = configManager.get('ERP_PASSWORD', '')
if (erpUrl && erpUsername && erpPassword) {
const updatedCount = await initializeErpCredentialsMySQL(
mysqlService,
erpUrl,
erpUsername,
erpPassword
)
console.log(` ✓ Updated ${updatedCount} users with ERP credentials`)
} else {
console.log(' ⚠ Skipping ERP credential initialization (missing .env values)')
}
console.log('✅ MySQL Migration completed successfully!\n')
} catch (error) {
console.error('❌ MySQL Migration failed:', error)
throw error
} finally {
await mysqlService.disconnect()
}
}
/**
* Run migration for SQL Server
*/
async function runSqlServerMigration(configManager: ConfigManager): Promise<void> {
console.log('\n📦 Running SQL Server Migration...')
const mssql = await import('mssql')
const sqlServerService = new SqlServerService({
server: configManager.get('DB_SERVER', 'localhost'),
port: configManager.getNumber('DB_SQLSERVER_PORT', 1433),
user: configManager.get('DB_USERNAME', 'sa'),
password: configManager.get('DB_PASSWORD', ''),
database: configManager.get('DB_NAME', ''),
options: {
encrypt: false,
trustServerCertificate: configManager.get('DB_TRUST_SERVER_CERTIFICATE') === 'yes'
}
})
try {
await sqlServerService.connect()
console.log('✓ Connected to SQL Server')
const tableName = MIGRATION_CONFIG.tableName.sqlserver
// Check and add columns
for (const [columnName, columnType] of [
['ERP_URL', 'NVARCHAR(500)'],
['ERP_Username', 'NVARCHAR(255)'],
['ERP_Password', 'NVARCHAR(255)']
] as const) {
const exists = await checkColumnExistsSqlServer(sqlServerService, tableName, columnName)
if (exists) {
console.log(` ✓ Column ${columnName} already exists`)
} else {
await addColumnSqlServer(sqlServerService, tableName, columnName, columnType)
}
}
// Initialize ERP credentials from .env
const erpUrl = configManager.get('ERP_URL', '')
const erpUsername = configManager.get('ERP_USERNAME', '')
const erpPassword = configManager.get('ERP_PASSWORD', '')
if (erpUrl && erpUsername && erpPassword) {
const updatedCount = await initializeErpCredentialsSqlServer(
sqlServerService,
erpUrl,
erpUsername,
erpPassword
)
console.log(` ✓ Updated ${updatedCount} users with ERP credentials`)
} else {
console.log(' ⚠ Skipping ERP credential initialization (missing .env values)')
}
console.log('✅ SQL Server Migration completed successfully!\n')
} catch (error) {
console.error('❌ SQL Server Migration failed:', error)
throw error
} finally {
await sqlServerService.disconnect()
}
}
/**
* Main migration runner
*/
async function runMigration(): Promise<void> {
console.log('==============================================')
console.log('BIPUsers Table Migration: Add ERP Parameters')
console.log('==============================================\n')
const configManager = ConfigManager.getInstance()
await configManager.initialize()
const dbType = configManager.get('DB_TYPE', 'mysql').toLowerCase()
const isSqlServer = dbType === 'sqlserver' || dbType === 'mssql'
try {
if (isSqlServer) {
await runSqlServerMigration(configManager)
} else {
await runMySQLMigration(configManager)
}
console.log('==============================================')
console.log('Migration Summary:')
console.log('==============================================')
console.log(`Database Type: ${isSqlServer ? 'SQL Server' : 'MySQL'}`)
console.log('Columns Added/Verified:')
console.log(' - ERP_URL (VARCHAR/NVARCHAR 500)')
console.log(' - ERP_Username (VARCHAR/NVARCHAR 255)')
console.log(' - ERP_Password (VARCHAR/NVARCHAR 255)')
console.log('==============================================\n')
} catch (error) {
console.error('\n❌ Migration failed with error:', error)
process.exit(1)
}
}
// Run migration
runMigration().catch((error) => {
console.error('Unexpected error:', error)
process.exit(1)
})

View File

@@ -0,0 +1,89 @@
-- ============================================
-- BIPUsers Table Migration: Add ERP Parameters
-- Database: MySQL
-- ============================================
-- This script adds three new columns to store ERP connection parameters:
-- - ERP_URL: The ERP system URL
-- - ERP_Username: The ERP username
-- - ERP_Password: The ERP password
--
-- Usage: Run this script in your MySQL client
-- Example: mysql -u root -p BLD_DB < add-erp-params-to-bipusers-mysql.sql
-- ============================================
USE BLD_DB;
-- Add ERP_URL column if not exists
SET @dbname = DATABASE();
SET @tablename = 'dbo_BIPUsers';
SET @columnname = 'ERP_URL';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(table_name = @tablename)
AND (table_schema = @dbname)
AND (column_name = @columnname)
) > 0,
'SELECT 1',
CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN ', @columnname, ' VARCHAR(500) NULL')
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- Add ERP_Username column if not exists
SET @columnname = 'ERP_Username';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(table_name = @tablename)
AND (table_schema = @dbname)
AND (column_name = @columnname)
) > 0,
'SELECT 1',
CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN ', @columnname, ' VARCHAR(255) NULL')
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- Add ERP_Password column if not exists
SET @columnname = 'ERP_Password';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(table_name = @tablename)
AND (table_schema = @dbname)
AND (column_name = @columnname)
) > 0,
'SELECT 1',
CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN ', @columnname, ' VARCHAR(255) NULL')
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- Verify columns were added
SELECT
COLUMN_NAME,
DATA_TYPE,
CHARACTER_MAXIMUM_LENGTH,
IS_NULLABLE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'dbo_BIPUsers'
AND COLUMN_NAME IN ('ERP_URL', 'ERP_Username', 'ERP_Password');
-- Optional: Update all existing users with the same ERP credentials
-- Replace the values below with your actual ERP credentials
-- Example:
-- UPDATE dbo_BIPUsers
-- SET ERP_URL = 'https://68.11.34.30:8082/',
-- ERP_Username = 'your_username',
-- ERP_Password = 'your_password'
-- WHERE ERP_URL IS NULL;
SELECT 'Migration completed successfully!' AS status;

View File

@@ -0,0 +1,114 @@
/**
* Database Migration Script
* Add ERP configuration fields to dbo_BIPUsers table
*
* This script adds three new columns to store ERP connection parameters:
* - ERP_URL: The ERP system URL
* - ERP_USERNAME: The ERP username
* - ERP_PASSWORD: The ERP password (encrypted in production)
*
* IMPORTANT:
* - For SQL Server: Run this script on the SQL Server database
* - For MySQL: Run this script on the MySQL database (syntax is auto-detected)
* - All existing users will have the same ERP credentials (to be configured individually later)
*/
-- ===========================================
-- SQL Server Version
-- ===========================================
-- Uncomment and run this section for SQL Server
/*
IF NOT EXISTS (SELECT * FROM sys.columns
WHERE object_id = OBJECT_ID(N'[dbo].[BIPUsers]')
AND name = 'ERP_URL')
BEGIN
ALTER TABLE [dbo].[BIPUsers]
ADD ERP_URL NVARCHAR(500) NULL;
ALTER TABLE [dbo].[BIPUsers]
ADD ERP_Username NVARCHAR(255) NULL;
ALTER TABLE [dbo].[BIPUsers]
ADD ERP_Password NVARCHAR(255) NULL;
PRINT 'ERP columns added successfully to [dbo].[BIPUsers]';
END
ELSE
BEGIN
PRINT 'ERP columns already exist in [dbo].[BIPUsers]';
END
-- Optional: Update all existing users with the same ERP credentials
-- Replace the values below with your actual ERP credentials
-- UPDATE [dbo].[BIPUsers]
-- SET ERP_URL = 'https://your-erp-system.com',
-- ERP_Username = 'your_username',
-- ERP_Password = 'your_password'
-- WHERE ERP_URL IS NULL;
*/
-- ===========================================
-- MySQL Version
-- ===========================================
-- Run this section for MySQL
-- Add ERP_URL column if not exists
SET @dbname = DATABASE();
SET @tablename = 'dbo_BIPUsers';
SET @columnname = 'ERP_URL';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(table_name = @tablename)
AND (table_schema = @dbname)
AND (column_name = @columnname)
) > 0,
'SELECT 1',
CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN ', @columnname, ' VARCHAR(500) NULL')
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- Add ERP_Username column if not exists
SET @columnname = 'ERP_Username';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(table_name = @tablename)
AND (table_schema = @dbname)
AND (column_name = @columnname)
) > 0,
'SELECT 1',
CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN ', @columnname, ' VARCHAR(255) NULL')
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- Add ERP_Password column if not exists
SET @columnname = 'ERP_Password';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(table_name = @tablename)
AND (table_schema = @dbname)
AND (column_name = @columnname)
) > 0,
'SELECT 1',
CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN ', @columnname, ' VARCHAR(255) NULL')
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- Optional: Update all existing users with the same ERP credentials
-- Replace the values below with your actual ERP credentials
-- UPDATE dbo_BIPUsers
-- SET ERP_URL = 'https://your-erp-system.com',
-- ERP_Username = 'your_username',
-- ERP_Password = 'your_password'
-- WHERE ERP_URL IS NULL;

View File

@@ -0,0 +1,98 @@
-- ============================================
-- BIPUsers 表迁移:添加 ERP 参数字段
-- 数据库MySQL
-- 目标数据库BLD_DB
-- ============================================
-- 使用说明:
-- 1. 在 MySQL Workbench / Navicat / DBeaver 中打开此文件
-- 2. 连接到数据库 192.168.31.83:3306/BLD_DB
-- 3. 执行全部 SQL 语句
-- ============================================
-- 切换到目标数据库
USE BLD_DB;
-- ============================================
-- 步骤 1: 添加新字段
-- ============================================
-- 添加 ERP_URL 字段(如果不存在)
-- 注意:如果 MySQL 版本不支持 ADD COLUMN IF NOT EXISTS请移除 IF NOT EXISTS
ALTER TABLE dbo_BIPUsers
ADD COLUMN ERP_URL VARCHAR(500) NULL COMMENT 'ERP 系统 URL';
-- 添加 ERP_Username 字段
ALTER TABLE dbo_BIPUsers
ADD COLUMN ERP_Username VARCHAR(255) NULL COMMENT 'ERP 用户名';
-- 添加 ERP_Password 字段
ALTER TABLE dbo_BIPUsers
ADD COLUMN ERP_Password VARCHAR(255) NULL COMMENT 'ERP 密码';
-- ============================================
-- 步骤 2: 验证字段已添加
-- ============================================
-- 显示表结构,确认新字段已添加
SELECT '字段添加验证' AS step;
DESCRIBE dbo_BIPUsers;
-- 或者使用以下查询确认新字段
SELECT
COLUMN_NAME AS '字段名',
DATA_TYPE AS '数据类型',
CHARACTER_MAXIMUM_LENGTH AS '最大长度',
IS_NULLABLE AS '允许 NULL',
COLUMN_COMMENT AS '注释'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'BLD_DB'
AND TABLE_NAME = 'dbo_BIPUsers'
AND COLUMN_NAME IN ('ERP_URL', 'ERP_Username', 'ERP_Password')
ORDER BY COLUMN_NAME;
-- ============================================
-- 步骤 3: 初始化 ERP 配置
-- 注意:请根据实际情况修改下面的配置值!
-- ============================================
SELECT '=== 请修改下面的 ERP 配置值 ===' AS notice;
SELECT '当前数据库中的用户:' AS notice;
SELECT UserName, UserType, ComputerName FROM dbo_BIPUsers ORDER BY UserName;
-- 更新所有用户的 ERP 配置
-- ⚠️ 请修改下面的配置值为你实际的 ERP 凭证!
UPDATE dbo_BIPUsers
SET
ERP_URL = 'https://68.11.34.30:8082/', -- 修改为你的 ERP 系统 URL
ERP_Username = 'your_erp_username', -- 修改为你的 ERP 用户名
ERP_Password = 'your_erp_password' -- 修改为你的 ERP 密码
WHERE ERP_URL IS NULL OR ERP_URL = '';
-- 显示更新后的结果
SELECT
'更新后的 ERP 配置' AS notice,
UserName,
ERP_URL,
ERP_Username
FROM dbo_BIPUsers
ORDER BY UserName;
-- ============================================
-- 步骤 4: 完成确认
-- ============================================
SELECT '================================' AS '';
SELECT '迁移完成!' AS message;
SELECT '================================' AS '';
SELECT '请确认:' AS notice;
SELECT '1. 所有用户都有 ERP_URL 配置' AS check1;
SELECT '2. ERP_URL 格式正确' AS check2;
SELECT '3. ERP 用户名和密码正确' AS check3;
SELECT '================================' AS '';
-- 统计信息
SELECT
COUNT(*) AS total_users,
COUNT(ERP_URL) AS users_with_erp_url,
COUNT(ERP_Username) AS users_with_erp_username
FROM dbo_BIPUsers;

View File

@@ -0,0 +1,184 @@
/**
* Simple Migration Script: Add ERP parameters to BIPUsers table
*
* This script adds ERP_URL, ERP_Username, and ERP_Password columns
* to the dbo_BIPUsers table.
*
* Usage:
* npx tsx src/main/services/user/migration/run-migration.ts
*/
import * as mysql from 'mysql2/promise'
import * as fs from 'fs'
import * as path from 'path'
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
/**
* Load .env file manually
*/
function loadEnv(filePath: string): Map<string, string> {
const envMap = new Map<string, string>()
if (!fs.existsSync(filePath)) {
console.warn(`.env file not found: ${filePath}`)
return envMap
}
const content = fs.readFileSync(filePath, 'utf-8')
const lines = content.split('\n')
for (const line of lines) {
const trimmedLine = line.trim()
if (!trimmedLine || trimmedLine.startsWith('#')) {
continue
}
const [key, ...valueParts] = trimmedLine.split('=')
if (key && valueParts.length > 0) {
const value = valueParts.join('=').trim()
envMap.set(key.trim(), value)
}
}
return envMap
}
/**
* Check if column exists in MySQL table
*/
async function checkColumnExists(
connection: mysql.Connection,
tableName: string,
columnName: string
): Promise<boolean> {
const sql = `
SELECT COUNT(*) as count
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME = ?
`
const [rows] = await connection.query(sql, [tableName, columnName])
const result = rows as any[]
return result.length > 0 && result[0].count > 0
}
/**
* Add column to MySQL table
*/
async function addColumn(
connection: mysql.Connection,
tableName: string,
columnName: string,
columnType: string
): Promise<void> {
const sql = `ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnType} NULL`
await connection.query(sql)
console.log(` ✓ Added column ${columnName} to ${tableName}`)
}
/**
* Main migration function
*/
async function runMigration(): Promise<void> {
console.log('==============================================')
console.log('BIPUsers Table Migration: Add ERP Parameters')
console.log('==============================================\n')
// Load .env file from project root
const envPath = path.resolve(process.cwd(), '.env')
console.log(`Loading .env from: ${envPath}`)
const env = loadEnv(envPath)
// Get database configuration
const dbHost = env.get('DB_MYSQL_HOST') || 'localhost'
const dbPort = parseInt(env.get('DB_MYSQL_PORT') || '3306', 10)
const dbUser = env.get('DB_USERNAME') || 'root'
const dbPassword = env.get('DB_PASSWORD') || ''
const dbName = env.get('DB_NAME') || ''
console.log(`Database: ${dbHost}:${dbPort}/${dbName}`)
console.log(`Username: ${dbUser}`)
console.log('')
let connection: mysql.Connection | null = null
try {
// Connect to MySQL
console.log('Connecting to MySQL...')
connection = await mysql.createConnection({
host: dbHost,
port: dbPort,
user: dbUser,
password: dbPassword,
database: dbName
})
console.log('✓ Connected to MySQL\n')
const tableName = 'dbo_BIPUsers'
// Check and add columns
console.log('Checking columns...')
for (const [columnName, columnType] of [
['ERP_URL', 'VARCHAR(500)'],
['ERP_Username', 'VARCHAR(255)'],
['ERP_Password', 'VARCHAR(255)']
] as const) {
const exists = await checkColumnExists(connection, tableName, columnName)
if (exists) {
console.log(` ✓ Column ${columnName} already exists`)
} else {
await addColumn(connection, tableName, columnName, columnType)
}
}
console.log('\n==============================================')
console.log('Migration Summary:')
console.log('==============================================')
console.log('Database Type: MySQL')
console.log('Database: ' + dbName)
console.log('Columns Added/Verified:')
console.log(' - ERP_URL (VARCHAR 500)')
console.log(' - ERP_Username (VARCHAR 255)')
console.log(' - ERP_Password (VARCHAR 255)')
console.log('==============================================')
console.log('\n✅ Migration completed successfully!\n')
console.log('Next steps:')
console.log('1. Update ERP credentials for users in dbo_BIPUsers table')
console.log('2. Example SQL:')
console.log(` UPDATE ${tableName}`)
console.log(` SET ERP_URL = 'https://your-erp.com',`)
console.log(` ERP_Username = 'your_username',`)
console.log(` ERP_Password = 'your_password'`)
console.log(` WHERE ERP_URL IS NULL;\n`)
} catch (error) {
console.error('\n❌ Migration failed with error:')
console.error(error)
console.error('\nTroubleshooting:')
console.error('1. Check if MySQL server is running')
console.error('2. Verify database credentials in .env file')
console.error('3. Ensure database "' + dbName + '" exists')
console.error('4. Check network connectivity to ' + dbHost + ':' + dbPort)
process.exit(1)
} finally {
// Disconnect
if (connection) {
try {
await connection.end()
console.log('Disconnected from MySQL')
} catch (e) {
// Ignore disconnect errors
}
}
}
}
// Run migration
runMigration().catch((error) => {
console.error('Unexpected error:', error)
process.exit(1)
})

View File

@@ -0,0 +1,200 @@
/**
* User ERP Configuration Service
*
* Manages ERP configuration (URL, username, password) stored in the BIPUsers table.
* Each user can have their own ERP credentials.
*
* Features:
* - Get current user's ERP config
* - Update current user's ERP config
* - Get ERP config for any user (admin only)
*/
import { BIPUsersDAO } from './bip-users-dao'
import { SessionManager } from './session-manager'
import { createLogger } from '../logger'
const log = createLogger('UserErpConfigService')
/**
* ERP Configuration object
*/
export interface ErpConfig {
url: string
username: string
password: string
}
/**
* User ERP Configuration Service Class
*/
export class UserErpConfigService {
private static instance: UserErpConfigService | null = null
private dao: BIPUsersDAO
private constructor() {
this.dao = new BIPUsersDAO()
}
/**
* Get the singleton instance
*/
public static getInstance(): UserErpConfigService {
if (UserErpConfigService.instance === null) {
UserErpConfigService.instance = new UserErpConfigService()
}
return UserErpConfigService.instance
}
/**
* Get ERP configuration for the current authenticated user
* @returns ERP configuration or null if not found
*/
async getCurrentUserErpConfig(): Promise<ErpConfig | null> {
try {
const sessionManager = SessionManager.getInstance()
const currentUser = sessionManager.getUserInfo()
if (!currentUser) {
log.warn('No authenticated user found')
return null
}
log.info('Fetching ERP config for user', { username: currentUser.username })
const config = await this.dao.getUserErpConfig(currentUser.username)
if (!config) {
log.warn('No ERP config found for user', { username: currentUser.username })
return null
}
log.info('ERP config retrieved successfully', {
username: currentUser.username,
hasUrl: !!config.url,
hasUsername: !!config.username,
hasPassword: !!config.password
})
return config
} catch (error) {
log.error('Error getting current user ERP config', { error })
return null
}
}
/**
* Get ERP configuration for a specific user (admin only)
* @param username - The username to get ERP config for
* @returns ERP configuration or null if not found
*/
async getUserErpConfig(username: string): Promise<ErpConfig | null> {
try {
log.info('Fetching ERP config for user', { username })
const config = await this.dao.getUserErpConfig(username)
if (!config) {
log.warn('No ERP config found for user', { username })
return null
}
return config
} catch (error) {
log.error('Error getting user ERP config', { error })
return null
}
}
/**
* Update ERP configuration for the current authenticated user
* @param config - ERP configuration to save
* @returns True if successful
*/
async updateCurrentUserErpConfig(config: ErpConfig): Promise<boolean> {
try {
const sessionManager = SessionManager.getInstance()
const currentUser = sessionManager.getUserInfo()
if (!currentUser) {
log.warn('No authenticated user found')
return false
}
log.info('Updating ERP config for user', { username: currentUser.username })
const success = await this.dao.updateUserErpConfig(
currentUser.username,
config.url,
config.username,
config.password
)
if (success) {
log.info('ERP config updated successfully', { username: currentUser.username })
} else {
log.error('Failed to update ERP config', { username: currentUser.username })
}
return success
} catch (error) {
log.error('Error updating current user ERP config', { error })
return false
}
}
/**
* Update ERP configuration for a specific user (admin only)
* @param username - The username to update ERP config for
* @param config - ERP configuration to save
* @returns True if successful
*/
async updateUserErpConfig(username: string, config: ErpConfig): Promise<boolean> {
try {
log.info('Updating ERP config for user', { username })
const success = await this.dao.updateUserErpConfig(
username,
config.url,
config.username,
config.password
)
if (success) {
log.info('ERP config updated successfully', { username })
} else {
log.error('Failed to update ERP config', { username })
}
return success
} catch (error) {
log.error('Error updating user ERP config', { error })
return false
}
}
/**
* Get ERP configuration for all users (admin only, for migration/audit)
* @returns List of users with their ERP configurations
*/
async getAllUsersErpConfig(): Promise<
Array<{
username: string
erpUrl: string
erpUsername: string
}>
> {
try {
log.info('Fetching ERP config for all users')
const configs = await this.dao.getAllUsersErpConfig()
log.info('Retrieved ERP configs for all users', { count: configs.length })
return configs
} catch (error) {
log.error('Error getting all users ERP config', { error })
return []
}
}
/**
* Disconnect from database
*/
async disconnect(): Promise<void> {
await this.dao.disconnect()
}
}

View File

@@ -235,6 +235,39 @@ export interface MaterialTypeAPI {
}>
}
/**
* User ERP Configuration API
*/
export interface UserErpConfigAPI {
/**
* Get current user's ERP configuration
*/
getCurrent: () => Promise<{
success: boolean
config?: { url: string; username: string; password: string }
error?: string
}>
/**
* Update current user's ERP configuration
*/
update: (config: { url: string; username: string; password: string }) => Promise<{
success: boolean
config?: { url: string; username: string; password: string }
error?: string
}>
/**
* Test ERP connection with provided credentials
*/
testConnection: (config: { url: string; username: string; password: string }) => Promise<{
success: boolean
message?: string
}>
/**
* Get all users' ERP configurations (admin only)
*/
getAll: () => Promise<Array<{ username: string; erpUrl: string; erpUsername: string }>>
}
declare global {
interface Window {
electron: {
@@ -263,6 +296,7 @@ declare global {
materials: MaterialsAPI
settings: SettingsAPI
materialType: MaterialTypeAPI
userErpConfig: UserErpConfigAPI
}
api: unknown
}

View File

@@ -137,6 +137,16 @@ const api = {
ipcRenderer.invoke('materialType:delete', { materialName, managerName }),
upsertBatch: (request: MaterialTypeBatchRequest) =>
ipcRenderer.invoke('materialType:upsertBatch', request)
},
// User ERP Configuration service
userErpConfig: {
getCurrent: () => ipcRenderer.invoke('user-erp-config:getCurrent'),
update: (config: { url: string; username: string; password: string }) =>
ipcRenderer.invoke('user-erp-config:update', config),
testConnection: (config: { url: string; username: string; password: string }) =>
ipcRenderer.invoke('user-erp-config:testConnection', config),
getAll: () => ipcRenderer.invoke('user-erp-config:getAll')
}
} as const