refactor: split validation handler responsibilities
This commit is contained in:
@@ -1,478 +1,49 @@
|
|||||||
/**
|
/**
|
||||||
* IPC handlers for material validation operations
|
* IPC handlers for material validation operations
|
||||||
*
|
|
||||||
* Provides endpoints for:
|
|
||||||
* - Running material validation from database
|
|
||||||
* - Getting/setting materials to be deleted
|
|
||||||
* - Manager-based filtering
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ipcMain } from 'electron'
|
import { ipcMain } from 'electron'
|
||||||
import { MySqlService } from '../services/database/mysql'
|
|
||||||
import { SqlServerService } from '../services/database/sql-server'
|
|
||||||
import { MaterialsToBeDeletedDAO } from '../services/database/materials-to-be-deleted-dao'
|
import { MaterialsToBeDeletedDAO } from '../services/database/materials-to-be-deleted-dao'
|
||||||
import { DiscreteMaterialPlanDAO } from '../services/database/discrete-material-plan-dao'
|
|
||||||
import { ConfigManager } from '../services/config/config-manager'
|
|
||||||
import { createLogger } from '../services/logger'
|
import { createLogger } from '../services/logger'
|
||||||
import type {
|
import type {
|
||||||
ValidationRequest,
|
|
||||||
ValidationResponse,
|
|
||||||
MaterialUpsertBatchRequest,
|
|
||||||
MaterialDeleteRequest,
|
MaterialDeleteRequest,
|
||||||
MaterialOperationResponse,
|
MaterialOperationResponse,
|
||||||
ValidationResult,
|
MaterialRecordSummary,
|
||||||
MaterialRecordSummary
|
MaterialUpsertBatchRequest,
|
||||||
|
ValidationRequest,
|
||||||
|
ValidationResponse
|
||||||
} from '../types/validation.types'
|
} from '../types/validation.types'
|
||||||
import { IPC_CHANNELS } from '../../shared/ipc-channels'
|
import { IPC_CHANNELS } from '../../shared/ipc-channels'
|
||||||
|
import { sharedProductionIdsStore } from '../services/validation/shared-production-ids-store'
|
||||||
|
import { validationApplicationService } from '../services/validation/validation-application-service'
|
||||||
|
|
||||||
const log = createLogger('ValidationHandler')
|
const log = createLogger('ValidationHandler')
|
||||||
|
|
||||||
/**
|
|
||||||
* Shared state for Production IDs from extractor page
|
|
||||||
* This is a simple in-memory store for sharing Production IDs between pages
|
|
||||||
*/
|
|
||||||
const sharedProductionIdsBySender = new Map<number, Set<string>>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set shared Production IDs
|
|
||||||
*/
|
|
||||||
export function setSharedProductionIds(senderId: number, ids: string[]): void {
|
|
||||||
sharedProductionIdsBySender.set(senderId, new Set(ids))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get shared Production IDs
|
|
||||||
*/
|
|
||||||
export function getSharedProductionIds(senderId: number): string[] {
|
|
||||||
const senderSet = sharedProductionIdsBySender.get(senderId)
|
|
||||||
return senderSet ? [...senderSet] : []
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear shared Production IDs
|
|
||||||
*/
|
|
||||||
export function clearSharedProductionIds(senderId: number): void {
|
|
||||||
sharedProductionIdsBySender.delete(senderId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get database service for validation operations (MySQL or SQL Server)
|
|
||||||
*/
|
|
||||||
async function getValidationDatabaseService(): Promise<MySqlService | SqlServerService> {
|
|
||||||
const configManager = ConfigManager.getInstance()
|
|
||||||
const config = configManager.getConfig()
|
|
||||||
const dbType = configManager.getDatabaseType()
|
|
||||||
|
|
||||||
if (dbType === 'sqlserver') {
|
|
||||||
const dbConfig = config.database.sqlserver
|
|
||||||
const sqlServerService = new SqlServerService({
|
|
||||||
server: dbConfig.server,
|
|
||||||
port: dbConfig.port,
|
|
||||||
user: dbConfig.username,
|
|
||||||
password: dbConfig.password,
|
|
||||||
database: dbConfig.database,
|
|
||||||
options: {
|
|
||||||
encrypt: false,
|
|
||||||
trustServerCertificate: dbConfig.trustServerCertificate
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await sqlServerService.connect()
|
|
||||||
return sqlServerService
|
|
||||||
} else {
|
|
||||||
const dbConfig = config.database.mysql
|
|
||||||
const mysqlService = new MySqlService({
|
|
||||||
host: dbConfig.host,
|
|
||||||
port: dbConfig.port,
|
|
||||||
user: dbConfig.username,
|
|
||||||
password: dbConfig.password,
|
|
||||||
database: dbConfig.database
|
|
||||||
})
|
|
||||||
await mysqlService.connect()
|
|
||||||
return mysqlService
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get table name based on database type
|
|
||||||
* Converts MySQL schema_tablename format to SQL Server [schema].[tablename] format
|
|
||||||
* e.g., productionContractData_26年压力表合同数据 -> [productionContractData].[26年压力表合同数据]
|
|
||||||
* dbo_MaterialsToBeDeleted -> [dbo].[MaterialsToBeDeleted]
|
|
||||||
*/
|
|
||||||
function getTableName(mysqlTableName: string): string {
|
|
||||||
const configManager = ConfigManager.getInstance()
|
|
||||||
const dbType = configManager.getDatabaseType()
|
|
||||||
|
|
||||||
if (dbType === 'sqlserver') {
|
|
||||||
// Find the FIRST underscore to split schema and table name
|
|
||||||
// This handles patterns like: schema_tablename
|
|
||||||
const firstUnderscoreIndex = mysqlTableName.indexOf('_')
|
|
||||||
if (firstUnderscoreIndex > 0) {
|
|
||||||
const schema = mysqlTableName.substring(0, firstUnderscoreIndex)
|
|
||||||
const tableName = mysqlTableName.substring(firstUnderscoreIndex + 1)
|
|
||||||
return `[${schema}].[${tableName}]`
|
|
||||||
}
|
|
||||||
// If no underscore found, default to dbo schema
|
|
||||||
return `[dbo].[${mysqlTableName}]`
|
|
||||||
}
|
|
||||||
return mysqlTableName
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read Production IDs from file
|
|
||||||
*/
|
|
||||||
function readProductionIds(filePath: string): string[] {
|
|
||||||
const fs = require('fs')
|
|
||||||
const content = fs.readFileSync(filePath, 'utf-8') as string
|
|
||||||
return content
|
|
||||||
.split('\n')
|
|
||||||
.map((line: string) => line.trim())
|
|
||||||
.filter((line: string) => line.length > 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Identify input type (production ID or order number)
|
|
||||||
*/
|
|
||||||
function identifyInputType(input: string): 'production_id' | 'order_number' | 'unknown' {
|
|
||||||
// Order number: SC + 14 digits
|
|
||||||
if (/^SC\d{14}$/.test(input)) {
|
|
||||||
return 'order_number'
|
|
||||||
}
|
|
||||||
// Production ID: 2 digits + 1 letter + 1-6 digits
|
|
||||||
if (/^\d{2}[A-Za-z]\d{1,6}$/.test(input)) {
|
|
||||||
return 'production_id'
|
|
||||||
}
|
|
||||||
return 'unknown'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get source numbers from inputs
|
|
||||||
*/
|
|
||||||
async function getSourceNumbersFromInputs(
|
|
||||||
inputs: string[],
|
|
||||||
dbService: MySqlService | SqlServerService
|
|
||||||
): Promise<string[]> {
|
|
||||||
const productionIds: string[] = []
|
|
||||||
const orderNumbers: string[] = []
|
|
||||||
const configManager = ConfigManager.getInstance()
|
|
||||||
const dbType = configManager.getDatabaseType()
|
|
||||||
const isSqlServer = dbType === 'sqlserver'
|
|
||||||
|
|
||||||
for (const item of inputs) {
|
|
||||||
const type = identifyInputType(item)
|
|
||||||
if (type === 'order_number') {
|
|
||||||
orderNumbers.push(item)
|
|
||||||
} else if (type === 'production_id') {
|
|
||||||
productionIds.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query production contract data for production IDs
|
|
||||||
// Table name in MySQL: productionContractData_26年压力表合同数据
|
|
||||||
// Column name: 生产订单号 (SourceNumber)
|
|
||||||
if (productionIds.length > 0) {
|
|
||||||
const contractTableName = getTableName('productionContractData_26年压力表合同数据')
|
|
||||||
const batchSize = 2000
|
|
||||||
|
|
||||||
if (isSqlServer) {
|
|
||||||
const sql = require('mssql')
|
|
||||||
const allOrderNumbers: string[] = []
|
|
||||||
|
|
||||||
for (let i = 0; i < productionIds.length; i += batchSize) {
|
|
||||||
const batch = productionIds.slice(i, i + batchSize)
|
|
||||||
const placeholders = batch.map((_, idx) => `@p${idx}`).join(',')
|
|
||||||
const params: Record<string, { value: string; type: any }> = {}
|
|
||||||
|
|
||||||
batch.forEach((id, idx) => {
|
|
||||||
params[`p${idx}`] = { value: id, type: sql.NVarChar }
|
|
||||||
})
|
|
||||||
|
|
||||||
const contractSql = `
|
|
||||||
SELECT DISTINCT 生产订单号
|
|
||||||
FROM ${contractTableName}
|
|
||||||
WHERE 总排号 IN (${placeholders})
|
|
||||||
`
|
|
||||||
const contractResult = await (dbService as SqlServerService).queryWithParams(
|
|
||||||
contractSql,
|
|
||||||
params
|
|
||||||
)
|
|
||||||
const dbOrderNumbers = contractResult.rows.map((row) => row.生产订单号 as string)
|
|
||||||
allOrderNumbers.push(...dbOrderNumbers)
|
|
||||||
}
|
|
||||||
|
|
||||||
orderNumbers.push(...allOrderNumbers)
|
|
||||||
} else {
|
|
||||||
const allOrderNumbers: string[] = []
|
|
||||||
|
|
||||||
for (let i = 0; i < productionIds.length; i += batchSize) {
|
|
||||||
const batch = productionIds.slice(i, i + batchSize)
|
|
||||||
const placeholders = batch.map(() => '?').join(',')
|
|
||||||
const contractSql = `
|
|
||||||
SELECT DISTINCT 生产订单号
|
|
||||||
FROM ${contractTableName}
|
|
||||||
WHERE 总排号 IN (${placeholders})
|
|
||||||
`
|
|
||||||
const contractResult = await (dbService as MySqlService).query(contractSql, batch)
|
|
||||||
const dbOrderNumbers = contractResult.rows.map((row) => row.生产订单号 as string)
|
|
||||||
allOrderNumbers.push(...dbOrderNumbers)
|
|
||||||
}
|
|
||||||
|
|
||||||
orderNumbers.push(...allOrderNumbers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deduplicate
|
|
||||||
return [...new Set(orderNumbers)]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register IPC handlers for validation operations
|
|
||||||
*/
|
|
||||||
export function registerValidationHandlers(): void {
|
export function registerValidationHandlers(): void {
|
||||||
// ==================== VALIDATION ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run material validation from database
|
|
||||||
*/
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
IPC_CHANNELS.VALIDATION_VALIDATE,
|
IPC_CHANNELS.VALIDATION_VALIDATE,
|
||||||
async (event, request: ValidationRequest): Promise<ValidationResponse> => {
|
async (event, request: ValidationRequest): Promise<ValidationResponse> => {
|
||||||
let dbService: MySqlService | SqlServerService | null = null
|
const sessionManager = (
|
||||||
|
await import('../services/user/session-manager')
|
||||||
|
).SessionManager.getInstance()
|
||||||
|
|
||||||
try {
|
const userInfo = sessionManager.getUserInfo()
|
||||||
// Get current user info
|
if (!userInfo) {
|
||||||
const sessionManager = (
|
|
||||||
await import('../services/user/session-manager')
|
|
||||||
).SessionManager.getInstance()
|
|
||||||
|
|
||||||
const userInfo = sessionManager.getUserInfo()
|
|
||||||
if (!userInfo) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: '用户未登录',
|
|
||||||
stats: {
|
|
||||||
totalRecords: 0,
|
|
||||||
matchedCount: 0,
|
|
||||||
markedCount: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isAdmin = userInfo.userType === 'Admin'
|
|
||||||
const username = userInfo.username
|
|
||||||
|
|
||||||
log.info('Starting validation', { mode: request.mode, user: username, isAdmin })
|
|
||||||
|
|
||||||
// Connect to database
|
|
||||||
dbService = await getValidationDatabaseService()
|
|
||||||
const configManager = ConfigManager.getInstance()
|
|
||||||
const dbType = configManager.getDatabaseType()
|
|
||||||
const isSqlServer = dbType === 'sqlserver'
|
|
||||||
|
|
||||||
let sourceNumbers: string[] | null = null
|
|
||||||
|
|
||||||
// Get source numbers based on mode
|
|
||||||
if (request.mode === 'database_filtered') {
|
|
||||||
if (request.useSharedProductionIds) {
|
|
||||||
// Use shared Production IDs from extractor page
|
|
||||||
const sharedIds = getSharedProductionIds(event.sender.id)
|
|
||||||
log.info(`Using ${sharedIds.length} shared Production IDs`)
|
|
||||||
|
|
||||||
if (sharedIds.length === 0) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: '没有可用的共享 Production ID。请在数据提取页面输入 Production ID。',
|
|
||||||
stats: {
|
|
||||||
totalRecords: 0,
|
|
||||||
matchedCount: 0,
|
|
||||||
markedCount: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceNumbers = await getSourceNumbersFromInputs(sharedIds, dbService)
|
|
||||||
log.info(`Got ${sourceNumbers.length} source numbers from shared Production IDs`)
|
|
||||||
|
|
||||||
// Check if we got any order numbers from the shared Production IDs
|
|
||||||
if (sourceNumbers.length === 0) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error:
|
|
||||||
'共享的 Production ID 没有找到对应的订单数据。请确保在数据提取页面输入了有效的 Production ID 并成功获取了订单数据。',
|
|
||||||
stats: {
|
|
||||||
totalRecords: 0,
|
|
||||||
matchedCount: 0,
|
|
||||||
markedCount: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (request.productionIdFile) {
|
|
||||||
// Read from file
|
|
||||||
const inputs = readProductionIds(request.productionIdFile)
|
|
||||||
log.info(`Read ${inputs.length} inputs from file`)
|
|
||||||
sourceNumbers = await getSourceNumbersFromInputs(inputs, dbService)
|
|
||||||
log.info(`Got ${sourceNumbers.length} source numbers`)
|
|
||||||
|
|
||||||
// Check if we got any order numbers from the file
|
|
||||||
if (sourceNumbers.length === 0) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error:
|
|
||||||
'文件中的 Production ID 没有找到对应的订单数据。请检查 Production ID 是否正确,或确保数据库中有对应的订单数据。',
|
|
||||||
stats: {
|
|
||||||
totalRecords: 0,
|
|
||||||
matchedCount: 0,
|
|
||||||
markedCount: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get material records from DiscreteMaterialPlanData
|
|
||||||
const materialDao = new DiscreteMaterialPlanDAO()
|
|
||||||
|
|
||||||
let materialRecords: any[] = []
|
|
||||||
|
|
||||||
if (request.mode === 'database_full') {
|
|
||||||
// Full table query with deduplication by MaterialCode
|
|
||||||
materialRecords = await materialDao.queryAllDistinctByMaterialCode()
|
|
||||||
} else if (sourceNumbers && sourceNumbers.length > 0) {
|
|
||||||
// Filtered query by source numbers
|
|
||||||
materialRecords = await materialDao.queryBySourceNumbersDistinct(sourceNumbers)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (materialRecords.length === 0) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: '未找到物料记录。请检查数据库中是否有对应订单的物料数据。',
|
|
||||||
stats: {
|
|
||||||
totalRecords: 0,
|
|
||||||
matchedCount: 0,
|
|
||||||
markedCount: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get type keywords from MaterialsTypeToBeDeleted
|
|
||||||
const typeKeywordTableName = getTableName('dbo_MaterialsTypeToBeDeleted')
|
|
||||||
const typeKeywordSql = `
|
|
||||||
SELECT MaterialName, ManagerName
|
|
||||||
FROM ${typeKeywordTableName}
|
|
||||||
WHERE MaterialName IS NOT NULL
|
|
||||||
`
|
|
||||||
const typeKeywordResult = isSqlServer
|
|
||||||
? await (dbService as SqlServerService).query(typeKeywordSql)
|
|
||||||
: await (dbService as MySqlService).query(typeKeywordSql)
|
|
||||||
|
|
||||||
const typeKeywords = typeKeywordResult.rows.map((row) => ({
|
|
||||||
materialName: row.MaterialName as string,
|
|
||||||
managerName: row.ManagerName as string
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Get marked material codes from MaterialsToBeDeleted
|
|
||||||
const markedTableName = getTableName('dbo_MaterialsToBeDeleted')
|
|
||||||
const markedSql = `
|
|
||||||
SELECT MaterialCode, ManagerName
|
|
||||||
FROM ${markedTableName}
|
|
||||||
WHERE MaterialCode IS NOT NULL AND ManagerName IS NOT NULL
|
|
||||||
`
|
|
||||||
const markedResult = isSqlServer
|
|
||||||
? await (dbService as SqlServerService).query(markedSql)
|
|
||||||
: await (dbService as MySqlService).query(markedSql)
|
|
||||||
|
|
||||||
const markedCodesDict = new Map<string, string>()
|
|
||||||
for (const row of markedResult.rows) {
|
|
||||||
markedCodesDict.set(row.MaterialCode as string, row.ManagerName as string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match materials
|
|
||||||
const results: ValidationResult[] = []
|
|
||||||
for (const record of materialRecords) {
|
|
||||||
const materialName = (record.MaterialName as string) || ''
|
|
||||||
const materialCode = (record.MaterialCode as string) || ''
|
|
||||||
const specification = (record.Specification as string) || ''
|
|
||||||
const model = (record.Model as string) || ''
|
|
||||||
|
|
||||||
// Priority 1: Check MaterialsToBeDeleted (MaterialCode exact match)
|
|
||||||
let managerName = markedCodesDict.get(materialCode) || null
|
|
||||||
const isMarkedForDeletion = managerName !== null
|
|
||||||
let matchedTypeKeyword: string | undefined = undefined
|
|
||||||
|
|
||||||
// Priority 2: Match with MaterialsTypeToBeDeleted (MaterialName contains)
|
|
||||||
if (!managerName) {
|
|
||||||
for (const typeKeyword of typeKeywords) {
|
|
||||||
if (typeKeyword.materialName && materialName.includes(typeKeyword.materialName)) {
|
|
||||||
matchedTypeKeyword = typeKeyword.materialName
|
|
||||||
managerName = typeKeyword.managerName
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Priority 3: User Override Match (only for non-admin users)
|
|
||||||
// Override with current user's typeKeyword if available
|
|
||||||
if (!isAdmin && username) {
|
|
||||||
const userKeywords = typeKeywords.filter((tk) => tk.managerName === username)
|
|
||||||
for (const userKeyword of userKeywords) {
|
|
||||||
if (userKeyword.materialName && materialName.includes(userKeyword.materialName)) {
|
|
||||||
matchedTypeKeyword = userKeyword.materialName
|
|
||||||
managerName = userKeyword.managerName
|
|
||||||
break // Force override with first match
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push({
|
|
||||||
materialName,
|
|
||||||
materialCode,
|
|
||||||
specification,
|
|
||||||
model,
|
|
||||||
managerName: managerName || '',
|
|
||||||
isMarkedForDeletion,
|
|
||||||
matchedTypeKeyword
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const markedCount = results.filter((r) => r.isMarkedForDeletion).length
|
|
||||||
const matchedCount = results.filter((r) => r.managerName).length
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
results,
|
|
||||||
stats: {
|
|
||||||
totalRecords: results.length,
|
|
||||||
matchedCount,
|
|
||||||
markedCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
||||||
log.error('Validation error', {
|
|
||||||
error: error instanceof Error ? error.message : String(error)
|
|
||||||
})
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `Validation failed: ${message}`
|
error: '用户未登录',
|
||||||
}
|
stats: {
|
||||||
} finally {
|
totalRecords: 0,
|
||||||
if (dbService) {
|
matchedCount: 0,
|
||||||
try {
|
markedCount: 0
|
||||||
await dbService.disconnect()
|
|
||||||
} catch (closeError) {
|
|
||||||
log.warn('Error disconnecting database', {
|
|
||||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return validationApplicationService.validate(request, userInfo, event.sender.id)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ==================== MATERIAL OPERATIONS ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upsert batch materials to MaterialsToBeDeleted
|
|
||||||
*/
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
IPC_CHANNELS.MATERIALS_UPSERT_BATCH,
|
IPC_CHANNELS.MATERIALS_UPSERT_BATCH,
|
||||||
async (_event, request: MaterialUpsertBatchRequest): Promise<MaterialOperationResponse> => {
|
async (_event, request: MaterialUpsertBatchRequest): Promise<MaterialOperationResponse> => {
|
||||||
@@ -486,9 +57,7 @@ export function registerValidationHandlers(): void {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||||
log.error('Upsert batch error', {
|
log.error('Upsert batch error', { error: message })
|
||||||
error: error instanceof Error ? error.message : String(error)
|
|
||||||
})
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `Upsert failed: ${message}`
|
error: `Upsert failed: ${message}`
|
||||||
@@ -497,9 +66,6 @@ export function registerValidationHandlers(): void {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete materials by material codes
|
|
||||||
*/
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
IPC_CHANNELS.MATERIALS_DELETE,
|
IPC_CHANNELS.MATERIALS_DELETE,
|
||||||
async (_event, request: MaterialDeleteRequest): Promise<MaterialOperationResponse> => {
|
async (_event, request: MaterialDeleteRequest): Promise<MaterialOperationResponse> => {
|
||||||
@@ -513,7 +79,7 @@ export function registerValidationHandlers(): void {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||||
log.error('Delete error', { error: error instanceof Error ? error.message : String(error) })
|
log.error('Delete error', { error: message })
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `Delete failed: ${message}`
|
error: `Delete failed: ${message}`
|
||||||
@@ -522,28 +88,19 @@ export function registerValidationHandlers(): void {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
ipcMain.handle(IPC_CHANNELS.MATERIALS_GET_MANAGERS, async (): Promise<{ managers: string[] }> => {
|
||||||
* Get unique manager names
|
try {
|
||||||
*/
|
const dao = new MaterialsToBeDeletedDAO()
|
||||||
ipcMain.handle(
|
const managers = await dao.getManagers()
|
||||||
IPC_CHANNELS.MATERIALS_GET_MANAGERS,
|
return { managers }
|
||||||
async (_event): Promise<{ managers: string[] }> => {
|
} catch (error) {
|
||||||
try {
|
log.error('Get managers error', {
|
||||||
const dao = new MaterialsToBeDeletedDAO()
|
error: error instanceof Error ? error.message : String(error)
|
||||||
const managers = await dao.getManagers()
|
})
|
||||||
return { managers }
|
return { managers: [] }
|
||||||
} catch (error) {
|
|
||||||
log.error('Get managers error', {
|
|
||||||
error: error instanceof Error ? error.message : String(error)
|
|
||||||
})
|
|
||||||
return { managers: [] }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* Update manager for a single material
|
|
||||||
*/
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
IPC_CHANNELS.MATERIALS_UPDATE_MANAGER,
|
IPC_CHANNELS.MATERIALS_UPDATE_MANAGER,
|
||||||
async (
|
async (
|
||||||
@@ -554,176 +111,47 @@ export function registerValidationHandlers(): void {
|
|||||||
const dao = new MaterialsToBeDeletedDAO()
|
const dao = new MaterialsToBeDeletedDAO()
|
||||||
return await dao.updateManager(request.materialCode, request.managerName)
|
return await dao.updateManager(request.materialCode, request.managerName)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Update manager error', {
|
const message = error instanceof Error ? error.message : String(error)
|
||||||
error: error instanceof Error ? error.message : String(error)
|
log.error('Update manager error', { error: message })
|
||||||
})
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof Error ? error.message : String(error)
|
error: message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Get materials by manager
|
|
||||||
*/
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
IPC_CHANNELS.MATERIALS_GET_BY_MANAGER,
|
IPC_CHANNELS.MATERIALS_GET_BY_MANAGER,
|
||||||
async (_event, managerName: string): Promise<{ materials: MaterialRecordSummary[] }> => {
|
async (_event, managerName: string): Promise<{ materials: MaterialRecordSummary[] }> => {
|
||||||
let dbService: MySqlService | SqlServerService | null = null
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
dbService = await getValidationDatabaseService()
|
const materials = await validationApplicationService.getMaterialsByManager(managerName)
|
||||||
const configManager = ConfigManager.getInstance()
|
return { materials }
|
||||||
const dbType = configManager.getDatabaseType()
|
|
||||||
const isSqlServer = dbType === 'sqlserver'
|
|
||||||
|
|
||||||
const dao = new MaterialsToBeDeletedDAO()
|
|
||||||
const materials = await dao.getMaterialsByManager(managerName)
|
|
||||||
|
|
||||||
// Get material codes set for quick lookup
|
|
||||||
const markedCodes = await dao.getAllMaterialCodes()
|
|
||||||
|
|
||||||
// Enrich with material details from DiscreteMaterialPlanData
|
|
||||||
const enrichedMaterials: MaterialRecordSummary[] = []
|
|
||||||
const detailTableName = getTableName('dbo_DiscreteMaterialPlanData')
|
|
||||||
|
|
||||||
for (const mat of materials) {
|
|
||||||
let detailResult: any
|
|
||||||
|
|
||||||
if (isSqlServer) {
|
|
||||||
const sql = require('mssql')
|
|
||||||
const detailSql = `
|
|
||||||
SELECT TOP 1 MaterialName, Specification, Model
|
|
||||||
FROM ${detailTableName}
|
|
||||||
WHERE MaterialCode = @materialCode
|
|
||||||
`
|
|
||||||
detailResult = await (dbService as SqlServerService).queryWithParams(detailSql, {
|
|
||||||
materialCode: { value: mat.materialCode, type: sql.NVarChar }
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
const detailSql = `
|
|
||||||
SELECT MaterialName, Specification, Model
|
|
||||||
FROM ${detailTableName}
|
|
||||||
WHERE MaterialCode = ?
|
|
||||||
LIMIT 1
|
|
||||||
`
|
|
||||||
detailResult = await (dbService as MySqlService).query(detailSql, [mat.materialCode])
|
|
||||||
}
|
|
||||||
|
|
||||||
enrichedMaterials.push({
|
|
||||||
materialCode: mat.materialCode,
|
|
||||||
materialName:
|
|
||||||
detailResult.rows.length > 0 ? (detailResult.rows[0].MaterialName as string) : '',
|
|
||||||
specification:
|
|
||||||
detailResult.rows.length > 0 ? (detailResult.rows[0].Specification as string) : '',
|
|
||||||
model: detailResult.rows.length > 0 ? (detailResult.rows[0].Model as string) : '',
|
|
||||||
managerName: mat.managerName,
|
|
||||||
isMarked: markedCodes.has(mat.materialCode)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return { materials: enrichedMaterials }
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Get by manager error', {
|
log.error('Get by manager error', {
|
||||||
error: error instanceof Error ? error.message : String(error)
|
error: error instanceof Error ? error.message : String(error)
|
||||||
})
|
})
|
||||||
return { materials: [] }
|
return { materials: [] }
|
||||||
} finally {
|
|
||||||
if (dbService) {
|
|
||||||
try {
|
|
||||||
await dbService.disconnect()
|
|
||||||
} catch (closeError) {
|
|
||||||
log.warn('Error disconnecting database', {
|
|
||||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all material records
|
|
||||||
*/
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
IPC_CHANNELS.MATERIALS_GET_ALL,
|
IPC_CHANNELS.MATERIALS_GET_ALL,
|
||||||
async (_event): Promise<{ materials: MaterialRecordSummary[] }> => {
|
async (): Promise<{ materials: MaterialRecordSummary[] }> => {
|
||||||
let dbService: MySqlService | SqlServerService | null = null
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
dbService = await getValidationDatabaseService()
|
const materials = await validationApplicationService.getAllMaterials()
|
||||||
const configManager = ConfigManager.getInstance()
|
return { materials }
|
||||||
const dbType = configManager.getDatabaseType()
|
|
||||||
const isSqlServer = dbType === 'sqlserver'
|
|
||||||
|
|
||||||
const dao = new MaterialsToBeDeletedDAO()
|
|
||||||
const materials = await dao.getAllRecords()
|
|
||||||
const markedCodes = await dao.getAllMaterialCodes()
|
|
||||||
|
|
||||||
const enrichedMaterials: MaterialRecordSummary[] = []
|
|
||||||
const detailTableName = getTableName('dbo_DiscreteMaterialPlanData')
|
|
||||||
|
|
||||||
for (const mat of materials) {
|
|
||||||
let detailResult: any
|
|
||||||
|
|
||||||
if (isSqlServer) {
|
|
||||||
const sql = require('mssql')
|
|
||||||
const detailSql = `
|
|
||||||
SELECT TOP 1 MaterialName, Specification, Model
|
|
||||||
FROM ${detailTableName}
|
|
||||||
WHERE MaterialCode = @materialCode
|
|
||||||
`
|
|
||||||
detailResult = await (dbService as SqlServerService).queryWithParams(detailSql, {
|
|
||||||
materialCode: { value: mat.materialCode, type: sql.NVarChar }
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
const detailSql = `
|
|
||||||
SELECT MaterialName, Specification, Model
|
|
||||||
FROM ${detailTableName}
|
|
||||||
WHERE MaterialCode = ?
|
|
||||||
LIMIT 1
|
|
||||||
`
|
|
||||||
detailResult = await (dbService as MySqlService).query(detailSql, [mat.materialCode])
|
|
||||||
}
|
|
||||||
|
|
||||||
enrichedMaterials.push({
|
|
||||||
materialCode: mat.materialCode,
|
|
||||||
materialName:
|
|
||||||
detailResult.rows.length > 0 ? (detailResult.rows[0].MaterialName as string) : '',
|
|
||||||
specification:
|
|
||||||
detailResult.rows.length > 0 ? (detailResult.rows[0].Specification as string) : '',
|
|
||||||
model: detailResult.rows.length > 0 ? (detailResult.rows[0].Model as string) : '',
|
|
||||||
managerName: mat.managerName,
|
|
||||||
isMarked: markedCodes.has(mat.materialCode)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return { materials: enrichedMaterials }
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Get all error', {
|
log.error('Get all error', {
|
||||||
error: error instanceof Error ? error.message : String(error)
|
error: error instanceof Error ? error.message : String(error)
|
||||||
})
|
})
|
||||||
return { materials: [] }
|
return { materials: [] }
|
||||||
} finally {
|
|
||||||
if (dbService) {
|
|
||||||
try {
|
|
||||||
await dbService.disconnect()
|
|
||||||
} catch (closeError) {
|
|
||||||
log.warn('Error disconnecting database', {
|
|
||||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
ipcMain.handle(IPC_CHANNELS.MATERIALS_GET_STATISTICS, async (): Promise<{ stats: any }> => {
|
||||||
* Get statistics
|
|
||||||
*/
|
|
||||||
ipcMain.handle(IPC_CHANNELS.MATERIALS_GET_STATISTICS, async (_event): Promise<{ stats: any }> => {
|
|
||||||
try {
|
try {
|
||||||
const dao = new MaterialsToBeDeletedDAO()
|
const dao = new MaterialsToBeDeletedDAO()
|
||||||
const stats = await dao.getStatistics()
|
const stats = await dao.getStatistics()
|
||||||
@@ -736,155 +164,52 @@ export function registerValidationHandlers(): void {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* Set shared Production IDs from extractor page
|
|
||||||
*/
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
IPC_CHANNELS.VALIDATION_SET_SHARED_PRODUCTION_IDS,
|
IPC_CHANNELS.VALIDATION_SET_SHARED_PRODUCTION_IDS,
|
||||||
async (event, productionIds: string[]): Promise<void> => {
|
async (event, productionIds: string[]): Promise<void> => {
|
||||||
log.info(`Received ${productionIds.length} shared Production IDs`)
|
log.info(`Received ${productionIds.length} shared Production IDs`)
|
||||||
setSharedProductionIds(event.sender.id, productionIds)
|
sharedProductionIdsStore.set(event.sender.id, productionIds)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Get shared Production IDs
|
|
||||||
*/
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
IPC_CHANNELS.VALIDATION_GET_SHARED_PRODUCTION_IDS,
|
IPC_CHANNELS.VALIDATION_GET_SHARED_PRODUCTION_IDS,
|
||||||
async (event): Promise<{ productionIds: string[] }> => {
|
async (event): Promise<{ productionIds: string[] }> => {
|
||||||
return { productionIds: getSharedProductionIds(event.sender.id) }
|
return { productionIds: sharedProductionIdsStore.get(event.sender.id) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear shared Production IDs
|
|
||||||
*/
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
IPC_CHANNELS.VALIDATION_CLEAR_SHARED_PRODUCTION_IDS,
|
IPC_CHANNELS.VALIDATION_CLEAR_SHARED_PRODUCTION_IDS,
|
||||||
async (event): Promise<void> => {
|
async (event): Promise<void> => {
|
||||||
log.info('Clearing shared Production IDs')
|
log.info('Clearing shared Production IDs')
|
||||||
clearSharedProductionIds(event.sender.id)
|
sharedProductionIdsStore.clear(event.sender.id)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Get cleaner data (order numbers from shared Production IDs + material codes from MaterialsToBeDeleted)
|
|
||||||
* Filters materials by current user (admin sees all, regular users see only their own)
|
|
||||||
*/
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
IPC_CHANNELS.VALIDATION_GET_CLEANER_DATA,
|
IPC_CHANNELS.VALIDATION_GET_CLEANER_DATA,
|
||||||
async (
|
async (
|
||||||
_event
|
event
|
||||||
): Promise<{
|
): Promise<{
|
||||||
success: boolean
|
success: boolean
|
||||||
orderNumbers?: string[]
|
orderNumbers?: string[]
|
||||||
materialCodes?: string[]
|
materialCodes?: string[]
|
||||||
error?: string
|
error?: string
|
||||||
}> => {
|
}> => {
|
||||||
let dbService: MySqlService | SqlServerService | null = null
|
|
||||||
const sessionManager = (
|
const sessionManager = (
|
||||||
await import('../services/user/session-manager')
|
await import('../services/user/session-manager')
|
||||||
).SessionManager.getInstance()
|
).SessionManager.getInstance()
|
||||||
|
const userInfo = sessionManager.getUserInfo()
|
||||||
|
|
||||||
try {
|
if (!userInfo) {
|
||||||
// Get current user
|
|
||||||
const userInfo = sessionManager.getUserInfo()
|
|
||||||
if (!userInfo) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: '用户未登录'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isAdmin = userInfo.userType === 'Admin'
|
|
||||||
const username = userInfo.username
|
|
||||||
const configManager = ConfigManager.getInstance()
|
|
||||||
const dbType = configManager.getDatabaseType()
|
|
||||||
const isSqlServer = dbType === 'sqlserver'
|
|
||||||
|
|
||||||
log.info(`User: ${username}, isAdmin: ${isAdmin}`)
|
|
||||||
|
|
||||||
// Connect to database
|
|
||||||
dbService = await getValidationDatabaseService()
|
|
||||||
|
|
||||||
// 1. Get order numbers from shared Production IDs
|
|
||||||
const sharedIds = getSharedProductionIds(_event.sender.id)
|
|
||||||
let orderNumbers: string[] = []
|
|
||||||
|
|
||||||
if (sharedIds.length > 0) {
|
|
||||||
log.info(`Using ${sharedIds.length} shared Production IDs`)
|
|
||||||
orderNumbers = await getSourceNumbersFromInputs(sharedIds, dbService)
|
|
||||||
log.info(`Got ${orderNumbers.length} order numbers`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Get material codes from MaterialsToBeDeleted table
|
|
||||||
let materialCodes: string[] = []
|
|
||||||
const markedTableName = getTableName('dbo_MaterialsToBeDeleted')
|
|
||||||
|
|
||||||
if (isAdmin) {
|
|
||||||
// Admin sees all materials
|
|
||||||
const allCodesSql = `
|
|
||||||
SELECT MaterialCode
|
|
||||||
FROM ${markedTableName}
|
|
||||||
WHERE MaterialCode IS NOT NULL
|
|
||||||
`
|
|
||||||
const result = isSqlServer
|
|
||||||
? await (dbService as SqlServerService).query(allCodesSql)
|
|
||||||
: await (dbService as MySqlService).query(allCodesSql)
|
|
||||||
|
|
||||||
materialCodes = result.rows.map((row) => row.MaterialCode as string).filter(Boolean)
|
|
||||||
log.info(`Admin user: got ${materialCodes.length} materials`)
|
|
||||||
} else {
|
|
||||||
// Regular users only see their own materials
|
|
||||||
if (isSqlServer) {
|
|
||||||
const sql = require('mssql')
|
|
||||||
const userMaterialsSql = `
|
|
||||||
SELECT MaterialCode
|
|
||||||
FROM ${markedTableName}
|
|
||||||
WHERE ManagerName = @username AND MaterialCode IS NOT NULL
|
|
||||||
`
|
|
||||||
const result = await (dbService as SqlServerService).queryWithParams(userMaterialsSql, {
|
|
||||||
username: { value: username, type: sql.NVarChar }
|
|
||||||
})
|
|
||||||
materialCodes = result.rows.map((row) => row.MaterialCode as string).filter(Boolean)
|
|
||||||
} else {
|
|
||||||
const userMaterialsSql = `
|
|
||||||
SELECT MaterialCode
|
|
||||||
FROM ${markedTableName}
|
|
||||||
WHERE ManagerName = ? AND MaterialCode IS NOT NULL
|
|
||||||
`
|
|
||||||
const result = await (dbService as MySqlService).query(userMaterialsSql, [username])
|
|
||||||
materialCodes = result.rows.map((row) => row.MaterialCode as string).filter(Boolean)
|
|
||||||
}
|
|
||||||
log.info(`Regular user: got ${materialCodes.length} materials`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
orderNumbers,
|
|
||||||
materialCodes
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
||||||
log.error('CleanerData error', {
|
|
||||||
error: error instanceof Error ? error.message : String(error)
|
|
||||||
})
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `获取清理数据失败:${message}`
|
error: '用户未登录'
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (dbService) {
|
|
||||||
try {
|
|
||||||
await dbService.disconnect()
|
|
||||||
} catch (closeError) {
|
|
||||||
log.warn('Error disconnecting database', {
|
|
||||||
error: closeError instanceof Error ? closeError.message : String(closeError)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return validationApplicationService.getCleanerData(userInfo, event.sender.id)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
95
src/main/services/validation/production-input-service.ts
Normal file
95
src/main/services/validation/production-input-service.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import { ConfigManager } from '../config/config-manager'
|
||||||
|
import { SqlServerService } from '../database/sql-server'
|
||||||
|
import type { ValidationDatabaseService } from './validation-database'
|
||||||
|
import { getValidationTableName } from './validation-database'
|
||||||
|
|
||||||
|
export function readProductionIds(filePath: string): string[] {
|
||||||
|
const content = fs.readFileSync(filePath, 'utf-8')
|
||||||
|
return content
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter((line) => line.length > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function identifyInputType(input: string): 'production_id' | 'order_number' | 'unknown' {
|
||||||
|
if (/^SC\d{14}$/.test(input)) {
|
||||||
|
return 'order_number'
|
||||||
|
}
|
||||||
|
if (/^\d{2}[A-Za-z]\d{1,6}$/.test(input)) {
|
||||||
|
return 'production_id'
|
||||||
|
}
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSourceNumbersFromInputs(
|
||||||
|
inputs: string[],
|
||||||
|
dbService: ValidationDatabaseService
|
||||||
|
): Promise<string[]> {
|
||||||
|
const productionIds: string[] = []
|
||||||
|
const orderNumbers: string[] = []
|
||||||
|
const configManager = ConfigManager.getInstance()
|
||||||
|
const isSqlServer = configManager.getDatabaseType() === 'sqlserver'
|
||||||
|
|
||||||
|
for (const item of inputs) {
|
||||||
|
const type = identifyInputType(item)
|
||||||
|
if (type === 'order_number') {
|
||||||
|
orderNumbers.push(item)
|
||||||
|
} else if (type === 'production_id') {
|
||||||
|
productionIds.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (productionIds.length > 0) {
|
||||||
|
const contractTableName = getValidationTableName('productionContractData_26年压力表合同数据')
|
||||||
|
const batchSize = 2000
|
||||||
|
|
||||||
|
if (isSqlServer) {
|
||||||
|
const sql = await import('mssql')
|
||||||
|
const allOrderNumbers: string[] = []
|
||||||
|
|
||||||
|
for (let i = 0; i < productionIds.length; i += batchSize) {
|
||||||
|
const batch = productionIds.slice(i, i + batchSize)
|
||||||
|
const placeholders = batch.map((_, idx) => `@p${idx}`).join(',')
|
||||||
|
const params: Record<string, { value: string; type: typeof sql.default.NVarChar }> = {}
|
||||||
|
|
||||||
|
batch.forEach((id, idx) => {
|
||||||
|
params[`p${idx}`] = { value: id, type: sql.default.NVarChar }
|
||||||
|
})
|
||||||
|
|
||||||
|
const contractSql = `
|
||||||
|
SELECT DISTINCT 生产订单号
|
||||||
|
FROM ${contractTableName}
|
||||||
|
WHERE 总排号 IN (${placeholders})
|
||||||
|
`
|
||||||
|
const contractResult = await (dbService as SqlServerService).queryWithParams(
|
||||||
|
contractSql,
|
||||||
|
params
|
||||||
|
)
|
||||||
|
allOrderNumbers.push(
|
||||||
|
...contractResult.rows.map((row: Record<string, unknown>) => row.生产订单号 as string)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
orderNumbers.push(...allOrderNumbers)
|
||||||
|
} else {
|
||||||
|
const allOrderNumbers: string[] = []
|
||||||
|
|
||||||
|
for (let i = 0; i < productionIds.length; i += batchSize) {
|
||||||
|
const batch = productionIds.slice(i, i + batchSize)
|
||||||
|
const placeholders = batch.map(() => '?').join(',')
|
||||||
|
const contractSql = `
|
||||||
|
SELECT DISTINCT 生产订单号
|
||||||
|
FROM ${contractTableName}
|
||||||
|
WHERE 总排号 IN (${placeholders})
|
||||||
|
`
|
||||||
|
const contractResult = await dbService.query(contractSql, batch)
|
||||||
|
allOrderNumbers.push(...contractResult.rows.map((row) => row.生产订单号 as string))
|
||||||
|
}
|
||||||
|
|
||||||
|
orderNumbers.push(...allOrderNumbers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...new Set(orderNumbers)]
|
||||||
|
}
|
||||||
18
src/main/services/validation/shared-production-ids-store.ts
Normal file
18
src/main/services/validation/shared-production-ids-store.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export class SharedProductionIdsStore {
|
||||||
|
private sharedProductionIdsBySender = new Map<number, Set<string>>()
|
||||||
|
|
||||||
|
set(senderId: number, ids: string[]): void {
|
||||||
|
this.sharedProductionIdsBySender.set(senderId, new Set(ids))
|
||||||
|
}
|
||||||
|
|
||||||
|
get(senderId: number): string[] {
|
||||||
|
const senderSet = this.sharedProductionIdsBySender.get(senderId)
|
||||||
|
return senderSet ? [...senderSet] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(senderId: number): void {
|
||||||
|
this.sharedProductionIdsBySender.delete(senderId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sharedProductionIdsStore = new SharedProductionIdsStore()
|
||||||
413
src/main/services/validation/validation-application-service.ts
Normal file
413
src/main/services/validation/validation-application-service.ts
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
import { DiscreteMaterialPlanDAO } from '../database/discrete-material-plan-dao'
|
||||||
|
import { MaterialsToBeDeletedDAO } from '../database/materials-to-be-deleted-dao'
|
||||||
|
import { SqlServerService } from '../database/sql-server'
|
||||||
|
import { createLogger } from '../logger'
|
||||||
|
import type {
|
||||||
|
MaterialRecordSummary,
|
||||||
|
ValidationRequest,
|
||||||
|
ValidationResponse,
|
||||||
|
ValidationResult
|
||||||
|
} from '../../types/validation.types'
|
||||||
|
import type { UserInfo } from '../../types/user.types'
|
||||||
|
import { getSourceNumbersFromInputs, readProductionIds } from './production-input-service'
|
||||||
|
import { sharedProductionIdsStore } from './shared-production-ids-store'
|
||||||
|
import {
|
||||||
|
createValidationDatabaseService,
|
||||||
|
getValidationTableName,
|
||||||
|
type ValidationDatabaseService
|
||||||
|
} from './validation-database'
|
||||||
|
|
||||||
|
const log = createLogger('ValidationApplicationService')
|
||||||
|
|
||||||
|
type TypeKeyword = {
|
||||||
|
materialName: string
|
||||||
|
managerName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ValidationApplicationService {
|
||||||
|
async validate(
|
||||||
|
request: ValidationRequest,
|
||||||
|
userInfo: UserInfo,
|
||||||
|
senderId: number
|
||||||
|
): Promise<ValidationResponse> {
|
||||||
|
let dbService: ValidationDatabaseService | null = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const isAdmin = userInfo.userType === 'Admin'
|
||||||
|
const username = userInfo.username
|
||||||
|
|
||||||
|
log.info('Starting validation', { mode: request.mode, user: username, isAdmin })
|
||||||
|
|
||||||
|
dbService = await createValidationDatabaseService()
|
||||||
|
|
||||||
|
let sourceNumbers: string[] | null = null
|
||||||
|
|
||||||
|
if (request.mode === 'database_filtered') {
|
||||||
|
if (request.useSharedProductionIds) {
|
||||||
|
const sharedIds = sharedProductionIdsStore.get(senderId)
|
||||||
|
log.info(`Using ${sharedIds.length} shared Production IDs`)
|
||||||
|
|
||||||
|
if (sharedIds.length === 0) {
|
||||||
|
return this.emptyFailure(
|
||||||
|
'没有可用的共享 Production ID。请在数据提取页面输入 Production ID。'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceNumbers = await getSourceNumbersFromInputs(sharedIds, dbService)
|
||||||
|
log.info(`Got ${sourceNumbers.length} source numbers from shared Production IDs`)
|
||||||
|
|
||||||
|
if (sourceNumbers.length === 0) {
|
||||||
|
return this.emptyFailure(
|
||||||
|
'共享的 Production ID 没有找到对应的订单数据。请确保在数据提取页面输入了有效的 Production ID 并成功获取了订单数据。'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (request.productionIdFile) {
|
||||||
|
const inputs = readProductionIds(request.productionIdFile)
|
||||||
|
log.info(`Read ${inputs.length} inputs from file`)
|
||||||
|
|
||||||
|
sourceNumbers = await getSourceNumbersFromInputs(inputs, dbService)
|
||||||
|
log.info(`Got ${sourceNumbers.length} source numbers`)
|
||||||
|
|
||||||
|
if (sourceNumbers.length === 0) {
|
||||||
|
return this.emptyFailure(
|
||||||
|
'文件中的 Production ID 没有找到对应的订单数据。请检查 Production ID 是否正确,或确保数据库中有对应的订单数据。'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const materialDao = new DiscreteMaterialPlanDAO()
|
||||||
|
let materialRecords: any[] = []
|
||||||
|
|
||||||
|
if (request.mode === 'database_full') {
|
||||||
|
materialRecords = await materialDao.queryAllDistinctByMaterialCode()
|
||||||
|
} else if (sourceNumbers && sourceNumbers.length > 0) {
|
||||||
|
materialRecords = await materialDao.queryBySourceNumbersDistinct(sourceNumbers)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (materialRecords.length === 0) {
|
||||||
|
return this.emptyFailure('未找到物料记录。请检查数据库中是否有对应订单的物料数据。')
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeKeywords = await this.loadTypeKeywords(dbService)
|
||||||
|
const markedCodes = await this.loadMarkedCodes(dbService)
|
||||||
|
const results = this.buildValidationResults(materialRecords, typeKeywords, markedCodes, {
|
||||||
|
isAdmin,
|
||||||
|
username
|
||||||
|
})
|
||||||
|
|
||||||
|
const markedCount = results.filter((result) => result.isMarkedForDeletion).length
|
||||||
|
const matchedCount = results.filter((result) => result.managerName).length
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
results,
|
||||||
|
stats: {
|
||||||
|
totalRecords: results.length,
|
||||||
|
matchedCount,
|
||||||
|
markedCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
log.error('Validation error', { error: message })
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `Validation failed: ${message}`
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (dbService) {
|
||||||
|
await this.disconnectQuietly(dbService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMaterialsByManager(managerName: string): Promise<MaterialRecordSummary[]> {
|
||||||
|
const dao = new MaterialsToBeDeletedDAO()
|
||||||
|
const materials = await dao.getMaterialsByManager(managerName)
|
||||||
|
const markedCodes = await dao.getAllMaterialCodes()
|
||||||
|
return this.enrichMaterials(materials, markedCodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllMaterials(): Promise<MaterialRecordSummary[]> {
|
||||||
|
const dao = new MaterialsToBeDeletedDAO()
|
||||||
|
const materials = await dao.getAllRecords()
|
||||||
|
const markedCodes = await dao.getAllMaterialCodes()
|
||||||
|
return this.enrichMaterials(materials, markedCodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCleanerData(
|
||||||
|
userInfo: UserInfo,
|
||||||
|
senderId: number
|
||||||
|
): Promise<{
|
||||||
|
success: boolean
|
||||||
|
orderNumbers?: string[]
|
||||||
|
materialCodes?: string[]
|
||||||
|
error?: string
|
||||||
|
}> {
|
||||||
|
let dbService: ValidationDatabaseService | null = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const isAdmin = userInfo.userType === 'Admin'
|
||||||
|
const username = userInfo.username
|
||||||
|
log.info(`User: ${username}, isAdmin: ${isAdmin}`)
|
||||||
|
|
||||||
|
dbService = await createValidationDatabaseService()
|
||||||
|
|
||||||
|
const sharedIds = sharedProductionIdsStore.get(senderId)
|
||||||
|
let orderNumbers: string[] = []
|
||||||
|
|
||||||
|
if (sharedIds.length > 0) {
|
||||||
|
log.info(`Using ${sharedIds.length} shared Production IDs`)
|
||||||
|
orderNumbers = await getSourceNumbersFromInputs(sharedIds, dbService)
|
||||||
|
log.info(`Got ${orderNumbers.length} order numbers`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const materialCodes = await this.loadMaterialCodesForCleaner(dbService, username, isAdmin)
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
orderNumbers,
|
||||||
|
materialCodes
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
log.error('CleanerData error', { error: message })
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `获取清理数据失败:${message}`
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (dbService) {
|
||||||
|
await this.disconnectQuietly(dbService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private emptyFailure(error: string): ValidationResponse {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error,
|
||||||
|
stats: {
|
||||||
|
totalRecords: 0,
|
||||||
|
matchedCount: 0,
|
||||||
|
markedCount: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadTypeKeywords(dbService: ValidationDatabaseService): Promise<TypeKeyword[]> {
|
||||||
|
const typeKeywordTableName = getValidationTableName('dbo_MaterialsTypeToBeDeleted')
|
||||||
|
const sql = `
|
||||||
|
SELECT MaterialName, ManagerName
|
||||||
|
FROM ${typeKeywordTableName}
|
||||||
|
WHERE MaterialName IS NOT NULL
|
||||||
|
`
|
||||||
|
const result = await dbService.query(sql)
|
||||||
|
return result.rows.map((row) => ({
|
||||||
|
materialName: row.MaterialName as string,
|
||||||
|
managerName: row.ManagerName as string
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadMarkedCodes(
|
||||||
|
dbService: ValidationDatabaseService
|
||||||
|
): Promise<Map<string, string>> {
|
||||||
|
const markedTableName = getValidationTableName('dbo_MaterialsToBeDeleted')
|
||||||
|
const sql = `
|
||||||
|
SELECT MaterialCode, ManagerName
|
||||||
|
FROM ${markedTableName}
|
||||||
|
WHERE MaterialCode IS NOT NULL AND ManagerName IS NOT NULL
|
||||||
|
`
|
||||||
|
const result = await dbService.query(sql)
|
||||||
|
const markedCodes = new Map<string, string>()
|
||||||
|
|
||||||
|
for (const row of result.rows) {
|
||||||
|
markedCodes.set(row.MaterialCode as string, row.ManagerName as string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return markedCodes
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildValidationResults(
|
||||||
|
materialRecords: any[],
|
||||||
|
typeKeywords: TypeKeyword[],
|
||||||
|
markedCodes: Map<string, string>,
|
||||||
|
context: { isAdmin: boolean; username: string }
|
||||||
|
): ValidationResult[] {
|
||||||
|
return materialRecords.map((record) => {
|
||||||
|
const materialName = (record.MaterialName as string) || ''
|
||||||
|
const materialCode = (record.MaterialCode as string) || ''
|
||||||
|
const specification = (record.Specification as string) || ''
|
||||||
|
const model = (record.Model as string) || ''
|
||||||
|
|
||||||
|
let managerName = markedCodes.get(materialCode) || null
|
||||||
|
const isMarkedForDeletion = managerName !== null
|
||||||
|
let matchedTypeKeyword: string | undefined
|
||||||
|
|
||||||
|
if (!managerName) {
|
||||||
|
for (const typeKeyword of typeKeywords) {
|
||||||
|
if (typeKeyword.materialName && materialName.includes(typeKeyword.materialName)) {
|
||||||
|
matchedTypeKeyword = typeKeyword.materialName
|
||||||
|
managerName = typeKeyword.managerName
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context.isAdmin && context.username) {
|
||||||
|
const userKeywords = typeKeywords.filter(
|
||||||
|
(keyword) => keyword.managerName === context.username
|
||||||
|
)
|
||||||
|
for (const userKeyword of userKeywords) {
|
||||||
|
if (userKeyword.materialName && materialName.includes(userKeyword.materialName)) {
|
||||||
|
matchedTypeKeyword = userKeyword.materialName
|
||||||
|
managerName = userKeyword.managerName
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
materialName,
|
||||||
|
materialCode,
|
||||||
|
specification,
|
||||||
|
model,
|
||||||
|
managerName: managerName || '',
|
||||||
|
isMarkedForDeletion,
|
||||||
|
matchedTypeKeyword
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async enrichMaterials(
|
||||||
|
materials: Array<{ materialCode: string; managerName: string }>,
|
||||||
|
markedCodes: Set<string>
|
||||||
|
): Promise<MaterialRecordSummary[]> {
|
||||||
|
let dbService: ValidationDatabaseService | null = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
dbService = await createValidationDatabaseService()
|
||||||
|
const detailTableName = getValidationTableName('dbo_DiscreteMaterialPlanData')
|
||||||
|
const enrichedMaterials: MaterialRecordSummary[] = []
|
||||||
|
|
||||||
|
for (const material of materials) {
|
||||||
|
const detailResult = await this.queryMaterialDetail(
|
||||||
|
dbService,
|
||||||
|
detailTableName,
|
||||||
|
material.materialCode
|
||||||
|
)
|
||||||
|
const firstRow = detailResult.rows[0]
|
||||||
|
|
||||||
|
enrichedMaterials.push({
|
||||||
|
materialCode: material.materialCode,
|
||||||
|
materialName: firstRow ? (firstRow.MaterialName as string) : '',
|
||||||
|
specification: firstRow ? (firstRow.Specification as string) : '',
|
||||||
|
model: firstRow ? (firstRow.Model as string) : '',
|
||||||
|
managerName: material.managerName,
|
||||||
|
isMarked: markedCodes.has(material.materialCode)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return enrichedMaterials
|
||||||
|
} finally {
|
||||||
|
if (dbService) {
|
||||||
|
await this.disconnectQuietly(dbService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async queryMaterialDetail(
|
||||||
|
dbService: ValidationDatabaseService,
|
||||||
|
detailTableName: string,
|
||||||
|
materialCode: string
|
||||||
|
) {
|
||||||
|
if (dbService.type === 'sqlserver') {
|
||||||
|
const sql = await import('mssql')
|
||||||
|
return (dbService as SqlServerService).queryWithParams(
|
||||||
|
`
|
||||||
|
SELECT TOP 1 MaterialName, Specification, Model
|
||||||
|
FROM ${detailTableName}
|
||||||
|
WHERE MaterialCode = @materialCode
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
materialCode: { value: materialCode, type: sql.default.NVarChar }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbService.query(
|
||||||
|
`
|
||||||
|
SELECT MaterialName, Specification, Model
|
||||||
|
FROM ${detailTableName}
|
||||||
|
WHERE MaterialCode = ?
|
||||||
|
LIMIT 1
|
||||||
|
`,
|
||||||
|
[materialCode]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadMaterialCodesForCleaner(
|
||||||
|
dbService: ValidationDatabaseService,
|
||||||
|
username: string,
|
||||||
|
isAdmin: boolean
|
||||||
|
): Promise<string[]> {
|
||||||
|
const markedTableName = getValidationTableName('dbo_MaterialsToBeDeleted')
|
||||||
|
|
||||||
|
if (isAdmin) {
|
||||||
|
const result = await dbService.query(
|
||||||
|
`
|
||||||
|
SELECT MaterialCode
|
||||||
|
FROM ${markedTableName}
|
||||||
|
WHERE MaterialCode IS NOT NULL
|
||||||
|
`
|
||||||
|
)
|
||||||
|
const materialCodes = result.rows.map((row) => row.MaterialCode as string).filter(Boolean)
|
||||||
|
log.info(`Admin user: got ${materialCodes.length} materials`)
|
||||||
|
return materialCodes
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dbService.type === 'sqlserver') {
|
||||||
|
const sql = await import('mssql')
|
||||||
|
const result = await (dbService as SqlServerService).queryWithParams(
|
||||||
|
`
|
||||||
|
SELECT MaterialCode
|
||||||
|
FROM ${markedTableName}
|
||||||
|
WHERE ManagerName = @username AND MaterialCode IS NOT NULL
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
username: { value: username, type: sql.default.NVarChar }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const materialCodes = result.rows
|
||||||
|
.map((row: Record<string, unknown>) => row.MaterialCode as string)
|
||||||
|
.filter(Boolean)
|
||||||
|
log.info(`Regular user: got ${materialCodes.length} materials`)
|
||||||
|
return materialCodes
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await dbService.query(
|
||||||
|
`
|
||||||
|
SELECT MaterialCode
|
||||||
|
FROM ${markedTableName}
|
||||||
|
WHERE ManagerName = ? AND MaterialCode IS NOT NULL
|
||||||
|
`,
|
||||||
|
[username]
|
||||||
|
)
|
||||||
|
const materialCodes = result.rows.map((row) => row.MaterialCode as string).filter(Boolean)
|
||||||
|
log.info(`Regular user: got ${materialCodes.length} materials`)
|
||||||
|
return materialCodes
|
||||||
|
}
|
||||||
|
|
||||||
|
private async disconnectQuietly(dbService: ValidationDatabaseService): Promise<void> {
|
||||||
|
try {
|
||||||
|
await dbService.disconnect()
|
||||||
|
} catch (closeError) {
|
||||||
|
log.warn('Error disconnecting database', {
|
||||||
|
error: closeError instanceof Error ? closeError.message : String(closeError)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validationApplicationService = new ValidationApplicationService()
|
||||||
56
src/main/services/validation/validation-database.ts
Normal file
56
src/main/services/validation/validation-database.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { ConfigManager } from '../config/config-manager'
|
||||||
|
import { MySqlService } from '../database/mysql'
|
||||||
|
import { SqlServerService } from '../database/sql-server'
|
||||||
|
|
||||||
|
export type ValidationDatabaseService = MySqlService | SqlServerService
|
||||||
|
|
||||||
|
export async function createValidationDatabaseService(): Promise<ValidationDatabaseService> {
|
||||||
|
const configManager = ConfigManager.getInstance()
|
||||||
|
const config = configManager.getConfig()
|
||||||
|
const dbType = configManager.getDatabaseType()
|
||||||
|
|
||||||
|
if (dbType === 'sqlserver') {
|
||||||
|
const dbConfig = config.database.sqlserver
|
||||||
|
const sqlServerService = new SqlServerService({
|
||||||
|
server: dbConfig.server,
|
||||||
|
port: dbConfig.port,
|
||||||
|
user: dbConfig.username,
|
||||||
|
password: dbConfig.password,
|
||||||
|
database: dbConfig.database,
|
||||||
|
options: {
|
||||||
|
encrypt: false,
|
||||||
|
trustServerCertificate: dbConfig.trustServerCertificate
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await sqlServerService.connect()
|
||||||
|
return sqlServerService
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbConfig = config.database.mysql
|
||||||
|
const mysqlService = new MySqlService({
|
||||||
|
host: dbConfig.host,
|
||||||
|
port: dbConfig.port,
|
||||||
|
user: dbConfig.username,
|
||||||
|
password: dbConfig.password,
|
||||||
|
database: dbConfig.database
|
||||||
|
})
|
||||||
|
await mysqlService.connect()
|
||||||
|
return mysqlService
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getValidationTableName(mysqlTableName: string): string {
|
||||||
|
const configManager = ConfigManager.getInstance()
|
||||||
|
const dbType = configManager.getDatabaseType()
|
||||||
|
|
||||||
|
if (dbType === 'sqlserver') {
|
||||||
|
const firstUnderscoreIndex = mysqlTableName.indexOf('_')
|
||||||
|
if (firstUnderscoreIndex > 0) {
|
||||||
|
const schema = mysqlTableName.substring(0, firstUnderscoreIndex)
|
||||||
|
const tableName = mysqlTableName.substring(firstUnderscoreIndex + 1)
|
||||||
|
return `[${schema}].[${tableName}]`
|
||||||
|
}
|
||||||
|
return `[dbo].[${mysqlTableName}]`
|
||||||
|
}
|
||||||
|
|
||||||
|
return mysqlTableName
|
||||||
|
}
|
||||||
16
tests/unit/production-input-service.test.ts
Normal file
16
tests/unit/production-input-service.test.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { identifyInputType } from '../../src/main/services/validation/production-input-service'
|
||||||
|
|
||||||
|
describe('production-input-service', () => {
|
||||||
|
it('identifies order numbers', () => {
|
||||||
|
expect(identifyInputType('SC12345678901234')).toBe('order_number')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('identifies production ids', () => {
|
||||||
|
expect(identifyInputType('26A1234')).toBe('production_id')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns unknown for unsupported formats', () => {
|
||||||
|
expect(identifyInputType('invalid-value')).toBe('unknown')
|
||||||
|
})
|
||||||
|
})
|
||||||
31
tests/unit/shared-production-ids-store.test.ts
Normal file
31
tests/unit/shared-production-ids-store.test.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { SharedProductionIdsStore } from '../../src/main/services/validation/shared-production-ids-store'
|
||||||
|
|
||||||
|
describe('SharedProductionIdsStore', () => {
|
||||||
|
it('stores unique ids per sender', () => {
|
||||||
|
const store = new SharedProductionIdsStore()
|
||||||
|
|
||||||
|
store.set(1, ['A', 'B', 'A'])
|
||||||
|
|
||||||
|
expect(store.get(1)).toEqual(['A', 'B'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps sender scopes isolated', () => {
|
||||||
|
const store = new SharedProductionIdsStore()
|
||||||
|
|
||||||
|
store.set(1, ['A'])
|
||||||
|
store.set(2, ['B'])
|
||||||
|
|
||||||
|
expect(store.get(1)).toEqual(['A'])
|
||||||
|
expect(store.get(2)).toEqual(['B'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clears sender data', () => {
|
||||||
|
const store = new SharedProductionIdsStore()
|
||||||
|
|
||||||
|
store.set(1, ['A'])
|
||||||
|
store.clear(1)
|
||||||
|
|
||||||
|
expect(store.get(1)).toEqual([])
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user