candidates.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. const db = require('../database/db');
  2. const { successResponse, errorResponse } = require('../utils/apiResponse');
  3. class CandidateController {
  4. static async getAllCandidates(req, res) {
  5. try {
  6. const [rows] = await db.query(`
  7. SELECT
  8. c.id,
  9. c.name,
  10. c.job_id as jobId,
  11. c.job_title as jobTitle,
  12. c.match_score as matchScore,
  13. c.status,
  14. c.highlights,
  15. c.concerns,
  16. c.summary,
  17. c.resume_text as resumeText,
  18. c.education,
  19. c.experience,
  20. c.skills,
  21. c.submitted_at as submittedAt,
  22. c.reviewed_at as reviewedAt,
  23. j.title as jobTitle
  24. FROM candidates c
  25. LEFT JOIN jobs j ON c.job_id = j.id
  26. `);
  27. const candidates = rows.map(row => ({
  28. ...row,
  29. highlights: row.highlights ? JSON.parse(row.highlights) : [],
  30. concerns: row.concerns ? JSON.parse(row.concerns) : [],
  31. skills: row.skills ? JSON.parse(row.skills) : [],
  32. submittedAt: row.submittedAt,
  33. reviewedAt: row.reviewedAt || null
  34. }));
  35. successResponse(res, candidates);
  36. } catch (err) {
  37. errorResponse(res, err);
  38. }
  39. }
  40. static async getCandidatesByJob(req, res) {
  41. try {
  42. const [rows] = await db.query(`
  43. SELECT
  44. c.id,
  45. c.name,
  46. c.job_id as jobId,
  47. c.job_title as jobTitle,
  48. c.match_score as matchScore,
  49. c.status,
  50. c.highlights,
  51. c.concerns,
  52. c.summary,
  53. c.resume_text as resumeText,
  54. c.education,
  55. c.experience,
  56. c.skills,
  57. c.submitted_at as submittedAt,
  58. c.reviewed_at as reviewedAt
  59. FROM candidates c
  60. WHERE c.job_id = ?
  61. `, [req.params.jobId]);
  62. const candidates = rows.map(row => ({
  63. ...row,
  64. highlights: row.highlights ? JSON.parse(row.highlights) : [],
  65. concerns: row.concerns ? JSON.parse(row.concerns) : [],
  66. skills: row.skills ? JSON.parse(row.skills) : [],
  67. submittedAt: row.submittedAt,
  68. reviewedAt: row.reviewedAt || null
  69. }));
  70. successResponse(res, candidates);
  71. } catch (err) {
  72. errorResponse(res, err);
  73. }
  74. }
  75. static async getCandidate(req, res) {
  76. try {
  77. const [rows] = await db.query(`
  78. SELECT
  79. c.id,
  80. c.name,
  81. c.job_id as jobId,
  82. c.job_title as jobTitle,
  83. c.match_score as matchScore,
  84. c.status,
  85. c.highlights,
  86. c.concerns,
  87. c.summary,
  88. c.resume_text as resumeText,
  89. c.education,
  90. c.experience,
  91. c.skills,
  92. c.submitted_at as submittedAt,
  93. c.reviewed_at as reviewedAt,
  94. j.title as jobTitle
  95. FROM candidates c
  96. LEFT JOIN jobs j ON c.job_id = j.id
  97. WHERE c.id = ?
  98. `, [req.params.id]);
  99. if (rows.length === 0) {
  100. return errorResponse(res, 'Candidate not found', 404);
  101. }
  102. const candidate = {
  103. ...rows[0],
  104. highlights: rows[0].highlights ? JSON.parse(rows[0].highlights) : [],
  105. concerns: rows[0].concerns ? JSON.parse(rows[0].concerns) : [],
  106. skills: rows[0].skills ? JSON.parse(rows[0].skills) : [],
  107. submittedAt: rows[0].submittedAt,
  108. reviewedAt: rows[0].reviewedAt || null
  109. };
  110. successResponse(res, candidate);
  111. } catch (err) {
  112. errorResponse(res, err);
  113. }
  114. }
  115. static async createCandidate(req, res) {
  116. try {
  117. const {
  118. name,
  119. jobId,
  120. jobTitle,
  121. matchScore,
  122. status = 'pending',
  123. highlights = [],
  124. concerns = [],
  125. summary = '',
  126. resumeText,
  127. education,
  128. experience,
  129. skills = []
  130. } = req.body;
  131. const [result] = await db.query(`
  132. INSERT INTO candidates (
  133. id,
  134. name,
  135. job_id,
  136. job_title,
  137. match_score,
  138. status,
  139. highlights,
  140. concerns,
  141. summary,
  142. resume_text,
  143. education,
  144. experience,
  145. skills,
  146. submitted_at
  147. ) VALUES (UUID(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
  148. `, [
  149. name,
  150. jobId,
  151. jobTitle,
  152. matchScore,
  153. status,
  154. JSON.stringify(highlights),
  155. JSON.stringify(concerns),
  156. summary,
  157. resumeText,
  158. education,
  159. experience,
  160. JSON.stringify(skills)
  161. ]);
  162. const [newCandidate] = await db.query(`
  163. SELECT * FROM candidates WHERE id = ?
  164. `, [result.insertId]);
  165. successResponse(res, {
  166. ...newCandidate[0],
  167. highlights: newCandidate[0].highlights ? JSON.parse(newCandidate[0].highlights) : [],
  168. concerns: newCandidate[0].concerns ? JSON.parse(newCandidate[0].concerns) : [],
  169. skills: newCandidate[0].skills ? JSON.parse(newCandidate[0].skills) : [],
  170. submittedAt: newCandidate[0].submitted_at,
  171. reviewedAt: newCandidate[0].reviewed_at || null
  172. }, 201);
  173. } catch (err) {
  174. errorResponse(res, err);
  175. }
  176. }
  177. static async updateCandidate(req, res) {
  178. try {
  179. const {
  180. name,
  181. jobId,
  182. jobTitle,
  183. matchScore,
  184. status,
  185. highlights,
  186. concerns,
  187. summary,
  188. resumeText,
  189. education,
  190. experience,
  191. skills
  192. } = req.body;
  193. const updates = [];
  194. const params = [];
  195. if (name) { updates.push('name = ?'); params.push(name); }
  196. if (jobId) { updates.push('job_id = ?'); params.push(jobId); }
  197. if (jobTitle) { updates.push('job_title = ?'); params.push(jobTitle); }
  198. if (matchScore) { updates.push('match_score = ?'); params.push(matchScore); }
  199. if (status) { updates.push('status = ?'); params.push(status); }
  200. if (highlights) { updates.push('highlights = ?'); params.push(JSON.stringify(highlights)); }
  201. if (concerns) { updates.push('concerns = ?'); params.push(JSON.stringify(concerns)); }
  202. if (summary) { updates.push('summary = ?'); params.push(summary); }
  203. if (resumeText) { updates.push('resume_text = ?'); params.push(resumeText); }
  204. if (education) { updates.push('education = ?'); params.push(education); }
  205. if (experience) { updates.push('experience = ?'); params.push(experience); }
  206. if (skills) { updates.push('skills = ?'); params.push(JSON.stringify(skills)); }
  207. if (updates.length === 0) {
  208. return errorResponse(res, 'No valid fields to update', 400);
  209. }
  210. params.push(req.params.id);
  211. await db.query(`
  212. UPDATE candidates
  213. SET ${updates.join(', ')}
  214. WHERE id = ?
  215. `, params);
  216. const [updatedCandidate] = await db.query(`
  217. SELECT * FROM candidates WHERE id = ?
  218. `, [req.params.id]);
  219. if (updatedCandidate.length === 0) {
  220. return errorResponse(res, 'Candidate not found', 404);
  221. }
  222. successResponse(res, {
  223. ...updatedCandidate[0],
  224. highlights: updatedCandidate[0].highlights ? JSON.parse(updatedCandidate[0].highlights) : [],
  225. concerns: updatedCandidate[0].concerns ? JSON.parse(updatedCandidate[0].concerns) : [],
  226. skills: updatedCandidate[0].skills ? JSON.parse(updatedCandidate[0].skills) : [],
  227. submittedAt: updatedCandidate[0].submitted_at,
  228. reviewedAt: updatedCandidate[0].reviewed_at || null
  229. });
  230. } catch (err) {
  231. errorResponse(res, err);
  232. }
  233. }
  234. static async updateCandidateStatus(req, res) {
  235. try {
  236. const { status } = req.body;
  237. console.log(`准备更新候选人状态: ID=${req.params.id}, 新状态=${status}`); // 调试
  238. if (!['pending', 'passed', 'rejected', 'interviewed'].includes(status)) {
  239. return errorResponse(res, 'Invalid status value', 400);
  240. }
  241. // 获取当前状态用于记录历史
  242. const [currentCandidate] = await db.query(`
  243. SELECT status FROM candidates WHERE id = ?
  244. `, [req.params.id]);
  245. if (currentCandidate.length === 0) {
  246. console.log('未找到候选人:', req.params.id); // 调试
  247. return errorResponse(res, 'Candidate not found', 404);
  248. }
  249. const oldStatus = currentCandidate[0].status;
  250. console.log(`当前状态: ${oldStatus}, 将更新为: ${status}`); // 调试
  251. // 执行更新
  252. const [result] = await db.query(`
  253. UPDATE candidates
  254. SET status = ?, reviewed_at = NOW()
  255. WHERE id = ?
  256. `, [status, req.params.id]);
  257. console.log('更新结果:', result); // 调试
  258. console.log('影响行数:', result.affectedRows); // 调试
  259. if (result.affectedRows === 0) {
  260. console.log('更新未影响任何行'); // 调试
  261. return errorResponse(res, 'No rows updated', 404);
  262. }
  263. // 记录状态变更历史
  264. await db.query(`
  265. INSERT INTO candidate_status_history (
  266. candidate_id, old_status, new_status, changed_by, change_reason
  267. ) VALUES (?, ?, ?, ?, ?)
  268. `, [
  269. req.params.id,
  270. oldStatus,
  271. status,
  272. req.user?.username || 'system',
  273. req.body.reason || 'Status updated'
  274. ]);
  275. // 获取更新后的数据
  276. const [updatedCandidate] = await db.query(`
  277. SELECT * FROM candidates WHERE id = ?
  278. `, [req.params.id]);
  279. console.log('更新后的候选人:', updatedCandidate[0]); // 调试
  280. successResponse(res, {
  281. ...updatedCandidate[0],
  282. highlights: JSON.parse(updatedCandidate[0].highlights || '[]'),
  283. concerns: JSON.parse(updatedCandidate[0].concerns || '[]'),
  284. skills: JSON.parse(updatedCandidate[0].skills || '[]')
  285. });
  286. } catch (err) {
  287. console.error('更新状态出错:', err); // 调试
  288. errorResponse(res, err);
  289. }
  290. }
  291. static async batchScreenCandidates(req, res) {
  292. try {
  293. const { candidateIds } = req.body;
  294. if (!Array.isArray(candidateIds) || candidateIds.length === 0) {
  295. return errorResponse(res, 'Invalid candidate IDs', 400);
  296. }
  297. // 这里可以添加实际的批量筛选逻辑
  298. // 例如调用AI服务进行批量评分等
  299. // 模拟批量更新状态
  300. await db.query(`
  301. UPDATE candidates
  302. SET status = 'passed', reviewed_at = NOW()
  303. WHERE id IN (?)
  304. `, [candidateIds]);
  305. // 获取更新后的候选人列表
  306. const [updatedCandidates] = await db.query(`
  307. SELECT * FROM candidates WHERE id IN (?)
  308. `, [candidateIds]);
  309. const formattedCandidates = updatedCandidates.map(candidate => ({
  310. ...candidate,
  311. highlights: candidate.highlights ? JSON.parse(candidate.highlights) : [],
  312. concerns: candidate.concerns ? JSON.parse(candidate.concerns) : [],
  313. skills: candidate.skills ? JSON.parse(candidate.skills) : [],
  314. submittedAt: candidate.submitted_at,
  315. reviewedAt: candidate.reviewed_at || null
  316. }));
  317. successResponse(res, formattedCandidates);
  318. } catch (err) {
  319. errorResponse(res, err);
  320. }
  321. }
  322. static async deleteCandidate(req, res) {
  323. try {
  324. const [result] = await db.query(`
  325. DELETE FROM candidates WHERE id = ?
  326. `, [req.params.id]);
  327. if (result.affectedRows === 0) {
  328. return errorResponse(res, 'Candidate not found', 404);
  329. }
  330. successResponse(res, { id: req.params.id, deleted: true });
  331. } catch (err) {
  332. errorResponse(res, err);
  333. }
  334. }
  335. static async getCandidateSkills(req, res) {
  336. try {
  337. const [rows] = await db.query(`
  338. SELECT
  339. s.id,
  340. s.name,
  341. s.category,
  342. cs.proficiency
  343. FROM candidate_skills cs
  344. JOIN skills s ON cs.skill_id = s.id
  345. WHERE cs.candidate_id = ?
  346. `, [req.params.id]);
  347. successResponse(res, rows);
  348. } catch (err) {
  349. errorResponse(res, err);
  350. }
  351. }
  352. static async getCandidateStatusHistory(req, res) {
  353. try {
  354. const [rows] = await db.query(`
  355. SELECT
  356. id,
  357. old_status as oldStatus,
  358. new_status as newStatus,
  359. changed_by as changedBy,
  360. change_reason as changeReason,
  361. change_time as changeTime
  362. FROM candidate_status_history
  363. WHERE candidate_id = ?
  364. ORDER BY change_time DESC
  365. `, [req.params.id]);
  366. successResponse(res, rows);
  367. } catch (err) {
  368. errorResponse(res, err);
  369. }
  370. }
  371. }
  372. module.exports = CandidateController;