From 5977254180ed64fa38659ff7519af4c7ff46a9ee Mon Sep 17 00:00:00 2001 From: Misaka Date: Thu, 5 Mar 2026 21:52:54 +0800 Subject: [PATCH] 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 --- docs/MIGRATION_GUIDE.md | 171 ++++++++++ src/main/ipc/cleaner-handler.ts | 48 ++- src/main/ipc/extractor-handler.ts | 49 ++- src/main/ipc/index.ts | 2 + src/main/ipc/user-erp-config-handler.ts | 191 +++++++++++ src/main/services/config/config-manager.ts | 39 +-- src/main/services/user/bip-users-dao.ts | 162 ++++++++- .../migration/add-erp-params-migration.ts | 316 ++++++++++++++++++ .../add-erp-params-to-bipusers-mysql.sql | 89 +++++ .../migration/add-erp-params-to-bipusers.sql | 114 +++++++ .../user/migration/manual-migration.sql | 98 ++++++ .../services/user/migration/run-migration.ts | 184 ++++++++++ .../services/user/user-erp-config-service.ts | 200 +++++++++++ src/preload/index.d.ts | 34 ++ src/preload/index.ts | 10 + 15 files changed, 1654 insertions(+), 53 deletions(-) create mode 100644 docs/MIGRATION_GUIDE.md create mode 100644 src/main/ipc/user-erp-config-handler.ts create mode 100644 src/main/services/user/migration/add-erp-params-migration.ts create mode 100644 src/main/services/user/migration/add-erp-params-to-bipusers-mysql.sql create mode 100644 src/main/services/user/migration/add-erp-params-to-bipusers.sql create mode 100644 src/main/services/user/migration/manual-migration.sql create mode 100644 src/main/services/user/migration/run-migration.ts create mode 100644 src/main/services/user/user-erp-config-service.ts diff --git a/docs/MIGRATION_GUIDE.md b/docs/MIGRATION_GUIDE.md new file mode 100644 index 0000000..240f1ae --- /dev/null +++ b/docs/MIGRATION_GUIDE.md @@ -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 diff --git a/src/main/ipc/cleaner-handler.ts b/src/main/ipc/cleaner-handler.ts index 56808fb..acad705 100644 --- a/src/main/ipc/cleaner-handler.ts +++ b/src/main/ipc/cleaner-handler.ts @@ -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 { } } +/** + * 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 }) diff --git a/src/main/ipc/extractor-handler.ts b/src/main/ipc/extractor-handler.ts index f0a76a1..fedc322 100644 --- a/src/main/ipc/extractor-handler.ts +++ b/src/main/ipc/extractor-handler.ts @@ -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 }) diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index 58ba777..0155055 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -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') } diff --git a/src/main/ipc/user-erp-config-handler.ts b/src/main/ipc/user-erp-config-handler.ts new file mode 100644 index 0000000..e5df880 --- /dev/null +++ b/src/main/ipc/user-erp-config-handler.ts @@ -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 => { + 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 => { + 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 => { + 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 [] + } + } + ) +} diff --git a/src/main/services/config/config-manager.ts b/src/main/services/config/config-manager.ts index 3f5a7b4..c821c19 100644 --- a/src/main/services/config/config-manager.ts +++ b/src/main/services/config/config-manager.ts @@ -108,11 +108,13 @@ function deepMerge(source: T, target: Partial): 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 { - // 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) diff --git a/src/main/services/user/bip-users-dao.ts b/src/main/services/user/bip-users-dao.ts index fb82683..6dd0f3e 100644 --- a/src/main/services/user/bip-users-dao.ts +++ b/src/main/services/user/bip-users-dao.ts @@ -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 { + 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 */ diff --git a/src/main/services/user/migration/add-erp-params-migration.ts b/src/main/services/user/migration/add-erp-params-migration.ts new file mode 100644 index 0000000..6b4e8b2 --- /dev/null +++ b/src/main/services/user/migration/add-erp-params-migration.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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) +}) diff --git a/src/main/services/user/migration/add-erp-params-to-bipusers-mysql.sql b/src/main/services/user/migration/add-erp-params-to-bipusers-mysql.sql new file mode 100644 index 0000000..c1946c8 --- /dev/null +++ b/src/main/services/user/migration/add-erp-params-to-bipusers-mysql.sql @@ -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; diff --git a/src/main/services/user/migration/add-erp-params-to-bipusers.sql b/src/main/services/user/migration/add-erp-params-to-bipusers.sql new file mode 100644 index 0000000..5123475 --- /dev/null +++ b/src/main/services/user/migration/add-erp-params-to-bipusers.sql @@ -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; diff --git a/src/main/services/user/migration/manual-migration.sql b/src/main/services/user/migration/manual-migration.sql new file mode 100644 index 0000000..078f06c --- /dev/null +++ b/src/main/services/user/migration/manual-migration.sql @@ -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; diff --git a/src/main/services/user/migration/run-migration.ts b/src/main/services/user/migration/run-migration.ts new file mode 100644 index 0000000..861feee --- /dev/null +++ b/src/main/services/user/migration/run-migration.ts @@ -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 { + const envMap = new Map() + + 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 { + 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 { + 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 { + 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) +}) diff --git a/src/main/services/user/user-erp-config-service.ts b/src/main/services/user/user-erp-config-service.ts new file mode 100644 index 0000000..f2c7d47 --- /dev/null +++ b/src/main/services/user/user-erp-config-service.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + await this.dao.disconnect() + } +} diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index af6bee8..371b715 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -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> +} + declare global { interface Window { electron: { @@ -263,6 +296,7 @@ declare global { materials: MaterialsAPI settings: SettingsAPI materialType: MaterialTypeAPI + userErpConfig: UserErpConfigAPI } api: unknown } diff --git a/src/preload/index.ts b/src/preload/index.ts index f1410f2..27c576a 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -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