feat(考勤管理): 添加手动打卡记录功能和加班计算
- 在考勤记录页面添加手动打卡表单 - 实现后端API处理手动打卡记录 - 新增工人仪表盘显示姓名 - 在考勤报表中添加加班工资计算功能
This commit is contained in:
+66
-6
@@ -37,8 +37,6 @@ async function startServer() {
|
||||
app.use(cors())
|
||||
app.use(express.json())
|
||||
|
||||
// Helper functions can be placed here if any are needed in the future
|
||||
|
||||
// --- API Endpoints ---
|
||||
|
||||
// Auth Endpoint
|
||||
@@ -93,6 +91,25 @@ async function startServer() {
|
||||
}
|
||||
})
|
||||
|
||||
// Fetch worker details endpoint
|
||||
app.get('/api/workers/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const [rows] = await db.execute(
|
||||
'SELECT full_name FROM workers WHERE id = ? AND role = "worker"',
|
||||
[id],
|
||||
)
|
||||
if (rows.length > 0) {
|
||||
res.json({ full_name: rows[0].full_name })
|
||||
} else {
|
||||
res.status(404).json({ message: 'Worker not found.' })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Get worker details error:', error)
|
||||
res.status(500).json({ message: 'Database error fetching worker details.' })
|
||||
}
|
||||
})
|
||||
|
||||
// Worker Status Endpoint
|
||||
app.get('/api/worker/status/:userId', async (req, res) => {
|
||||
try {
|
||||
@@ -104,7 +121,7 @@ async function startServer() {
|
||||
if (rows.length > 0) {
|
||||
res.json({ eventType: rows[0].event_type })
|
||||
} else {
|
||||
res.json({ eventType: 'clock_out' })
|
||||
res.json({ eventType: 'clock_out' }) // Default to clocked out
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Worker status error:', error)
|
||||
@@ -116,8 +133,9 @@ async function startServer() {
|
||||
app.get('/api/worker/clock-history/:userId', async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.params
|
||||
// MODIFIED: Use LEFT JOIN and COALESCE to handle manual entries
|
||||
const [rows] = await db.execute(
|
||||
`SELECT cr.id, cr.event_type, cr.timestamp, qc.name as qrCodeUsedName, cr.latitude, cr.longitude FROM clock_records cr JOIN qr_codes qc ON cr.qr_code_id = qc.id WHERE cr.worker_id = ? ORDER BY cr.timestamp DESC`,
|
||||
`SELECT cr.id, cr.event_type, cr.timestamp, COALESCE(qc.name, 'Manual Entry') as qrCodeUsedName, cr.latitude, cr.longitude, cr.notes FROM clock_records cr LEFT JOIN qr_codes qc ON cr.qr_code_id = qc.id WHERE cr.worker_id = ? ORDER BY cr.timestamp DESC`,
|
||||
[userId],
|
||||
)
|
||||
res.json(rows)
|
||||
@@ -186,6 +204,42 @@ async function startServer() {
|
||||
}
|
||||
})
|
||||
|
||||
// --- NEW --- Manager: POST (Add Manual Attendance Record)
|
||||
// Note: For this to work, you may need to alter your database table:
|
||||
// ALTER TABLE clock_records ADD COLUMN notes TEXT;
|
||||
app.post('/api/managers/add-record', async (req, res) => {
|
||||
try {
|
||||
const { workerId, eventType, timestamp, notes } = req.body
|
||||
|
||||
if (!workerId || !eventType || !timestamp) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ message: 'Worker ID, event type, and timestamp are required.' })
|
||||
}
|
||||
|
||||
// Check last event to prevent adding a duplicate event type
|
||||
const [lastEventRows] = await db.execute(
|
||||
'SELECT event_type FROM clock_records WHERE worker_id = ? ORDER BY timestamp DESC LIMIT 1',
|
||||
[workerId],
|
||||
)
|
||||
|
||||
if (lastEventRows.length > 0 && lastEventRows[0].event_type === eventType) {
|
||||
const status = eventType === 'clock_in' ? 'in' : 'out'
|
||||
return res.status(409).json({ message: `Worker is already clocked ${status}.` })
|
||||
}
|
||||
|
||||
await db.execute(
|
||||
'INSERT INTO clock_records (worker_id, event_type, timestamp, notes, qr_code_id, latitude, longitude) VALUES (?, ?, ?, ?, NULL, NULL, NULL)',
|
||||
[workerId, eventType, timestamp, notes],
|
||||
)
|
||||
|
||||
res.status(201).json({ message: 'Manual record added successfully.' })
|
||||
} catch (error) {
|
||||
console.error('Add manual record error:', error)
|
||||
res.status(500).json({ message: 'Database error adding manual record.' })
|
||||
}
|
||||
})
|
||||
|
||||
// Manager: GET Attendance Records
|
||||
app.get('/api/managers/attendance-records', async (req, res) => {
|
||||
try {
|
||||
@@ -196,7 +250,10 @@ async function startServer() {
|
||||
const idsArray = workerIds.split(',').map(Number)
|
||||
if (idsArray.length === 0) return res.json([])
|
||||
const placeholders = idsArray.map(() => '?').join(',')
|
||||
let query = `SELECT cr.id, w.full_name, cr.event_type, cr.timestamp, qc.name as qrCodeUsedName, cr.latitude, cr.longitude FROM clock_records cr JOIN qr_codes qc ON cr.qr_code_id = qc.id JOIN workers w ON cr.worker_id = w.id WHERE cr.worker_id IN (${placeholders})`
|
||||
|
||||
// MODIFIED: Use LEFT JOIN and COALESCE to handle manual entries, and select `notes`
|
||||
let query = `SELECT cr.id, w.full_name, cr.event_type, cr.timestamp, COALESCE(qc.name, 'Manual Entry') as qrCodeUsedName, cr.latitude, cr.longitude, cr.notes FROM clock_records cr LEFT JOIN qr_codes qc ON cr.qr_code_id = qc.id JOIN workers w ON cr.worker_id = w.id WHERE cr.worker_id IN (${placeholders})`
|
||||
|
||||
const params = [...idsArray]
|
||||
if (startDate && endDate) {
|
||||
const endOfDay = new Date(endDate)
|
||||
@@ -205,10 +262,13 @@ async function startServer() {
|
||||
params.push(startDate, endOfDay)
|
||||
}
|
||||
query += ' ORDER BY w.full_name, cr.timestamp DESC'
|
||||
|
||||
const [rows] = await db.execute(query, params)
|
||||
|
||||
if (format === 'csv') {
|
||||
// MODIFIED: Add 'notes' to CSV export
|
||||
const json2csvParser = new Parser({
|
||||
fields: ['full_name', 'event_type', 'timestamp', 'qrCodeUsedName'],
|
||||
fields: ['full_name', 'event_type', 'timestamp', 'qrCodeUsedName', 'notes'],
|
||||
})
|
||||
const csv = json2csvParser.parse(rows)
|
||||
res.header('Content-Type', 'text/csv')
|
||||
|
||||
Reference in New Issue
Block a user