feat(logging-p0): Wave 3 - Database DAO layer transformed with enhanced logging
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
import { create, type IDatabaseService } from './index'
|
||||
import { createLogger } from '../logger'
|
||||
import { createLogger, run, getRequestId, trackDuration } from '../logger'
|
||||
|
||||
const log = createLogger('DiscreteMaterialPlanDAO')
|
||||
|
||||
@@ -138,11 +138,17 @@ export class DiscreteMaterialPlanDAO {
|
||||
const tableName = this.getTableName()
|
||||
|
||||
const sqlString = `SELECT * FROM ${tableName}`
|
||||
const result = await dbService.query(sqlString)
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString), {
|
||||
operationName: 'DiscreteMaterialPlanDAO.queryAll',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
|
||||
return result.rows
|
||||
return result.result.rows
|
||||
} catch (error) {
|
||||
log.error('Query all error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -182,10 +188,16 @@ export class DiscreteMaterialPlanDAO {
|
||||
WHERE rn = 1
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString)
|
||||
return result.rows
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString), {
|
||||
operationName: 'DiscreteMaterialPlanDAO.queryAllDistinctByMaterialCode',
|
||||
context: { tableName: this.getTableName(), operationType: 'SELECT' }
|
||||
})
|
||||
return result.result.rows
|
||||
} catch (error) {
|
||||
log.error('Query all distinct by material code error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -221,13 +233,25 @@ export class DiscreteMaterialPlanDAO {
|
||||
WHERE SourceNumber IN (${placeholders})
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, batch)
|
||||
allResults.push(...result.rows)
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, batch), {
|
||||
operationName: 'DiscreteMaterialPlanDAO.queryBySourceNumbers',
|
||||
context: {
|
||||
tableName,
|
||||
operationType: 'SELECT',
|
||||
batchNumber: Math.floor(i / batchSize) + 1,
|
||||
batchSize: batch.length
|
||||
}
|
||||
})
|
||||
allResults.push(...result.result.rows)
|
||||
}
|
||||
|
||||
return allResults
|
||||
} catch (error) {
|
||||
log.error('Query by source numbers error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
recordCount: sourceNumbers.length,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -280,13 +304,25 @@ export class DiscreteMaterialPlanDAO {
|
||||
WHERE rn = 1
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, batch)
|
||||
allResults.push(...result.rows)
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, batch), {
|
||||
operationName: 'DiscreteMaterialPlanDAO.queryBySourceNumbersDistinct',
|
||||
context: {
|
||||
tableName,
|
||||
operationType: 'SELECT',
|
||||
batchNumber: Math.floor(i / batchSize) + 1,
|
||||
batchSize: batch.length
|
||||
}
|
||||
})
|
||||
allResults.push(...result.result.rows)
|
||||
}
|
||||
|
||||
return allResults
|
||||
} catch (error) {
|
||||
log.error('Query by source numbers distinct error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
recordCount: sourceNumbers.length,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -311,10 +347,19 @@ export class DiscreteMaterialPlanDAO {
|
||||
WHERE SourceNumber = ${placeholder}
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [sourceNumber])
|
||||
return result.rows
|
||||
const result = await trackDuration(
|
||||
async () => await dbService.query(sqlString, [sourceNumber]),
|
||||
{
|
||||
operationName: 'DiscreteMaterialPlanDAO.queryBySourceNumber',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
}
|
||||
)
|
||||
return result.result.rows
|
||||
} catch (error) {
|
||||
log.error('Query by source number error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -341,10 +386,19 @@ export class DiscreteMaterialPlanDAO {
|
||||
WHERE PlanNumber = ${placeholder}
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [planNumber])
|
||||
return result.rows
|
||||
const result = await trackDuration(
|
||||
async () => await dbService.query(sqlString, [planNumber]),
|
||||
{
|
||||
operationName: 'DiscreteMaterialPlanDAO.queryByPlanNumber',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
}
|
||||
)
|
||||
return result.result.rows
|
||||
} catch (error) {
|
||||
log.error('Query by plan number error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -378,13 +432,25 @@ export class DiscreteMaterialPlanDAO {
|
||||
WHERE PlanNumber IN (${placeholders})
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, batch)
|
||||
allResults.push(...result.rows)
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, batch), {
|
||||
operationName: 'DiscreteMaterialPlanDAO.queryByPlanNumbers',
|
||||
context: {
|
||||
tableName,
|
||||
operationType: 'SELECT',
|
||||
batchNumber: Math.floor(i / batchSize) + 1,
|
||||
batchSize: batch.length
|
||||
}
|
||||
})
|
||||
allResults.push(...result.result.rows)
|
||||
}
|
||||
|
||||
return allResults
|
||||
} catch (error) {
|
||||
log.error('Query by plan numbers error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
recordCount: planNumbers.length,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -404,18 +470,31 @@ export class DiscreteMaterialPlanDAO {
|
||||
return 0
|
||||
}
|
||||
|
||||
const batchId = getRequestId() || `delete-${Date.now()}`
|
||||
let totalDeleted = 0
|
||||
|
||||
try {
|
||||
const dbService = await this.getDatabaseService()
|
||||
const tableName = this.getTableName()
|
||||
const isSqlServer = dbService.type === 'sqlserver'
|
||||
const batchSize = 2000
|
||||
let totalDeleted = 0
|
||||
|
||||
// Get unique source numbers
|
||||
const uniqueSourceNumbers = [...new Set(sourceNumbers.filter(Boolean))]
|
||||
const totalBatches = Math.ceil(uniqueSourceNumbers.length / batchSize)
|
||||
|
||||
log.info('Starting batch delete operation', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'DELETE',
|
||||
requestId: batchId,
|
||||
totalRecords: uniqueSourceNumbers.length,
|
||||
batchSize,
|
||||
totalBatches
|
||||
})
|
||||
|
||||
for (let i = 0; i < uniqueSourceNumbers.length; i += batchSize) {
|
||||
const batch = uniqueSourceNumbers.slice(i, i + batchSize)
|
||||
const batchNumber = Math.floor(i / batchSize) + 1
|
||||
const placeholders = this.buildPlaceholders(batch.length, isSqlServer)
|
||||
|
||||
const sqlString = `
|
||||
@@ -423,16 +502,32 @@ export class DiscreteMaterialPlanDAO {
|
||||
WHERE SourceNumber IN (${placeholders})
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, batch)
|
||||
totalDeleted += result.rowCount || 0
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, batch), {
|
||||
operationName: 'DiscreteMaterialPlanDAO.deleteBySourceNumbers',
|
||||
context: {
|
||||
tableName,
|
||||
operationType: 'DELETE',
|
||||
batchId,
|
||||
batchNumber,
|
||||
totalBatches,
|
||||
batchSize: batch.length
|
||||
}
|
||||
})
|
||||
const deletedCount = result.result.rowCount || 0
|
||||
totalDeleted += deletedCount
|
||||
|
||||
log.debug('Deleted batch', {
|
||||
batch: i / batchSize + 1,
|
||||
count: result.rowCount
|
||||
batch: batchNumber,
|
||||
totalBatches,
|
||||
count: deletedCount,
|
||||
batchId
|
||||
})
|
||||
}
|
||||
|
||||
log.info('Deleted records by source numbers', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'DELETE',
|
||||
requestId: batchId,
|
||||
totalDeleted,
|
||||
sourceNumberCount: uniqueSourceNumbers.length
|
||||
})
|
||||
@@ -440,6 +535,11 @@ export class DiscreteMaterialPlanDAO {
|
||||
return totalDeleted
|
||||
} catch (error) {
|
||||
log.error('Delete by source numbers error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'DELETE',
|
||||
requestId: batchId,
|
||||
totalDeleted,
|
||||
recordCount: sourceNumbers.length,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
throw error
|
||||
@@ -459,11 +559,13 @@ export class DiscreteMaterialPlanDAO {
|
||||
return 0
|
||||
}
|
||||
|
||||
const batchId = getRequestId() || `insert-${Date.now()}`
|
||||
let totalInserted = 0
|
||||
|
||||
try {
|
||||
const dbService = await this.getDatabaseService()
|
||||
const tableName = this.getTableName()
|
||||
const isSqlServer = dbService.type === 'sqlserver'
|
||||
let totalInserted = 0
|
||||
|
||||
// SQL Server has a limit of 2100 parameters per query
|
||||
// Each record has 28 columns, so max rows per batch = 2100 / 28 = 75
|
||||
@@ -473,35 +575,61 @@ export class DiscreteMaterialPlanDAO {
|
||||
const effectiveBatchSize = isSqlServer
|
||||
? Math.min(batchSize, Math.floor(sqlServerMaxParams / columnsPerRow))
|
||||
: batchSize
|
||||
const totalBatches = Math.ceil(records.length / effectiveBatchSize)
|
||||
|
||||
log.info('Batch insert parameters', {
|
||||
log.info('Batch insert started', {
|
||||
tableName,
|
||||
operationType: 'INSERT',
|
||||
requestId: batchId,
|
||||
isSqlServer,
|
||||
dbType: dbService.type,
|
||||
columnsPerRow,
|
||||
effectiveBatchSize,
|
||||
totalRecords: records.length
|
||||
totalRecords: records.length,
|
||||
totalBatches
|
||||
})
|
||||
|
||||
// Process in batches
|
||||
for (let i = 0; i < records.length; i += effectiveBatchSize) {
|
||||
const batch = records.slice(i, i + effectiveBatchSize)
|
||||
const inserted = await this.insertBatch(dbService, tableName, batch, isSqlServer)
|
||||
const batchNumber = Math.floor(i / effectiveBatchSize) + 1
|
||||
|
||||
const inserted = await this.insertBatchWithTracking(
|
||||
dbService,
|
||||
tableName,
|
||||
batch,
|
||||
isSqlServer,
|
||||
batchId,
|
||||
batchNumber,
|
||||
totalBatches
|
||||
)
|
||||
totalInserted += inserted
|
||||
|
||||
log.debug('Inserted batch', {
|
||||
batch: Math.floor(i / effectiveBatchSize) + 1,
|
||||
count: inserted
|
||||
batch: batchNumber,
|
||||
totalBatches,
|
||||
count: inserted,
|
||||
batchId
|
||||
})
|
||||
}
|
||||
|
||||
log.info('Batch insert completed', {
|
||||
tableName,
|
||||
operationType: 'INSERT',
|
||||
requestId: batchId,
|
||||
totalInserted,
|
||||
batchSize: effectiveBatchSize
|
||||
batchSize: effectiveBatchSize,
|
||||
totalBatches
|
||||
})
|
||||
|
||||
return totalInserted
|
||||
} catch (error) {
|
||||
log.error('Batch insert error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'INSERT',
|
||||
requestId: batchId,
|
||||
totalInserted,
|
||||
recordCount: records.length,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
throw error
|
||||
@@ -509,13 +637,16 @@ export class DiscreteMaterialPlanDAO {
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a single batch of records
|
||||
* Insert a single batch of records with tracking
|
||||
*/
|
||||
private async insertBatch(
|
||||
private async insertBatchWithTracking(
|
||||
dbService: IDatabaseService,
|
||||
tableName: string,
|
||||
records: MaterialPlanRecord[],
|
||||
isSqlServer: boolean
|
||||
isSqlServer: boolean,
|
||||
batchId: string,
|
||||
batchNumber: number,
|
||||
totalBatches: number
|
||||
): Promise<number> {
|
||||
if (records.length === 0) {
|
||||
return 0
|
||||
@@ -567,8 +698,30 @@ export class DiscreteMaterialPlanDAO {
|
||||
VALUES ${rowPlaceholders.join(', ')}
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, values)
|
||||
return result.rowCount || records.length
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, values), {
|
||||
operationName: 'DiscreteMaterialPlanDAO.insertBatch',
|
||||
context: {
|
||||
tableName,
|
||||
operationType: 'INSERT',
|
||||
batchId,
|
||||
batchNumber,
|
||||
totalBatches,
|
||||
recordCount: records.length
|
||||
}
|
||||
})
|
||||
return result.result.rowCount || records.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a single batch of records (legacy method - kept for compatibility)
|
||||
*/
|
||||
private async insertBatch(
|
||||
dbService: IDatabaseService,
|
||||
tableName: string,
|
||||
records: MaterialPlanRecord[],
|
||||
isSqlServer: boolean
|
||||
): Promise<number> {
|
||||
return this.insertBatchWithTracking(dbService, tableName, records, isSqlServer, 'unknown', 1, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -660,11 +813,17 @@ export class DiscreteMaterialPlanDAO {
|
||||
const tableName = this.getTableName()
|
||||
|
||||
const sqlString = `SELECT COUNT(*) as count FROM ${tableName}`
|
||||
const result = await dbService.query(sqlString)
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString), {
|
||||
operationName: 'DiscreteMaterialPlanDAO.countAll',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
|
||||
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
||||
return result.result.rows.length > 0 ? (result.result.rows[0].count as number) : 0
|
||||
} catch (error) {
|
||||
log.error('Count all error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
@@ -689,10 +848,19 @@ export class DiscreteMaterialPlanDAO {
|
||||
WHERE PlanNumber = ${placeholder}
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [planNumber])
|
||||
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
||||
const result = await trackDuration(
|
||||
async () => await dbService.query(sqlString, [planNumber]),
|
||||
{
|
||||
operationName: 'DiscreteMaterialPlanDAO.countByPlanNumber',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
}
|
||||
)
|
||||
return result.result.rows.length > 0 ? (result.result.rows[0].count as number) : 0
|
||||
} catch (error) {
|
||||
log.error('Count by plan number error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
@@ -725,8 +893,18 @@ export class DiscreteMaterialPlanDAO {
|
||||
AND MaterialName IS NOT NULL
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, batch)
|
||||
allNames.push(...result.rows.map((row) => row.MaterialName as string).filter(Boolean))
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, batch), {
|
||||
operationName: 'DiscreteMaterialPlanDAO.getUniqueMaterialNames',
|
||||
context: {
|
||||
tableName,
|
||||
operationType: 'SELECT',
|
||||
batchNumber: Math.floor(i / batchSize) + 1,
|
||||
batchSize: batch.length
|
||||
}
|
||||
})
|
||||
allNames.push(
|
||||
...result.result.rows.map((row) => row.MaterialName as string).filter(Boolean)
|
||||
)
|
||||
}
|
||||
|
||||
return allNames
|
||||
@@ -737,11 +915,18 @@ export class DiscreteMaterialPlanDAO {
|
||||
WHERE MaterialName IS NOT NULL
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString)
|
||||
return result.rows.map((row) => row.MaterialName as string).filter(Boolean)
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString), {
|
||||
operationName: 'DiscreteMaterialPlanDAO.getUniqueMaterialNames',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
return result.result.rows.map((row) => row.MaterialName as string).filter(Boolean)
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Get unique material names error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
recordCount: sourceNumbers?.length || 0,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -767,10 +952,16 @@ export class DiscreteMaterialPlanDAO {
|
||||
FROM ${tableName}
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString)
|
||||
return result.rows.length > 0 ? result.rows[0] : {}
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString), {
|
||||
operationName: 'DiscreteMaterialPlanDAO.getStatistics',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
return result.result.rows.length > 0 ? result.result.rows[0] : {}
|
||||
} catch (error) {
|
||||
log.error('Get statistics error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return {}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
import { create, type IDatabaseService } from './index'
|
||||
import { createLogger } from '../logger'
|
||||
import { createLogger, run, getRequestId, trackDuration } from '../logger'
|
||||
import type {
|
||||
OperationHistoryRecord,
|
||||
BatchStats,
|
||||
@@ -106,15 +106,31 @@ export class ExtractorOperationHistoryDAO {
|
||||
records: InsertBatchRecordInput[]
|
||||
): Promise<boolean> {
|
||||
if (!records || records.length === 0) {
|
||||
log.warn('No records to insert')
|
||||
log.warn('No records to insert', {
|
||||
batchId,
|
||||
tableName: this.getTableName(),
|
||||
requestId: getRequestId()
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
const requestId = getRequestId() || `insert-${Date.now()}`
|
||||
|
||||
try {
|
||||
const dbService = await this.getDatabaseService()
|
||||
const tableName = this.getTableName()
|
||||
const isSqlServer = dbService.type === 'sqlserver'
|
||||
|
||||
log.info('Batch records insertion started', {
|
||||
tableName,
|
||||
operationType: 'INSERT',
|
||||
requestId,
|
||||
batchId,
|
||||
userId,
|
||||
username,
|
||||
recordCount: records.length
|
||||
})
|
||||
|
||||
for (const record of records) {
|
||||
try {
|
||||
if (isSqlServer) {
|
||||
@@ -124,13 +140,20 @@ export class ExtractorOperationHistoryDAO {
|
||||
VALUES
|
||||
(@p0, @p1, @p2, @p3, @p4, GETDATE(), 'pending')
|
||||
`
|
||||
await dbService.query(sqlString, [
|
||||
batchId,
|
||||
userId,
|
||||
username,
|
||||
record.productionId || null,
|
||||
record.orderNumber
|
||||
])
|
||||
await trackDuration(
|
||||
async () =>
|
||||
await dbService.query(sqlString, [
|
||||
batchId,
|
||||
userId,
|
||||
username,
|
||||
record.productionId || null,
|
||||
record.orderNumber
|
||||
]),
|
||||
{
|
||||
operationName: 'ExtractorOperationHistoryDAO.insertBatchRecords',
|
||||
context: { tableName, operationType: 'INSERT', batchId }
|
||||
}
|
||||
)
|
||||
} else {
|
||||
const sqlString = `
|
||||
INSERT INTO ${tableName}
|
||||
@@ -138,16 +161,26 @@ export class ExtractorOperationHistoryDAO {
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, NOW(), 'pending')
|
||||
`
|
||||
await dbService.query(sqlString, [
|
||||
batchId,
|
||||
userId,
|
||||
username,
|
||||
record.productionId || null,
|
||||
record.orderNumber
|
||||
])
|
||||
await trackDuration(
|
||||
async () =>
|
||||
await dbService.query(sqlString, [
|
||||
batchId,
|
||||
userId,
|
||||
username,
|
||||
record.productionId || null,
|
||||
record.orderNumber
|
||||
]),
|
||||
{
|
||||
operationName: 'ExtractorOperationHistoryDAO.insertBatchRecords',
|
||||
context: { tableName, operationType: 'INSERT', batchId }
|
||||
}
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error inserting individual record', {
|
||||
tableName,
|
||||
operationType: 'INSERT',
|
||||
requestId,
|
||||
batchId,
|
||||
orderNumber: record.orderNumber,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
@@ -155,10 +188,21 @@ export class ExtractorOperationHistoryDAO {
|
||||
}
|
||||
}
|
||||
|
||||
log.info('Batch records inserted', { batchId, count: records.length })
|
||||
log.info('Batch records inserted', {
|
||||
tableName,
|
||||
operationType: 'INSERT',
|
||||
requestId,
|
||||
batchId,
|
||||
count: records.length
|
||||
})
|
||||
return true
|
||||
} catch (error) {
|
||||
log.error('Insert batch records error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'INSERT',
|
||||
requestId,
|
||||
batchId,
|
||||
recordCount: records.length,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return false
|
||||
@@ -186,12 +230,24 @@ export class ExtractorOperationHistoryDAO {
|
||||
`
|
||||
const params = [status, batchId]
|
||||
|
||||
await dbService.query(sqlString, params)
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, params), {
|
||||
operationName: 'ExtractorOperationHistoryDAO.updateBatchStatus',
|
||||
context: { tableName, operationType: 'UPDATE', batchId }
|
||||
})
|
||||
|
||||
log.info('Batch status updated', { batchId, status })
|
||||
log.info('Batch status updated', {
|
||||
tableName,
|
||||
operationType: 'UPDATE',
|
||||
requestId: getRequestId(),
|
||||
batchId,
|
||||
status
|
||||
})
|
||||
return { success: true, updatedCount: 1 }
|
||||
} catch (error) {
|
||||
log.error('Update batch status error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'UPDATE',
|
||||
requestId: getRequestId(),
|
||||
batchId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
@@ -244,11 +300,17 @@ export class ExtractorOperationHistoryDAO {
|
||||
params = [status, errorMessage || null, batchId, orderNumber]
|
||||
}
|
||||
|
||||
await dbService.query(sqlString, params)
|
||||
await trackDuration(async () => await dbService.query(sqlString, params), {
|
||||
operationName: 'ExtractorOperationHistoryDAO.updateRecordStatus',
|
||||
context: { tableName, operationType: 'UPDATE', batchId }
|
||||
})
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
log.error('Update record status error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'UPDATE',
|
||||
requestId: getRequestId(),
|
||||
batchId,
|
||||
orderNumber,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
@@ -291,7 +353,6 @@ export class ExtractorOperationHistoryDAO {
|
||||
sqlString += isSqlServer ? ` WHERE UserId = @p0 ` : ` WHERE UserId = ? `
|
||||
params.push(userId)
|
||||
} else if (options?.usernames && options.usernames.length > 0) {
|
||||
// Admin user filtering by multiple usernames using IN clause
|
||||
const placeholders = this.buildPlaceholders(options.usernames.length, isSqlServer)
|
||||
sqlString += ` WHERE Username IN (${placeholders}) `
|
||||
params.push(...options.usernames)
|
||||
@@ -307,7 +368,6 @@ export class ExtractorOperationHistoryDAO {
|
||||
const safeOffset = options.offset !== undefined ? Math.floor(options.offset) : undefined
|
||||
|
||||
if (isSqlServer) {
|
||||
// SQL Server: use parameterized OFFSET/FETCH
|
||||
const offsetIndex = params.length
|
||||
if (safeOffset !== undefined) {
|
||||
params.push(safeOffset)
|
||||
@@ -320,9 +380,6 @@ export class ExtractorOperationHistoryDAO {
|
||||
sqlString += ` OFFSET 0 ROWS FETCH NEXT @p${offsetIndex} ROWS ONLY`
|
||||
}
|
||||
} else {
|
||||
// MySQL: embed validated integer values directly.
|
||||
// connection.execute() uses binary protocol prepared statements,
|
||||
// which do not reliably support ? placeholders in LIMIT/OFFSET clauses.
|
||||
if (safeOffset !== undefined) {
|
||||
sqlString += ` LIMIT ${safeLimit} OFFSET ${safeOffset}`
|
||||
} else {
|
||||
@@ -331,9 +388,12 @@ export class ExtractorOperationHistoryDAO {
|
||||
}
|
||||
}
|
||||
|
||||
const result = await dbService.query(sqlString, params)
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, params), {
|
||||
operationName: 'ExtractorOperationHistoryDAO.getBatches',
|
||||
context: { tableName, operationType: 'SELECT', userId }
|
||||
})
|
||||
|
||||
return result.rows.map((row) => ({
|
||||
return result.result.rows.map((row) => ({
|
||||
batchId: row.BatchId as string,
|
||||
userId: row.UserId as number,
|
||||
username: row.Username as string,
|
||||
@@ -346,6 +406,10 @@ export class ExtractorOperationHistoryDAO {
|
||||
}))
|
||||
} catch (error) {
|
||||
log.error('Get batches error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
userId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -381,9 +445,12 @@ export class ExtractorOperationHistoryDAO {
|
||||
ORDER BY ID
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [batchId])
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, [batchId]), {
|
||||
operationName: 'ExtractorOperationHistoryDAO.getBatchDetails',
|
||||
context: { tableName, operationType: 'SELECT', batchId }
|
||||
})
|
||||
|
||||
return result.rows.map((row) => ({
|
||||
return result.result.rows.map((row) => ({
|
||||
id: row.ID as number,
|
||||
batchId: row.BatchId as string,
|
||||
userId: row.UserId as number,
|
||||
@@ -397,6 +464,9 @@ export class ExtractorOperationHistoryDAO {
|
||||
}))
|
||||
} catch (error) {
|
||||
log.error('Get batch details error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
batchId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
@@ -432,13 +502,16 @@ export class ExtractorOperationHistoryDAO {
|
||||
GROUP BY BatchId, UserId, Username
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [batchId])
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, [batchId]), {
|
||||
operationName: 'ExtractorOperationHistoryDAO.getBatchStats',
|
||||
context: { tableName, operationType: 'SELECT', batchId }
|
||||
})
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
if (result.result.rows.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const row = result.rows[0]
|
||||
const row = result.result.rows[0]
|
||||
return {
|
||||
batchId: row.BatchId as string,
|
||||
userId: row.UserId as number,
|
||||
@@ -452,6 +525,9 @@ export class ExtractorOperationHistoryDAO {
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Get batch stats error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
batchId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
@@ -473,6 +549,8 @@ export class ExtractorOperationHistoryDAO {
|
||||
requestingUserId: number,
|
||||
isAdmin: boolean
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
const requestId = getRequestId() || `delete-${Date.now()}`
|
||||
|
||||
try {
|
||||
const dbService = await this.getDatabaseService()
|
||||
const tableName = this.getTableName()
|
||||
@@ -497,12 +575,24 @@ export class ExtractorOperationHistoryDAO {
|
||||
WHERE BatchId = ${placeholder}
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [batchId])
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, [batchId]), {
|
||||
operationName: 'ExtractorOperationHistoryDAO.deleteBatch',
|
||||
context: { tableName, operationType: 'DELETE', batchId, requestingUserId }
|
||||
})
|
||||
|
||||
log.info('Batch deleted', { batchId, rowCount: result.rowCount })
|
||||
log.info('Batch deleted', {
|
||||
tableName,
|
||||
operationType: 'DELETE',
|
||||
requestId,
|
||||
batchId,
|
||||
rowCount: result.result.rowCount
|
||||
})
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
log.error('Delete batch error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'DELETE',
|
||||
requestId,
|
||||
batchId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
@@ -530,10 +620,16 @@ export class ExtractorOperationHistoryDAO {
|
||||
WHERE UserId = ${placeholder}
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [userId])
|
||||
return result.rowCount
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, [userId]), {
|
||||
operationName: 'ExtractorOperationHistoryDAO.deleteByUser',
|
||||
context: { tableName, operationType: 'DELETE', userId }
|
||||
})
|
||||
return result.result.rowCount
|
||||
} catch (error) {
|
||||
log.error('Delete by user error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'DELETE',
|
||||
requestId: getRequestId(),
|
||||
userId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
@@ -561,10 +657,16 @@ export class ExtractorOperationHistoryDAO {
|
||||
WHERE BatchId = ${placeholder}
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [batchId])
|
||||
return result.rows.length > 0 && (result.rows[0].count as number) > 0
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, [batchId]), {
|
||||
operationName: 'ExtractorOperationHistoryDAO.batchExists',
|
||||
context: { tableName, operationType: 'SELECT', batchId }
|
||||
})
|
||||
return result.result.rows.length > 0 && (result.result.rows[0].count as number) > 0
|
||||
} catch (error) {
|
||||
log.error('Batch exists error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
batchId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
@@ -595,16 +697,21 @@ export class ExtractorOperationHistoryDAO {
|
||||
sqlString += isSqlServer ? ` WHERE UserId = @p0 ` : ` WHERE UserId = ? `
|
||||
params.push(userId)
|
||||
} else if (usernames && usernames.length > 0) {
|
||||
// Admin user filtering by multiple usernames using IN clause
|
||||
const placeholders = this.buildPlaceholders(usernames.length, isSqlServer)
|
||||
sqlString += ` WHERE Username IN (${placeholders}) `
|
||||
params.push(...usernames)
|
||||
}
|
||||
|
||||
const result = await dbService.query(sqlString, params)
|
||||
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, params), {
|
||||
operationName: 'ExtractorOperationHistoryDAO.countBatches',
|
||||
context: { tableName, operationType: 'SELECT', userId }
|
||||
})
|
||||
return result.result.rows.length > 0 ? (result.result.rows[0].count as number) : 0
|
||||
} catch (error) {
|
||||
log.error('Count batches error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
import { create, type IDatabaseService } from './index'
|
||||
import { createLogger } from '../logger'
|
||||
import { createLogger, run, getRequestId, trackDuration } from '../logger'
|
||||
|
||||
const log = createLogger('MaterialsToBeDeletedDAO')
|
||||
|
||||
@@ -100,7 +100,11 @@ export class MaterialsToBeDeletedDAO {
|
||||
*/
|
||||
async upsertMaterial(materialCode: string, managerName: string): Promise<boolean> {
|
||||
if (!materialCode || !materialCode.trim()) {
|
||||
log.error('MaterialCode cannot be empty')
|
||||
log.error('MaterialCode cannot be empty', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'UPSERT',
|
||||
requestId: getRequestId()
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -112,7 +116,6 @@ export class MaterialsToBeDeletedDAO {
|
||||
const isSqlServer = dbService.type === 'sqlserver'
|
||||
|
||||
if (isSqlServer) {
|
||||
// SQL Server MERGE statement
|
||||
const sqlString = `
|
||||
MERGE ${tableName} AS target
|
||||
USING (VALUES (@p0, @p1)) AS source (MaterialCode, ManagerName)
|
||||
@@ -121,21 +124,30 @@ export class MaterialsToBeDeletedDAO {
|
||||
WHEN NOT MATCHED THEN INSERT (MaterialCode, ManagerName) VALUES (source.MaterialCode, source.ManagerName);
|
||||
`
|
||||
|
||||
await dbService.query(sqlString, [code, manager])
|
||||
await trackDuration(async () => await dbService.query(sqlString, [code, manager]), {
|
||||
operationName: 'MaterialsToBeDeletedDAO.upsertMaterial',
|
||||
context: { tableName, operationType: 'MERGE' }
|
||||
})
|
||||
} else {
|
||||
// MySQL ON DUPLICATE KEY UPDATE
|
||||
const sqlString = `
|
||||
INSERT INTO ${tableName} (MaterialCode, ManagerName)
|
||||
VALUES (?, ?)
|
||||
ON DUPLICATE KEY UPDATE ManagerName = VALUES(ManagerName)
|
||||
`
|
||||
|
||||
await dbService.query(sqlString, [code, manager])
|
||||
await trackDuration(async () => await dbService.query(sqlString, [code, manager]), {
|
||||
operationName: 'MaterialsToBeDeletedDAO.upsertMaterial',
|
||||
context: { tableName, operationType: 'INSERT' }
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
log.error('Upsert material error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'UPSERT',
|
||||
requestId: getRequestId(),
|
||||
materialCode: materialCode.trim(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return false
|
||||
@@ -154,6 +166,7 @@ export class MaterialsToBeDeletedDAO {
|
||||
return { total: 0, success: 0, failed: 0 }
|
||||
}
|
||||
|
||||
const batchId = getRequestId() || `upsert-${Date.now()}`
|
||||
const stats: UpsertStats = {
|
||||
total: materials.length,
|
||||
success: 0,
|
||||
@@ -165,6 +178,14 @@ export class MaterialsToBeDeletedDAO {
|
||||
const tableName = this.getTableName()
|
||||
const isSqlServer = dbService.type === 'sqlserver'
|
||||
|
||||
log.info('Batch upsert started', {
|
||||
tableName,
|
||||
operationType: 'UPSERT',
|
||||
requestId: batchId,
|
||||
totalRecords: materials.length,
|
||||
dbType: dbService.type
|
||||
})
|
||||
|
||||
for (const material of materials) {
|
||||
const materialCode = material.materialCode?.trim()
|
||||
const managerName = material.managerName?.trim() || ''
|
||||
@@ -176,7 +197,6 @@ export class MaterialsToBeDeletedDAO {
|
||||
|
||||
try {
|
||||
if (isSqlServer) {
|
||||
// SQL Server MERGE statement
|
||||
const sqlString = `
|
||||
MERGE ${tableName} AS target
|
||||
USING (VALUES (@p0, @p1)) AS source (MaterialCode, ManagerName)
|
||||
@@ -185,29 +205,56 @@ export class MaterialsToBeDeletedDAO {
|
||||
WHEN NOT MATCHED THEN INSERT (MaterialCode, ManagerName) VALUES (source.MaterialCode, source.ManagerName);
|
||||
`
|
||||
|
||||
await dbService.query(sqlString, [materialCode, managerName || null])
|
||||
await trackDuration(
|
||||
async () => await dbService.query(sqlString, [materialCode, managerName || null]),
|
||||
{
|
||||
operationName: 'MaterialsToBeDeletedDAO.upsertBatch',
|
||||
context: { tableName, operationType: 'MERGE', batchId }
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// MySQL ON DUPLICATE KEY UPDATE
|
||||
const sqlString = `
|
||||
INSERT INTO ${tableName} (MaterialCode, ManagerName)
|
||||
VALUES (?, ?)
|
||||
ON DUPLICATE KEY UPDATE ManagerName = VALUES(ManagerName)
|
||||
`
|
||||
|
||||
await dbService.query(sqlString, [materialCode, managerName || null])
|
||||
await trackDuration(
|
||||
async () => await dbService.query(sqlString, [materialCode, managerName || null]),
|
||||
{
|
||||
operationName: 'MaterialsToBeDeletedDAO.upsertBatch',
|
||||
context: { tableName, operationType: 'INSERT', batchId }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
stats.success++
|
||||
} catch (error) {
|
||||
log.error('Error upserting material', {
|
||||
tableName,
|
||||
operationType: 'UPSERT',
|
||||
requestId: batchId,
|
||||
materialCode,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
stats.failed++
|
||||
}
|
||||
}
|
||||
|
||||
log.info('Batch upsert completed', {
|
||||
tableName,
|
||||
operationType: 'UPSERT',
|
||||
requestId: batchId,
|
||||
success: stats.success,
|
||||
failed: stats.failed,
|
||||
total: stats.total
|
||||
})
|
||||
} catch (error) {
|
||||
log.error('Batch upsert error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'UPSERT',
|
||||
requestId: batchId,
|
||||
totalRecords: materials.length,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
stats.failed = stats.total - stats.success
|
||||
@@ -279,10 +326,16 @@ export class MaterialsToBeDeletedDAO {
|
||||
WHERE MaterialCode IS NOT NULL
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString)
|
||||
return new Set(result.rows.map((row) => row.MaterialCode as string).filter(Boolean))
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString), {
|
||||
operationName: 'MaterialsToBeDeletedDAO.getAllMaterialCodes',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
return new Set(result.result.rows.map((row) => row.MaterialCode as string).filter(Boolean))
|
||||
} catch (error) {
|
||||
log.error('Get all material codes error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return new Set()
|
||||
@@ -305,14 +358,20 @@ export class MaterialsToBeDeletedDAO {
|
||||
ORDER BY ManagerName, MaterialCode
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString)
|
||||
return result.rows.map((row) => ({
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString), {
|
||||
operationName: 'MaterialsToBeDeletedDAO.getAllRecords',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
return result.result.rows.map((row) => ({
|
||||
id: row.ID as number,
|
||||
materialCode: row.MaterialCode as string,
|
||||
managerName: row.ManagerName as string
|
||||
}))
|
||||
} catch (error) {
|
||||
log.error('Get all records error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -338,14 +397,23 @@ export class MaterialsToBeDeletedDAO {
|
||||
ORDER BY MaterialCode
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [managerName])
|
||||
return result.rows.map((row) => ({
|
||||
const result = await trackDuration(
|
||||
async () => await dbService.query(sqlString, [managerName]),
|
||||
{
|
||||
operationName: 'MaterialsToBeDeletedDAO.getMaterialsByManager',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
}
|
||||
)
|
||||
return result.result.rows.map((row) => ({
|
||||
id: row.ID as number,
|
||||
materialCode: row.MaterialCode as string,
|
||||
managerName: row.ManagerName as string
|
||||
}))
|
||||
} catch (error) {
|
||||
log.error('Get materials by manager error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -368,10 +436,16 @@ export class MaterialsToBeDeletedDAO {
|
||||
ORDER BY ManagerName
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString)
|
||||
return result.rows.map((row) => row.ManagerName as string).filter(Boolean)
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString), {
|
||||
operationName: 'MaterialsToBeDeletedDAO.getManagers',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
return result.result.rows.map((row) => row.ManagerName as string).filter(Boolean)
|
||||
} catch (error) {
|
||||
log.error('Get managers error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -397,13 +471,16 @@ export class MaterialsToBeDeletedDAO {
|
||||
WHERE MaterialCode = ${placeholder}
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [code])
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, [code]), {
|
||||
operationName: 'MaterialsToBeDeletedDAO.getRecordByMaterialCode',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
if (result.result.rows.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const row = result.rows[0]
|
||||
const row = result.result.rows[0]
|
||||
return {
|
||||
id: row.ID as number,
|
||||
materialCode: row.MaterialCode as string,
|
||||
@@ -411,6 +488,9 @@ export class MaterialsToBeDeletedDAO {
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Get record by material code error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return null
|
||||
@@ -437,10 +517,16 @@ export class MaterialsToBeDeletedDAO {
|
||||
WHERE MaterialCode = ${placeholder}
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [code])
|
||||
return result.rowCount > 0
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, [code]), {
|
||||
operationName: 'MaterialsToBeDeletedDAO.deleteByMaterialCode',
|
||||
context: { tableName, operationType: 'DELETE' }
|
||||
})
|
||||
return result.result.rowCount > 0
|
||||
} catch (error) {
|
||||
log.error('Delete by material code error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'DELETE',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return false
|
||||
@@ -464,10 +550,19 @@ export class MaterialsToBeDeletedDAO {
|
||||
WHERE ManagerName = ${placeholder}
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [managerName])
|
||||
return result.rowCount
|
||||
const result = await trackDuration(
|
||||
async () => await dbService.query(sqlString, [managerName]),
|
||||
{
|
||||
operationName: 'MaterialsToBeDeletedDAO.deleteByManager',
|
||||
context: { tableName, operationType: 'DELETE' }
|
||||
}
|
||||
)
|
||||
return result.result.rowCount
|
||||
} catch (error) {
|
||||
log.error('Delete by manager error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'DELETE',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
@@ -484,10 +579,16 @@ export class MaterialsToBeDeletedDAO {
|
||||
const tableName = this.getTableName()
|
||||
|
||||
const sqlString = `DELETE FROM ${tableName}`
|
||||
const result = await dbService.query(sqlString)
|
||||
return result.rowCount
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString), {
|
||||
operationName: 'MaterialsToBeDeletedDAO.deleteAllMaterials',
|
||||
context: { tableName, operationType: 'DELETE' }
|
||||
})
|
||||
return result.result.rowCount
|
||||
} catch (error) {
|
||||
log.error('Delete all materials error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'DELETE',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
@@ -504,6 +605,7 @@ export class MaterialsToBeDeletedDAO {
|
||||
return 0
|
||||
}
|
||||
|
||||
const batchId = getRequestId() || `delete-${Date.now()}`
|
||||
let totalDeleted = 0
|
||||
const batchSize = 1000
|
||||
|
||||
@@ -511,9 +613,20 @@ export class MaterialsToBeDeletedDAO {
|
||||
const dbService = await this.getDatabaseService()
|
||||
const tableName = this.getTableName()
|
||||
const isSqlServer = dbService.type === 'sqlserver'
|
||||
const totalBatches = Math.ceil(materialCodes.length / batchSize)
|
||||
|
||||
log.info('Batch delete started', {
|
||||
tableName,
|
||||
operationType: 'DELETE',
|
||||
requestId: batchId,
|
||||
totalRecords: materialCodes.length,
|
||||
batchSize,
|
||||
totalBatches
|
||||
})
|
||||
|
||||
for (let i = 0; i < materialCodes.length; i += batchSize) {
|
||||
const batch = materialCodes.slice(i, i + batchSize)
|
||||
const batchNumber = Math.floor(i / batchSize) + 1
|
||||
const placeholders = this.buildPlaceholders(batch.length, isSqlServer)
|
||||
|
||||
const sqlString = `
|
||||
@@ -521,14 +634,41 @@ export class MaterialsToBeDeletedDAO {
|
||||
WHERE MaterialCode IN (${placeholders})
|
||||
`
|
||||
|
||||
const result = await dbService.query(
|
||||
sqlString,
|
||||
batch.map((c) => c.trim())
|
||||
const result = await trackDuration(
|
||||
async () =>
|
||||
await dbService.query(
|
||||
sqlString,
|
||||
batch.map((c) => c.trim())
|
||||
),
|
||||
{
|
||||
operationName: 'MaterialsToBeDeletedDAO.deleteByMaterialCodes',
|
||||
context: {
|
||||
tableName,
|
||||
operationType: 'DELETE',
|
||||
batchId,
|
||||
batchNumber,
|
||||
totalBatches,
|
||||
batchSize: batch.length
|
||||
}
|
||||
}
|
||||
)
|
||||
totalDeleted += result.rowCount
|
||||
totalDeleted += result.result.rowCount
|
||||
}
|
||||
|
||||
log.info('Batch delete completed', {
|
||||
tableName,
|
||||
operationType: 'DELETE',
|
||||
requestId: batchId,
|
||||
totalDeleted,
|
||||
totalRecords: materialCodes.length
|
||||
})
|
||||
} catch (error) {
|
||||
log.error('Delete by material codes error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'DELETE',
|
||||
requestId: batchId,
|
||||
totalDeleted,
|
||||
recordCount: materialCodes.length,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
}
|
||||
@@ -557,10 +697,16 @@ export class MaterialsToBeDeletedDAO {
|
||||
WHERE MaterialCode = ${placeholder}
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [code])
|
||||
return result.rows.length > 0 && (result.rows[0].count as number) > 0
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, [code]), {
|
||||
operationName: 'MaterialsToBeDeletedDAO.materialExists',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
return result.result.rows.length > 0 && (result.result.rows[0].count as number) > 0
|
||||
} catch (error) {
|
||||
log.error('Material exists error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return false
|
||||
@@ -577,11 +723,17 @@ export class MaterialsToBeDeletedDAO {
|
||||
const tableName = this.getTableName()
|
||||
|
||||
const sqlString = `SELECT COUNT(*) as count FROM ${tableName}`
|
||||
const result = await dbService.query(sqlString)
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString), {
|
||||
operationName: 'MaterialsToBeDeletedDAO.countAll',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
|
||||
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
||||
return result.result.rows.length > 0 ? (result.result.rows[0].count as number) : 0
|
||||
} catch (error) {
|
||||
log.error('Count all error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
@@ -606,10 +758,19 @@ export class MaterialsToBeDeletedDAO {
|
||||
WHERE ManagerName = ${placeholder}
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [managerName])
|
||||
return result.rows.length > 0 ? (result.rows[0].count as number) : 0
|
||||
const result = await trackDuration(
|
||||
async () => await dbService.query(sqlString, [managerName]),
|
||||
{
|
||||
operationName: 'MaterialsToBeDeletedDAO.countByManager',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
}
|
||||
)
|
||||
return result.result.rows.length > 0 ? (result.result.rows[0].count as number) : 0
|
||||
} catch (error) {
|
||||
log.error('Count by manager error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return 0
|
||||
@@ -634,8 +795,11 @@ export class MaterialsToBeDeletedDAO {
|
||||
WHERE MaterialCode IS NOT NULL
|
||||
`
|
||||
|
||||
const statsResult = await dbService.query(statsSql)
|
||||
const stats = statsResult.rows[0] || {}
|
||||
const statsResult = await trackDuration(async () => await dbService.query(statsSql), {
|
||||
operationName: 'MaterialsToBeDeletedDAO.getStatistics',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
const stats = statsResult.result.rows[0] || {}
|
||||
|
||||
// Get materials per manager
|
||||
const managerSql = `
|
||||
@@ -646,8 +810,11 @@ export class MaterialsToBeDeletedDAO {
|
||||
ORDER BY count DESC
|
||||
`
|
||||
|
||||
const managerResult = await dbService.query(managerSql)
|
||||
const materialsPerManager = managerResult.rows.map((row) => ({
|
||||
const managerResult = await trackDuration(async () => await dbService.query(managerSql), {
|
||||
operationName: 'MaterialsToBeDeletedDAO.getStatistics.managers',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
const materialsPerManager = managerResult.result.rows.map((row) => ({
|
||||
[row.ManagerName as string]: row.count as number
|
||||
}))
|
||||
|
||||
@@ -658,6 +825,9 @@ export class MaterialsToBeDeletedDAO {
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Get statistics error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { create, type IDatabaseService } from './index'
|
||||
import { createLogger } from '../logger'
|
||||
import { createLogger, run, getRequestId, trackDuration } from '../logger'
|
||||
|
||||
const log = createLogger('MaterialsTypeToBeDeletedDAO')
|
||||
|
||||
@@ -87,14 +87,20 @@ export class MaterialsTypeToBeDeletedDAO {
|
||||
ORDER BY ManagerName, MaterialName
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString)
|
||||
return result.rows.map((row) => ({
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString), {
|
||||
operationName: 'MaterialsTypeToBeDeletedDAO.getAllMaterials',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
return result.result.rows.map((row) => ({
|
||||
id: row.ID as number,
|
||||
materialName: row.MaterialName as string,
|
||||
managerName: row.ManagerName as string
|
||||
}))
|
||||
} catch (error) {
|
||||
log.error('Get all materials error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -120,14 +126,23 @@ export class MaterialsTypeToBeDeletedDAO {
|
||||
ORDER BY MaterialName
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString, [managerName])
|
||||
return result.rows.map((row) => ({
|
||||
const result = await trackDuration(
|
||||
async () => await dbService.query(sqlString, [managerName]),
|
||||
{
|
||||
operationName: 'MaterialsTypeToBeDeletedDAO.getMaterialsByManager',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
}
|
||||
)
|
||||
return result.result.rows.map((row) => ({
|
||||
id: row.ID as number,
|
||||
materialName: row.MaterialName as string,
|
||||
managerName: row.ManagerName as string
|
||||
}))
|
||||
} catch (error) {
|
||||
log.error('Get materials by manager error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -150,10 +165,16 @@ export class MaterialsTypeToBeDeletedDAO {
|
||||
ORDER BY ManagerName
|
||||
`
|
||||
|
||||
const result = await dbService.query(sqlString)
|
||||
return result.rows.map((row) => row.ManagerName as string).filter(Boolean)
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString), {
|
||||
operationName: 'MaterialsTypeToBeDeletedDAO.getManagers',
|
||||
context: { tableName, operationType: 'SELECT' }
|
||||
})
|
||||
return result.result.rows.map((row) => row.ManagerName as string).filter(Boolean)
|
||||
} catch (error) {
|
||||
log.error('Get managers error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'SELECT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return []
|
||||
@@ -170,7 +191,11 @@ export class MaterialsTypeToBeDeletedDAO {
|
||||
*/
|
||||
async upsertMaterial(materialName: string, managerName: string): Promise<boolean> {
|
||||
if (!materialName || !materialName.trim()) {
|
||||
log.error('MaterialName cannot be empty')
|
||||
log.error('MaterialName cannot be empty', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'UPSERT',
|
||||
requestId: getRequestId()
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -182,7 +207,6 @@ export class MaterialsTypeToBeDeletedDAO {
|
||||
const isSqlServer = dbService.type === 'sqlserver'
|
||||
|
||||
if (isSqlServer) {
|
||||
// SQL Server MERGE statement
|
||||
const sqlString = `
|
||||
MERGE ${tableName} AS target
|
||||
USING (VALUES (@p0, @p1)) AS source (MaterialName, ManagerName)
|
||||
@@ -191,21 +215,29 @@ export class MaterialsTypeToBeDeletedDAO {
|
||||
WHEN NOT MATCHED THEN INSERT (MaterialName, ManagerName) VALUES (source.MaterialName, source.ManagerName);
|
||||
`
|
||||
|
||||
await dbService.query(sqlString, [name, manager])
|
||||
await trackDuration(async () => await dbService.query(sqlString, [name, manager]), {
|
||||
operationName: 'MaterialsTypeToBeDeletedDAO.upsertMaterial',
|
||||
context: { tableName, operationType: 'MERGE' }
|
||||
})
|
||||
} else {
|
||||
// MySQL ON DUPLICATE KEY UPDATE
|
||||
const sqlString = `
|
||||
INSERT INTO ${tableName} (MaterialName, ManagerName)
|
||||
VALUES (?, ?)
|
||||
ON DUPLICATE KEY UPDATE ManagerName = VALUES(ManagerName)
|
||||
`
|
||||
|
||||
await dbService.query(sqlString, [name, manager])
|
||||
await trackDuration(async () => await dbService.query(sqlString, [name, manager]), {
|
||||
operationName: 'MaterialsTypeToBeDeletedDAO.upsertMaterial',
|
||||
context: { tableName, operationType: 'INSERT' }
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
log.error('Upsert material error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'UPSERT',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return false
|
||||
@@ -247,10 +279,16 @@ export class MaterialsTypeToBeDeletedDAO {
|
||||
params = [name]
|
||||
}
|
||||
|
||||
const result = await dbService.query(sqlString, params)
|
||||
return result.rowCount > 0
|
||||
const result = await trackDuration(async () => await dbService.query(sqlString, params), {
|
||||
operationName: 'MaterialsTypeToBeDeletedDAO.deleteMaterial',
|
||||
context: { tableName, operationType: 'DELETE' }
|
||||
})
|
||||
return result.result.rowCount > 0
|
||||
} catch (error) {
|
||||
log.error('Delete material error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'DELETE',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return false
|
||||
@@ -284,29 +322,46 @@ export class MaterialsTypeToBeDeletedDAO {
|
||||
SET MaterialName = @p0, ManagerName = @p1
|
||||
WHERE MaterialName = @p2 AND ManagerName = @p3
|
||||
`
|
||||
const result = await dbService.query(sqlString, [
|
||||
newName.trim(),
|
||||
newManager.trim(),
|
||||
oldName.trim(),
|
||||
oldManager.trim()
|
||||
])
|
||||
return result.rowCount > 0
|
||||
const result = await trackDuration(
|
||||
async () =>
|
||||
await dbService.query(sqlString, [
|
||||
newName.trim(),
|
||||
newManager.trim(),
|
||||
oldName.trim(),
|
||||
oldManager.trim()
|
||||
]),
|
||||
{
|
||||
operationName: 'MaterialsTypeToBeDeletedDAO.updateMaterial',
|
||||
context: { tableName, operationType: 'UPDATE' }
|
||||
}
|
||||
)
|
||||
return result.result.rowCount > 0
|
||||
} else {
|
||||
const sqlString = `
|
||||
UPDATE ${tableName}
|
||||
SET MaterialName = ?, ManagerName = ?
|
||||
WHERE MaterialName = ? AND ManagerName = ?
|
||||
`
|
||||
const result = await dbService.query(sqlString, [
|
||||
newName.trim(),
|
||||
newManager.trim(),
|
||||
oldName.trim(),
|
||||
oldManager.trim()
|
||||
])
|
||||
return result.rowCount > 0
|
||||
const result = await trackDuration(
|
||||
async () =>
|
||||
await dbService.query(sqlString, [
|
||||
newName.trim(),
|
||||
newManager.trim(),
|
||||
oldName.trim(),
|
||||
oldManager.trim()
|
||||
]),
|
||||
{
|
||||
operationName: 'MaterialsTypeToBeDeletedDAO.updateMaterial',
|
||||
context: { tableName, operationType: 'UPDATE' }
|
||||
}
|
||||
)
|
||||
return result.result.rowCount > 0
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Update material error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'UPDATE',
|
||||
requestId: getRequestId(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return false
|
||||
@@ -323,9 +378,24 @@ export class MaterialsTypeToBeDeletedDAO {
|
||||
async upsertBatch(
|
||||
request: MaterialTypeBatchRequest
|
||||
): Promise<{ total: number; success: number; failed: number }> {
|
||||
const batchId = getRequestId() || `batch-${Date.now()}`
|
||||
const stats = { total: 0, success: 0, failed: 0 }
|
||||
|
||||
try {
|
||||
const tableName = this.getTableName()
|
||||
const totalOperations =
|
||||
request.toInsert.length + request.toUpdate.length + request.toDelete.length
|
||||
|
||||
log.info('Batch upsert started', {
|
||||
tableName,
|
||||
operationType: 'BATCH',
|
||||
requestId: batchId,
|
||||
totalOperations,
|
||||
inserts: request.toInsert.length,
|
||||
updates: request.toUpdate.length,
|
||||
deletes: request.toDelete.length
|
||||
})
|
||||
|
||||
// Process inserts
|
||||
for (const record of request.toInsert) {
|
||||
stats.total++
|
||||
@@ -355,9 +425,24 @@ export class MaterialsTypeToBeDeletedDAO {
|
||||
else stats.failed++
|
||||
}
|
||||
|
||||
log.info('Batch upsert completed', {
|
||||
tableName,
|
||||
operationType: 'BATCH',
|
||||
requestId: batchId,
|
||||
success: stats.success,
|
||||
failed: stats.failed,
|
||||
total: stats.total
|
||||
})
|
||||
|
||||
return stats
|
||||
} catch (error) {
|
||||
log.error('Batch upsert error', {
|
||||
tableName: this.getTableName(),
|
||||
operationType: 'BATCH',
|
||||
requestId: batchId,
|
||||
total: stats.total,
|
||||
success: stats.success,
|
||||
failed: stats.failed,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
return stats
|
||||
|
||||
Reference in New Issue
Block a user