feat: add TypeORM, logger, schemas, hooks and stores
- Add TypeORM integration with data-source, entities and repositories - Add logger service for structured logging - Add Zod validation schemas for auth, cleaner and extractor - Add custom React hooks (useAuth, useCleaner, useExtractor, useValidation) - Add Zustand stores (useAppStore, useUserStore) - Add UI components (Button, Modal, Toast) - Add error types and ErpBrowserManager - Refactor IPC handlers and services - Add unit tests for new modules Co-Authored-By: Claude (glm-5) <noreply@anthropic.com>
This commit is contained in:
97
src/main/services/database/data-source.ts
Normal file
97
src/main/services/database/data-source.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* TypeORM Data Source Configuration
|
||||
*
|
||||
* Provides a centralized database connection for TypeORM entities.
|
||||
* Supports both MySQL and SQL Server based on DB_TYPE environment variable.
|
||||
*/
|
||||
|
||||
import 'reflect-metadata'
|
||||
import { DataSource, DataSourceOptions } from 'typeorm'
|
||||
|
||||
/**
|
||||
* Get database type from environment
|
||||
*/
|
||||
function getDatabaseType(): 'mysql' | 'mssql' {
|
||||
const dbType = process.env.DB_TYPE?.toLowerCase()
|
||||
if (dbType === 'sqlserver' || dbType === 'mssql') {
|
||||
return 'mssql'
|
||||
}
|
||||
return 'mysql'
|
||||
}
|
||||
|
||||
/**
|
||||
* Build DataSourceOptions based on database type
|
||||
*/
|
||||
function buildDataSourceOptions(): DataSourceOptions {
|
||||
const type = getDatabaseType()
|
||||
|
||||
const commonOptions: Partial<DataSourceOptions> = {
|
||||
entities: [__dirname + '/entities/*.{ts,js}'],
|
||||
synchronize: false, // Never auto-sync in production
|
||||
logging: process.env.NODE_ENV !== 'production'
|
||||
}
|
||||
|
||||
if (type === 'mssql') {
|
||||
return {
|
||||
type: 'mssql',
|
||||
host: process.env.DB_SERVER || 'localhost',
|
||||
port: parseInt(process.env.DB_SQLSERVER_PORT || '1433', 10),
|
||||
username: process.env.DB_USERNAME || 'sa',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
database: process.env.DB_NAME || '',
|
||||
options: {
|
||||
encrypt: process.env.DB_TRUST_SERVER_CERTIFICATE === 'yes',
|
||||
trustServerCertificate: process.env.DB_TRUST_SERVER_CERTIFICATE === 'yes'
|
||||
},
|
||||
...commonOptions
|
||||
} as DataSourceOptions
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'mysql',
|
||||
host: process.env.DB_MYSQL_HOST || 'localhost',
|
||||
port: parseInt(process.env.DB_MYSQL_PORT || '3306', 10),
|
||||
username: process.env.DB_USERNAME || 'root',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
database: process.env.DB_NAME || '',
|
||||
...commonOptions
|
||||
} as DataSourceOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeORM DataSource singleton
|
||||
*/
|
||||
let dataSource: DataSource | null = null
|
||||
|
||||
/**
|
||||
* Get or create the DataSource
|
||||
*/
|
||||
export function getDataSource(): DataSource {
|
||||
if (!dataSource) {
|
||||
dataSource = new DataSource(buildDataSourceOptions())
|
||||
}
|
||||
return dataSource
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the DataSource
|
||||
*/
|
||||
export async function initializeDataSource(): Promise<DataSource> {
|
||||
const ds = getDataSource()
|
||||
if (!ds.isInitialized) {
|
||||
await ds.initialize()
|
||||
}
|
||||
return ds
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the DataSource
|
||||
*/
|
||||
export async function destroyDataSource(): Promise<void> {
|
||||
if (dataSource && dataSource.isInitialized) {
|
||||
await dataSource.destroy()
|
||||
dataSource = null
|
||||
}
|
||||
}
|
||||
|
||||
export default getDataSource
|
||||
@@ -11,6 +11,9 @@
|
||||
import { MySqlService } from './mysql'
|
||||
import { SqlServerService } from './sql-server'
|
||||
import sql from 'mssql'
|
||||
import { createLogger } from '../logger'
|
||||
|
||||
const log = createLogger('DiscreteMaterialPlanDAO')
|
||||
|
||||
/**
|
||||
* Material plan record interface
|
||||
@@ -171,13 +174,16 @@ export class DiscreteMaterialPlanDAO {
|
||||
|
||||
const sqlString = `SELECT * FROM ${tableName}`
|
||||
|
||||
const result = this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
const result =
|
||||
this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
|
||||
return result.rows
|
||||
} catch (error) {
|
||||
console.error('[DiscreteMaterialPlanDAO] Query all error:', error)
|
||||
log.error('Query all error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -215,13 +221,16 @@ export class DiscreteMaterialPlanDAO {
|
||||
WHERE rn = 1
|
||||
`
|
||||
|
||||
const result = this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
const result =
|
||||
this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
|
||||
return result.rows
|
||||
} catch (error) {
|
||||
console.error('[DiscreteMaterialPlanDAO] Query all distinct by material code error:', error)
|
||||
log.error('Query all distinct by material code error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -279,7 +288,9 @@ export class DiscreteMaterialPlanDAO {
|
||||
|
||||
return allResults
|
||||
} catch (error) {
|
||||
console.error('[DiscreteMaterialPlanDAO] Query by source numbers error:', error)
|
||||
log.error('Query by source numbers error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -371,7 +382,9 @@ export class DiscreteMaterialPlanDAO {
|
||||
|
||||
return allResults
|
||||
} catch (error) {
|
||||
console.error('[DiscreteMaterialPlanDAO] Query by source numbers distinct error:', error)
|
||||
log.error('Query by source numbers distinct error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -410,7 +423,9 @@ export class DiscreteMaterialPlanDAO {
|
||||
return result.rows
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[DiscreteMaterialPlanDAO] Query by source number error:', error)
|
||||
log.error('Query by source number error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -451,7 +466,9 @@ export class DiscreteMaterialPlanDAO {
|
||||
return result.rows
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[DiscreteMaterialPlanDAO] Query by plan number error:', error)
|
||||
log.error('Query by plan number error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -499,7 +516,9 @@ export class DiscreteMaterialPlanDAO {
|
||||
return result.rows
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[DiscreteMaterialPlanDAO] Query by plan numbers error:', error)
|
||||
log.error('Query by plan numbers error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -517,13 +536,16 @@ export class DiscreteMaterialPlanDAO {
|
||||
|
||||
const sqlString = `SELECT COUNT(*) as count FROM ${tableName}`
|
||||
|
||||
const result = this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
const result =
|
||||
this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
|
||||
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
||||
} catch (error) {
|
||||
console.error('[DiscreteMaterialPlanDAO] Count all error:', error)
|
||||
log.error('Count all error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -562,7 +584,9 @@ export class DiscreteMaterialPlanDAO {
|
||||
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[DiscreteMaterialPlanDAO] Count by plan number error:', error)
|
||||
log.error('Count by plan number error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -594,9 +618,7 @@ export class DiscreteMaterialPlanDAO {
|
||||
`
|
||||
|
||||
const result = await (dbService as SqlServerService).queryWithParams(sqlString, params)
|
||||
return result.rows
|
||||
.map(row => row.MaterialName as string)
|
||||
.filter(Boolean)
|
||||
return result.rows.map((row) => row.MaterialName as string).filter(Boolean)
|
||||
} else {
|
||||
const placeholders = sourceNumbers.map(() => '?').join(',')
|
||||
|
||||
@@ -608,9 +630,7 @@ export class DiscreteMaterialPlanDAO {
|
||||
`
|
||||
|
||||
const result = await (dbService as MySqlService).query(sqlString, sourceNumbers)
|
||||
return result.rows
|
||||
.map(row => row.MaterialName as string)
|
||||
.filter(Boolean)
|
||||
return result.rows.map((row) => row.MaterialName as string).filter(Boolean)
|
||||
}
|
||||
} else {
|
||||
const sqlString = `
|
||||
@@ -619,16 +639,17 @@ export class DiscreteMaterialPlanDAO {
|
||||
WHERE MaterialName IS NOT NULL
|
||||
`
|
||||
|
||||
const result = this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
const result =
|
||||
this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
|
||||
return result.rows
|
||||
.map(row => row.MaterialName as string)
|
||||
.filter(Boolean)
|
||||
return result.rows.map((row) => row.MaterialName as string).filter(Boolean)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[DiscreteMaterialPlanDAO] Get unique material names error:', error)
|
||||
log.error('Get unique material names error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -652,13 +673,16 @@ export class DiscreteMaterialPlanDAO {
|
||||
FROM ${tableName}
|
||||
`
|
||||
|
||||
const result = this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
const result =
|
||||
this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
|
||||
return result.rows.length > 0 ? result.rows[0] : {}
|
||||
} catch (error) {
|
||||
console.error('[DiscreteMaterialPlanDAO] Get statistics error:', error)
|
||||
log.error('Get statistics error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
143
src/main/services/database/entities/DiscreteMaterialPlan.ts
Normal file
143
src/main/services/database/entities/DiscreteMaterialPlan.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* TypeORM Entity for DiscreteMaterialPlanData table
|
||||
*/
|
||||
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm'
|
||||
|
||||
@Entity('DiscreteMaterialPlanData')
|
||||
export class DiscreteMaterialPlan {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number
|
||||
|
||||
@Column({ name: 'Factory', type: 'nvarchar', length: 100, nullable: true })
|
||||
factory!: string | null
|
||||
|
||||
@Column({ name: 'MaterialStatus', type: 'nvarchar', length: 50, nullable: true })
|
||||
materialStatus!: string | null
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'PlanNumber', type: 'nvarchar', length: 100, nullable: true })
|
||||
planNumber!: string | null
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'SourceNumber', type: 'nvarchar', length: 100, nullable: true })
|
||||
sourceNumber!: string | null
|
||||
|
||||
@Column({ name: 'MaterialType', type: 'nvarchar', length: 100, nullable: true })
|
||||
materialType!: string | null
|
||||
|
||||
@Column({ name: 'ProductCode', type: 'nvarchar', length: 100, nullable: true })
|
||||
productCode!: string | null
|
||||
|
||||
@Column({ name: 'ProductName', type: 'nvarchar', length: 255, nullable: true })
|
||||
productName!: string | null
|
||||
|
||||
@Column({ name: 'ProductUnit', type: 'nvarchar', length: 50, nullable: true })
|
||||
productUnit!: string | null
|
||||
|
||||
@Column({ name: 'ProductPlanQuantity', type: 'decimal', precision: 18, scale: 4, nullable: true })
|
||||
productPlanQuantity!: number | null
|
||||
|
||||
@Column({ name: 'UseDepartment', type: 'nvarchar', length: 100, nullable: true })
|
||||
useDepartment!: string | null
|
||||
|
||||
@Column({ name: 'Remark', type: 'nvarchar', length: 500, nullable: true })
|
||||
remark!: string | null
|
||||
|
||||
@Column({ name: 'Creator', type: 'nvarchar', length: 100, nullable: true })
|
||||
creator!: string | null
|
||||
|
||||
@Column({ name: 'CreateDate', type: 'datetime', nullable: true })
|
||||
createDate!: Date | null
|
||||
|
||||
@Column({ name: 'Approver', type: 'nvarchar', length: 100, nullable: true })
|
||||
approver!: string | null
|
||||
|
||||
@Column({ name: 'ApproveDate', type: 'datetime', nullable: true })
|
||||
approveDate!: Date | null
|
||||
|
||||
@Column({ name: 'SequenceNumber', type: 'int', nullable: true })
|
||||
sequenceNumber!: number | null
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'MaterialCode', type: 'nvarchar', length: 100, nullable: true })
|
||||
materialCode!: string | null
|
||||
|
||||
@Column({ name: 'MaterialName', type: 'nvarchar', length: 255, nullable: true })
|
||||
materialName!: string | null
|
||||
|
||||
@Column({ name: 'Specification', type: 'nvarchar', length: 255, nullable: true })
|
||||
specification!: string | null
|
||||
|
||||
@Column({ name: 'Model', type: 'nvarchar', length: 255, nullable: true })
|
||||
model!: string | null
|
||||
|
||||
@Column({ name: 'DrawingNumber', type: 'nvarchar', length: 100, nullable: true })
|
||||
drawingNumber!: string | null
|
||||
|
||||
@Column({ name: 'MaterialQuality', type: 'nvarchar', length: 100, nullable: true })
|
||||
materialQuality!: string | null
|
||||
|
||||
@Column({ name: 'PlanQuantity', type: 'decimal', precision: 18, scale: 4, nullable: true })
|
||||
planQuantity!: number | null
|
||||
|
||||
@Column({ name: 'Unit', type: 'nvarchar', length: 50, nullable: true })
|
||||
unit!: string | null
|
||||
|
||||
@Column({ name: 'RequiredDate', type: 'datetime', nullable: true })
|
||||
requiredDate!: Date | null
|
||||
|
||||
@Column({ name: 'Warehouse', type: 'nvarchar', length: 100, nullable: true })
|
||||
warehouse!: string | null
|
||||
|
||||
@Column({ name: 'UnitUsage', type: 'decimal', precision: 18, scale: 6, nullable: true })
|
||||
unitUsage!: number | null
|
||||
|
||||
@Column({
|
||||
name: 'CumulativeOutputQuantity',
|
||||
type: 'decimal',
|
||||
precision: 18,
|
||||
scale: 4,
|
||||
nullable: true
|
||||
})
|
||||
cumulativeOutputQuantity!: number | null
|
||||
|
||||
@Column({ name: 'BOMVersion', type: 'nvarchar', length: 50, nullable: true })
|
||||
bomVersion!: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Material plan record interface for type-safe operations
|
||||
*/
|
||||
export interface MaterialPlanRecordData {
|
||||
id?: number
|
||||
factory?: string | null
|
||||
materialStatus?: string | null
|
||||
planNumber?: string | null
|
||||
sourceNumber?: string | null
|
||||
materialType?: string | null
|
||||
productCode?: string | null
|
||||
productName?: string | null
|
||||
productUnit?: string | null
|
||||
productPlanQuantity?: number | null
|
||||
useDepartment?: string | null
|
||||
remark?: string | null
|
||||
creator?: string | null
|
||||
createDate?: Date | null
|
||||
approver?: string | null
|
||||
approveDate?: Date | null
|
||||
sequenceNumber?: number | null
|
||||
materialCode?: string | null
|
||||
materialName?: string | null
|
||||
specification?: string | null
|
||||
model?: string | null
|
||||
drawingNumber?: string | null
|
||||
materialQuality?: string | null
|
||||
planQuantity?: number | null
|
||||
unit?: string | null
|
||||
requiredDate?: Date | null
|
||||
warehouse?: string | null
|
||||
unitUsage?: number | null
|
||||
cumulativeOutputQuantity?: number | null
|
||||
bomVersion?: string | null
|
||||
}
|
||||
27
src/main/services/database/entities/MaterialsToBeDeleted.ts
Normal file
27
src/main/services/database/entities/MaterialsToBeDeleted.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* TypeORM Entity for MaterialsToBeDeleted table
|
||||
*/
|
||||
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm'
|
||||
|
||||
@Entity('MaterialsToBeDeleted')
|
||||
export class MaterialsToBeDeleted {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column({ name: 'MaterialCode', type: 'nvarchar', length: 255, nullable: false })
|
||||
materialCode!: string
|
||||
|
||||
@Column({ name: 'ManagerName', type: 'nvarchar', length: 255, nullable: true })
|
||||
managerName!: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Material record interface for type-safe operations
|
||||
*/
|
||||
export interface MaterialRecordData {
|
||||
id?: number
|
||||
materialCode: string
|
||||
managerName: string | null
|
||||
}
|
||||
@@ -11,6 +11,9 @@
|
||||
import { MySqlService } from './mysql'
|
||||
import { SqlServerService } from './sql-server'
|
||||
import sql from 'mssql'
|
||||
import { createLogger } from '../logger'
|
||||
|
||||
const log = createLogger('MaterialsToBeDeletedDAO')
|
||||
|
||||
/**
|
||||
* Material record interface
|
||||
@@ -132,7 +135,7 @@ export class MaterialsToBeDeletedDAO {
|
||||
*/
|
||||
async upsertMaterial(materialCode: string, managerName: string): Promise<boolean> {
|
||||
if (!materialCode || !materialCode.trim()) {
|
||||
console.error('[MaterialsToBeDeletedDAO] MaterialCode cannot be empty')
|
||||
log.error('MaterialCode cannot be empty')
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -167,7 +170,9 @@ export class MaterialsToBeDeletedDAO {
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Upsert material error:', error)
|
||||
log.error('Upsert material error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -177,7 +182,9 @@ export class MaterialsToBeDeletedDAO {
|
||||
* @param materials - List of materials with materialCode and managerName
|
||||
* @returns Statistics object
|
||||
*/
|
||||
async upsertBatch(materials: { materialCode: string; managerName: string }[]): Promise<UpsertStats> {
|
||||
async upsertBatch(
|
||||
materials: { materialCode: string; managerName: string }[]
|
||||
): Promise<UpsertStats> {
|
||||
if (!materials || materials.length === 0) {
|
||||
return { total: 0, success: 0, failed: 0 }
|
||||
}
|
||||
@@ -227,12 +234,17 @@ export class MaterialsToBeDeletedDAO {
|
||||
|
||||
stats.success++
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Error upserting material:', materialCode, error)
|
||||
log.error('Error upserting material', {
|
||||
materialCode,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
stats.failed++
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Batch upsert error:', error)
|
||||
log.error('Batch upsert error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
stats.failed = stats.total - stats.success
|
||||
}
|
||||
|
||||
@@ -256,13 +268,16 @@ export class MaterialsToBeDeletedDAO {
|
||||
WHERE MaterialCode IS NOT NULL
|
||||
`
|
||||
|
||||
const result = this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
const result =
|
||||
this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
|
||||
return new Set(result.rows.map(row => row.MaterialCode as string).filter(Boolean))
|
||||
return new Set(result.rows.map((row) => row.MaterialCode as string).filter(Boolean))
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Get all material codes error:', error)
|
||||
log.error('Get all material codes error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return new Set()
|
||||
}
|
||||
}
|
||||
@@ -283,17 +298,20 @@ export class MaterialsToBeDeletedDAO {
|
||||
ORDER BY ManagerName, MaterialCode
|
||||
`
|
||||
|
||||
const result = this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
const result =
|
||||
this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
|
||||
return result.rows.map(row => ({
|
||||
return result.rows.map((row) => ({
|
||||
id: row.ID as number,
|
||||
materialCode: row.MaterialCode as string,
|
||||
managerName: row.ManagerName as string
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Get all records error:', error)
|
||||
log.error('Get all records error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -320,7 +338,7 @@ export class MaterialsToBeDeletedDAO {
|
||||
managerName: { value: managerName, type: sql.NVarChar }
|
||||
})
|
||||
|
||||
return result.rows.map(row => ({
|
||||
return result.rows.map((row) => ({
|
||||
id: row.ID as number,
|
||||
materialCode: row.MaterialCode as string,
|
||||
managerName: row.ManagerName as string
|
||||
@@ -335,14 +353,16 @@ export class MaterialsToBeDeletedDAO {
|
||||
|
||||
const result = await (dbService as MySqlService).query(sqlString, [managerName])
|
||||
|
||||
return result.rows.map(row => ({
|
||||
return result.rows.map((row) => ({
|
||||
id: row.ID as number,
|
||||
materialCode: row.MaterialCode as string,
|
||||
managerName: row.ManagerName as string
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Get materials by manager error:', error)
|
||||
log.error('Get materials by manager error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -363,13 +383,16 @@ export class MaterialsToBeDeletedDAO {
|
||||
ORDER BY ManagerName
|
||||
`
|
||||
|
||||
const result = this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
const result =
|
||||
this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
|
||||
return result.rows.map(row => row.ManagerName as string).filter(Boolean)
|
||||
return result.rows.map((row) => row.ManagerName as string).filter(Boolean)
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Get managers error:', error)
|
||||
log.error('Get managers error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -427,7 +450,9 @@ export class MaterialsToBeDeletedDAO {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Get record by material code error:', error)
|
||||
log.error('Get record by material code error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -467,7 +492,9 @@ export class MaterialsToBeDeletedDAO {
|
||||
return result.rowCount > 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Delete by material code error:', error)
|
||||
log.error('Delete by material code error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -504,7 +531,9 @@ export class MaterialsToBeDeletedDAO {
|
||||
return result.rowCount
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Delete by manager error:', error)
|
||||
log.error('Delete by manager error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -520,13 +549,16 @@ export class MaterialsToBeDeletedDAO {
|
||||
|
||||
const sqlString = `DELETE FROM ${tableName}`
|
||||
|
||||
const result = this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
const result =
|
||||
this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
|
||||
return result.rowCount
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Delete all materials error:', error)
|
||||
log.error('Delete all materials error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -579,7 +611,9 @@ export class MaterialsToBeDeletedDAO {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Delete by material codes error:', error)
|
||||
log.error('Delete by material codes error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
}
|
||||
|
||||
return totalDeleted
|
||||
@@ -622,7 +656,9 @@ export class MaterialsToBeDeletedDAO {
|
||||
return result.rows.length > 0 && (result.rows[0].count as number) > 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Material exists error:', error)
|
||||
log.error('Material exists error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -638,13 +674,16 @@ export class MaterialsToBeDeletedDAO {
|
||||
|
||||
const sqlString = `SELECT COUNT(*) as count FROM ${tableName}`
|
||||
|
||||
const result = this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
const result =
|
||||
this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(sqlString)
|
||||
: await (dbService as MySqlService).query(sqlString)
|
||||
|
||||
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Count all error:', error)
|
||||
log.error('Count all error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -683,7 +722,9 @@ export class MaterialsToBeDeletedDAO {
|
||||
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Count by manager error:', error)
|
||||
log.error('Count by manager error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -706,9 +747,10 @@ export class MaterialsToBeDeletedDAO {
|
||||
WHERE MaterialCode IS NOT NULL
|
||||
`
|
||||
|
||||
const statsResult = this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(statsSql)
|
||||
: await (dbService as MySqlService).query(statsSql)
|
||||
const statsResult =
|
||||
this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(statsSql)
|
||||
: await (dbService as MySqlService).query(statsSql)
|
||||
|
||||
const stats = statsResult.rows[0] || {}
|
||||
|
||||
@@ -721,11 +763,12 @@ export class MaterialsToBeDeletedDAO {
|
||||
ORDER BY count DESC
|
||||
`
|
||||
|
||||
const managerResult = this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(managerSql)
|
||||
: await (dbService as MySqlService).query(managerSql)
|
||||
const managerResult =
|
||||
this.dbType === 'sqlserver'
|
||||
? await (dbService as SqlServerService).query(managerSql)
|
||||
: await (dbService as MySqlService).query(managerSql)
|
||||
|
||||
const materialsPerManager = managerResult.rows.map(row => ({
|
||||
const materialsPerManager = managerResult.rows.map((row) => ({
|
||||
[row.ManagerName as string]: row.count as number
|
||||
}))
|
||||
|
||||
@@ -735,7 +778,9 @@ export class MaterialsToBeDeletedDAO {
|
||||
materialsPerManager
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MaterialsToBeDeletedDAO] Get statistics error:', error)
|
||||
log.error('Get statistics error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return {
|
||||
totalMaterials: 0,
|
||||
uniqueManagers: 0,
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* Repository for DiscreteMaterialPlan entity
|
||||
*
|
||||
* Provides type-safe database operations for discrete material plan data.
|
||||
*/
|
||||
|
||||
import { DataSource, Repository, In } from 'typeorm'
|
||||
import { DiscreteMaterialPlan, MaterialPlanRecordData } from '../entities/DiscreteMaterialPlan'
|
||||
import { getDataSource } from '../data-source'
|
||||
import { createLogger } from '../../logger'
|
||||
|
||||
const log = createLogger('DiscreteMaterialPlanRepository')
|
||||
|
||||
/**
|
||||
* DiscreteMaterialPlan Repository class
|
||||
*/
|
||||
export class DiscreteMaterialPlanRepository {
|
||||
private repository: Repository<DiscreteMaterialPlan> | null = null
|
||||
private dataSource: DataSource | null = null
|
||||
|
||||
/**
|
||||
* Get the repository instance
|
||||
*/
|
||||
private async getRepository(): Promise<Repository<DiscreteMaterialPlan>> {
|
||||
if (!this.repository) {
|
||||
this.dataSource = getDataSource()
|
||||
if (!this.dataSource.isInitialized) {
|
||||
await this.dataSource.initialize()
|
||||
}
|
||||
this.repository = this.dataSource.getRepository(DiscreteMaterialPlan)
|
||||
}
|
||||
return this.repository
|
||||
}
|
||||
|
||||
/**
|
||||
* Query all records
|
||||
*/
|
||||
async queryAll(): Promise<DiscreteMaterialPlan[]> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
return await repo.find()
|
||||
} catch (error) {
|
||||
log.error('Query all failed', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query all records with deduplication by MaterialCode
|
||||
*/
|
||||
async queryAllDistinctByMaterialCode(): Promise<DiscreteMaterialPlan[]> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
|
||||
const query = `
|
||||
WITH RankedRecords AS (
|
||||
SELECT *,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY MaterialCode
|
||||
ORDER BY CreateDate ASC, SequenceNumber ASC
|
||||
) AS rn
|
||||
FROM DiscreteMaterialPlanData
|
||||
WHERE MaterialCode IS NOT NULL
|
||||
)
|
||||
SELECT * FROM RankedRecords WHERE rn = 1
|
||||
`
|
||||
|
||||
return await repo.query(query)
|
||||
} catch (error) {
|
||||
log.error('Query all distinct by material code failed', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query by source numbers (production order numbers)
|
||||
*/
|
||||
async queryBySourceNumbers(sourceNumbers: string[]): Promise<DiscreteMaterialPlan[]> {
|
||||
if (!sourceNumbers.length) return []
|
||||
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
const batchSize = 2000
|
||||
const allResults: DiscreteMaterialPlan[] = []
|
||||
|
||||
for (let i = 0; i < sourceNumbers.length; i += batchSize) {
|
||||
const batch = sourceNumbers.slice(i, i + batchSize)
|
||||
const results = await repo.find({
|
||||
where: { sourceNumber: In(batch) }
|
||||
})
|
||||
allResults.push(...results)
|
||||
}
|
||||
|
||||
return allResults
|
||||
} catch (error) {
|
||||
log.error('Query by source numbers failed', {
|
||||
count: sourceNumbers.length,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query by source numbers with deduplication by MaterialCode
|
||||
*/
|
||||
async queryBySourceNumbersDistinct(sourceNumbers: string[]): Promise<DiscreteMaterialPlan[]> {
|
||||
if (!sourceNumbers.length) return []
|
||||
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
const batchSize = 2000
|
||||
const allResults: DiscreteMaterialPlan[] = []
|
||||
|
||||
for (let i = 0; i < sourceNumbers.length; i += batchSize) {
|
||||
const batch = sourceNumbers.slice(i, i + batchSize)
|
||||
|
||||
const query = `
|
||||
WITH RankedRecords AS (
|
||||
SELECT *,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY MaterialCode
|
||||
ORDER BY CreateDate ASC, SequenceNumber ASC
|
||||
) AS rn
|
||||
FROM DiscreteMaterialPlanData
|
||||
WHERE SourceNumber IN (?) AND MaterialCode IS NOT NULL
|
||||
)
|
||||
SELECT * FROM RankedRecords WHERE rn = 1
|
||||
`
|
||||
|
||||
const results = await repo.query(query, [batch])
|
||||
allResults.push(...results)
|
||||
}
|
||||
|
||||
return allResults
|
||||
} catch (error) {
|
||||
log.error('Query by source numbers distinct failed', {
|
||||
count: sourceNumbers.length,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query by single source number
|
||||
*/
|
||||
async queryBySourceNumber(sourceNumber: string): Promise<DiscreteMaterialPlan[]> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
return await repo.find({ where: { sourceNumber } })
|
||||
} catch (error) {
|
||||
log.error('Query by source number failed', {
|
||||
sourceNumber,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query by plan number
|
||||
*/
|
||||
async queryByPlanNumber(planNumber: string): Promise<DiscreteMaterialPlan[]> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
return await repo.find({ where: { planNumber } })
|
||||
} catch (error) {
|
||||
log.error('Query by plan number failed', {
|
||||
planNumber,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query by multiple plan numbers
|
||||
*/
|
||||
async queryByPlanNumbers(planNumbers: string[]): Promise<DiscreteMaterialPlan[]> {
|
||||
if (!planNumbers.length) return []
|
||||
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
return await repo.find({
|
||||
where: { planNumber: In(planNumbers) }
|
||||
})
|
||||
} catch (error) {
|
||||
log.error('Query by plan numbers failed', {
|
||||
count: planNumbers.length,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all records
|
||||
*/
|
||||
async countAll(): Promise<number> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
return await repo.count()
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unique material names
|
||||
*/
|
||||
async getUniqueMaterialNames(sourceNumbers?: string[]): Promise<string[]> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
|
||||
let query = repo
|
||||
.createQueryBuilder('m')
|
||||
.select('DISTINCT m.materialName', 'materialName')
|
||||
.where('m.materialName IS NOT NULL')
|
||||
|
||||
if (sourceNumbers && sourceNumbers.length > 0) {
|
||||
query = query.andWhere('m.sourceNumber IN (:...sourceNumbers)', { sourceNumbers })
|
||||
}
|
||||
|
||||
const result = await query.orderBy('m.materialName', 'ASC').getRawMany()
|
||||
return result.map((r) => r.materialName).filter(Boolean)
|
||||
} catch (error) {
|
||||
log.error('Get unique material names failed', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistics
|
||||
*/
|
||||
async getStatistics(): Promise<{
|
||||
totalRecords: number
|
||||
uniquePlans: number
|
||||
uniqueOrders: number
|
||||
earliestRecord: Date | null
|
||||
latestRecord: Date | null
|
||||
}> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
|
||||
const result = await repo
|
||||
.createQueryBuilder('m')
|
||||
.select('COUNT(*)', 'totalRecords')
|
||||
.addSelect('COUNT(DISTINCT m.planNumber)', 'uniquePlans')
|
||||
.addSelect('COUNT(DISTINCT m.sourceNumber)', 'uniqueOrders')
|
||||
.addSelect('MIN(m.createDate)', 'earliestRecord')
|
||||
.addSelect('MAX(m.createDate)', 'latestRecord')
|
||||
.getRawOne()
|
||||
|
||||
return {
|
||||
totalRecords: parseInt(result?.totalRecords || '0', 10),
|
||||
uniquePlans: parseInt(result?.uniquePlans || '0', 10),
|
||||
uniqueOrders: parseInt(result?.uniqueOrders || '0', 10),
|
||||
earliestRecord: result?.earliestRecord || null,
|
||||
latestRecord: result?.latestRecord || null
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Get statistics failed', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return {
|
||||
totalRecords: 0,
|
||||
uniquePlans: 0,
|
||||
uniqueOrders: 0,
|
||||
earliestRecord: null,
|
||||
latestRecord: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* Repository for MaterialsToBeDeleted entity
|
||||
*
|
||||
* Provides type-safe database operations for materials to be deleted.
|
||||
*/
|
||||
|
||||
import { DataSource, Repository, In } from 'typeorm'
|
||||
import { MaterialsToBeDeleted, MaterialRecordData } from '../entities/MaterialsToBeDeleted'
|
||||
import { getDataSource } from '../data-source'
|
||||
import { createLogger } from '../../logger'
|
||||
|
||||
const log = createLogger('MaterialsToBeDeletedRepository')
|
||||
|
||||
/**
|
||||
* Upsert statistics
|
||||
*/
|
||||
export interface UpsertStats {
|
||||
total: number
|
||||
success: number
|
||||
failed: number
|
||||
}
|
||||
|
||||
/**
|
||||
* MaterialsToBeDeleted Repository class
|
||||
*/
|
||||
export class MaterialsToBeDeletedRepository {
|
||||
private repository: Repository<MaterialsToBeDeleted> | null = null
|
||||
private dataSource: DataSource | null = null
|
||||
|
||||
/**
|
||||
* Get the repository instance
|
||||
*/
|
||||
private async getRepository(): Promise<Repository<MaterialsToBeDeleted>> {
|
||||
if (!this.repository) {
|
||||
this.dataSource = getDataSource()
|
||||
if (!this.dataSource.isInitialized) {
|
||||
await this.dataSource.initialize()
|
||||
}
|
||||
this.repository = this.dataSource.getRepository(MaterialsToBeDeleted)
|
||||
}
|
||||
return this.repository
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or update a single material record
|
||||
*/
|
||||
async upsert(materialCode: string, managerName: string | null): Promise<boolean> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
|
||||
// Use upsert pattern
|
||||
let entity = await repo.findOne({ where: { materialCode } })
|
||||
|
||||
if (entity) {
|
||||
entity.managerName = managerName
|
||||
} else {
|
||||
entity = repo.create({ materialCode, managerName })
|
||||
}
|
||||
|
||||
await repo.save(entity)
|
||||
log.debug('Upserted material', { materialCode })
|
||||
return true
|
||||
} catch (error) {
|
||||
log.error('Upsert material failed', {
|
||||
materialCode,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or update multiple material records in batch
|
||||
*/
|
||||
async upsertBatch(materials: MaterialRecordData[]): Promise<UpsertStats> {
|
||||
const stats: UpsertStats = {
|
||||
total: materials.length,
|
||||
success: 0,
|
||||
failed: 0
|
||||
}
|
||||
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
|
||||
for (const material of materials) {
|
||||
if (!material.materialCode?.trim()) {
|
||||
stats.failed++
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
let entity = await repo.findOne({ where: { materialCode: material.materialCode } })
|
||||
|
||||
if (entity) {
|
||||
entity.managerName = material.managerName
|
||||
} else {
|
||||
entity = repo.create({
|
||||
materialCode: material.materialCode,
|
||||
managerName: material.managerName
|
||||
})
|
||||
}
|
||||
|
||||
await repo.save(entity)
|
||||
stats.success++
|
||||
} catch {
|
||||
stats.failed++
|
||||
}
|
||||
}
|
||||
|
||||
log.info('Batch upsert completed', stats)
|
||||
return stats
|
||||
} catch (error) {
|
||||
log.error('Batch upsert failed', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
stats.failed = stats.total - stats.success
|
||||
return stats
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all material codes as a set
|
||||
*/
|
||||
async getAllMaterialCodes(): Promise<Set<string>> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
const records = await repo.find({
|
||||
select: ['materialCode'],
|
||||
where: { materialCode: In([]) } // This will be overridden
|
||||
})
|
||||
|
||||
// Use query builder for better performance
|
||||
const result = await repo
|
||||
.createQueryBuilder('m')
|
||||
.select('m.materialCode')
|
||||
.where('m.materialCode IS NOT NULL')
|
||||
.getMany()
|
||||
|
||||
return new Set(result.map((r) => r.materialCode).filter(Boolean))
|
||||
} catch (error) {
|
||||
log.error('Get all material codes failed', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return new Set()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all records
|
||||
*/
|
||||
async getAllRecords(): Promise<MaterialsToBeDeleted[]> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
return await repo.find({
|
||||
order: { managerName: 'ASC', materialCode: 'ASC' }
|
||||
})
|
||||
} catch (error) {
|
||||
log.error('Get all records failed', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get materials by manager name
|
||||
*/
|
||||
async getByManager(managerName: string): Promise<MaterialsToBeDeleted[]> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
return await repo.find({
|
||||
where: { managerName },
|
||||
order: { materialCode: 'ASC' }
|
||||
})
|
||||
} catch (error) {
|
||||
log.error('Get by manager failed', {
|
||||
managerName,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unique manager names
|
||||
*/
|
||||
async getManagers(): Promise<string[]> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
const result = await repo
|
||||
.createQueryBuilder('m')
|
||||
.select('DISTINCT m.managerName', 'managerName')
|
||||
.where('m.managerName IS NOT NULL')
|
||||
.orderBy('m.managerName', 'ASC')
|
||||
.getRawMany()
|
||||
|
||||
return result.map((r) => r.managerName).filter(Boolean)
|
||||
} catch (error) {
|
||||
log.error('Get managers failed', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete by material code
|
||||
*/
|
||||
async deleteByMaterialCode(materialCode: string): Promise<boolean> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
const result = await repo.delete({ materialCode })
|
||||
return (result.affected ?? 0) > 0
|
||||
} catch (error) {
|
||||
log.error('Delete by material code failed', {
|
||||
materialCode,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete multiple materials by codes
|
||||
*/
|
||||
async deleteByMaterialCodes(materialCodes: string[]): Promise<number> {
|
||||
if (!materialCodes.length) return 0
|
||||
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
const result = await repo.delete({ materialCode: In(materialCodes) })
|
||||
return result.affected ?? 0
|
||||
} catch (error) {
|
||||
log.error('Delete by material codes failed', {
|
||||
count: materialCodes.length,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a material exists
|
||||
*/
|
||||
async exists(materialCode: string): Promise<boolean> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
const count = await repo.count({ where: { materialCode } })
|
||||
return count > 0
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all records
|
||||
*/
|
||||
async countAll(): Promise<number> {
|
||||
try {
|
||||
const repo = await this.getRepository()
|
||||
return await repo.count()
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user