feat(考勤管理): 添加手动打卡记录功能和加班计算

- 在考勤记录页面添加手动打卡表单
- 实现后端API处理手动打卡记录
- 新增工人仪表盘显示姓名
- 在考勤报表中添加加班工资计算功能
This commit is contained in:
sudomarcma
2025-06-16 17:17:24 +08:00
parent a9759ac2c4
commit 2b2947c0ce
5 changed files with 692 additions and 55 deletions
+66 -6
View File
@@ -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')