diff --git a/sql/message.sql b/sql/message.sql index 0423092..a45f1c5 100644 --- a/sql/message.sql +++ b/sql/message.sql @@ -2,7 +2,7 @@ CREATE TABLE `message` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `patient_id` INT UNSIGNED NOT NULL COMMENT '患者ID', - `type` TINYINT NOT NULL DEFAULT 0 COMMENT '类型:1=审核通过 2=审核拒绝 3=回寄信息', + `type` TINYINT NOT NULL DEFAULT 0 COMMENT '类型:1=审核通过 2=审核拒绝 3=回寄信息 4=送检修改申请审核结果', `title` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '标题', `content` TEXT COMMENT '详细内容', `reason` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '驳回原因', diff --git a/sql/sample_edit_request.sql b/sql/sample_edit_request.sql new file mode 100644 index 0000000..1f99aa3 --- /dev/null +++ b/sql/sample_edit_request.sql @@ -0,0 +1,24 @@ +-- 送检信息修改申请与送检流转日志 +ALTER TABLE `patient` + MODIFY COLUMN `sample_info_status` tinyint(1) DEFAULT 0 COMMENT '送检信息状态: 0可修改 1已生效 2已寄回 3修改申请待审核', + ADD COLUMN `sample_edit_reason` varchar(500) DEFAULT '' COMMENT '送检信息修改申请原因' AFTER `return_time`, + ADD COLUMN `sample_edit_reject_reason` varchar(500) DEFAULT '' COMMENT '送检信息修改申请驳回原因' AFTER `sample_edit_reason`, + ADD COLUMN `sample_edit_apply_time` datetime DEFAULT NULL COMMENT '送检信息修改申请时间' AFTER `sample_edit_reject_reason`, + ADD COLUMN `sample_edit_audit_time` datetime DEFAULT NULL COMMENT '送检信息修改申请审核时间' AFTER `sample_edit_apply_time`; + +CREATE TABLE IF NOT EXISTS `patient_sample_log` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `patient_id` int unsigned NOT NULL COMMENT '患者ID', + `action` varchar(50) NOT NULL DEFAULT '' COMMENT '操作类型', + `title` varchar(100) NOT NULL DEFAULT '' COMMENT '日志标题', + `content` text COMMENT '日志内容', + `reason` varchar(500) NOT NULL DEFAULT '' COMMENT '原因', + `operator_type` varchar(20) NOT NULL DEFAULT '' COMMENT '操作人类型: patient/admin/system', + `operator_id` int unsigned NOT NULL DEFAULT 0 COMMENT '操作人ID', + `operator_name` varchar(100) NOT NULL DEFAULT '' COMMENT '操作人名称', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_patient_id` (`patient_id`), + KEY `idx_action` (`action`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='患者送检流转日志'; diff --git a/sql/subscribe.sql b/sql/subscribe.sql index f8376e2..6890ac0 100644 --- a/sql/subscribe.sql +++ b/sql/subscribe.sql @@ -12,4 +12,6 @@ ALTER TABLE `wechat_user` ADD COLUMN `mp_env_version` varchar(20) DEFAULT 'release' COMMENT '小程序版本: develop/trial/release' AFTER `patient_id`; -- 更新 pap_mini_cytx 应用的模板配置 -UPDATE `sys_wechat_app` SET `subscribe_templates` = '{"audit_result":"OcMLIGS8pMnpGzZmPkdMEOmmWmvvvU_nN-kmiK5sUGE"}' WHERE `remark` = 'pap_mini_cytx'; +UPDATE `sys_wechat_app` +SET `subscribe_templates` = '{"audit_result":"OcMLIGS8pMnpGzZmPkdMEOmmWmvvvU_nN-kmiK5sUGE","sample_edit_audit":"OcMLIGS8pMnpGzZmPkdMELDgFefqOeQ-1jsit8Ldw2w"}' +WHERE `remark` = 'pap_mini_cytx'; diff --git a/src/config/router.js b/src/config/router.js index d9aaceb..cc22f24 100644 --- a/src/config/router.js +++ b/src/config/router.js @@ -54,6 +54,9 @@ module.exports = [ ['/admin/patient/saveSampleReceiverConfig', 'admin/patient/saveSampleReceiverConfig', 'post'], ['/admin/patient/resetSampleInfo', 'admin/patient/resetSampleInfo', 'post'], ['/admin/patient/saveReturnTrackingNo', 'admin/patient/saveReturnTrackingNo', 'post'], + ['/admin/patient/approveSampleEdit', 'admin/patient/approveSampleEdit', 'post'], + ['/admin/patient/rejectSampleEdit', 'admin/patient/rejectSampleEdit', 'post'], + ['/admin/patient/editSampleInfo', 'admin/patient/editSampleInfo', 'post'], ['/admin/patient/export', 'admin/patient/export'], // 公共接口 ['/common/regions', 'common/regions'], @@ -83,6 +86,7 @@ module.exports = [ ['/api/mp/subscribeConfig', 'mp/subscribeConfig'], ['/api/mp/sampleInfo', 'mp/sampleInfo'], ['/api/mp/saveSampleInfo', 'mp/saveSampleInfo', 'post'], + ['/api/mp/applySampleInfoEdit', 'mp/applySampleInfoEdit', 'post'], ['/api/mp/regenerateSign', 'mp/regenerateSign'], ['/api/mp/regenerateSignByUrl', 'mp/regenerateSignByUrl', 'post'], diff --git a/src/controller/admin/patient.js b/src/controller/admin/patient.js index d143f90..627a80e 100644 --- a/src/controller/admin/patient.js +++ b/src/controller/admin/patient.js @@ -1,13 +1,119 @@ const Base = require('../base.js'); const dayjs = require('dayjs'); +const APP_REMARK = 'pap_mini_cytx'; const AUDIT_APPROVED_CONTENT = '您提交的肠愈同行患者关爱项目资料已审核通过,请于7天内在小程序-个人中心-送检信息中提交相关信息。'; +const SAMPLE_EDIT_AUDIT_TEMPLATE_ID = 'OcMLIGS8pMnpGzZmPkdMELDgFefqOeQ-1jsit8Ldw2w'; module.exports = class extends Base { _canEditPatient() { return this.isSuperAdmin || (this.userPermissions || []).includes('patient:edit'); } + _adminOperator() { + const user = this.adminUser || {}; + return { + operator_id: user.id || 0, + operator_name: user.nickname || user.username || '' + }; + } + + _parseArrayValue(value) { + if (Array.isArray(value)) return value; + if (!value) return []; + try { return JSON.parse(value || '[]') || []; } catch (e) { return []; } + } + + _hasSampleInfo(patient = {}) { + const sampleTypes = this._parseArrayValue(patient.sample_types); + const samplePhotos = this._parseArrayValue(patient.sample_photos); + return !!( + sampleTypes.length || + samplePhotos.length || + patient.report_email || + patient.sample_tracking_no || + patient.return_name || + patient.return_phone || + patient.return_address + ); + } + + _sampleInfoStatusText(patient = {}) { + const status = Number(patient.sample_info_status) || 0; + if (status === 3) return '修改申请待审核'; + if (status === 2) return '已寄回'; + if (status === 1) return '已生效'; + return this._hasSampleInfo(patient) ? '可修改' : '未提交'; + } + + _sampleInfoStatusType(patient = {}) { + const status = Number(patient.sample_info_status) || 0; + if (status === 3) return 'warning'; + if (status === 2) return 'primary'; + if (status === 1) return 'success'; + return this._hasSampleInfo(patient) ? 'warning' : 'info'; + } + + _buildFlowLogs(audits = [], sampleLogs = []) { + const auditLogs = audits.map(item => { + const action = item.action || ''; + let title = '资料流转'; + let content = ''; + if (action === 'submit') { + title = '提交患者资料'; + content = `${item.operator_name || '系统'} 提交了患者资料,等待审核`; + } else if (action === 'approve') { + title = '患者资料审核通过'; + content = `${item.operator_name || '系统'} 审核通过`; + } else if (action === 'reject') { + title = '患者资料审核驳回'; + content = `${item.operator_name || '系统'} 驳回了资料`; + } + return { + id: item.id, + source: 'audit', + action, + title, + content, + reason: item.reason || '', + operator_name: item.operator_name || '', + operator_type: 'admin', + create_time: item.create_time + }; + }); + const logs = sampleLogs.map(item => ({ + id: item.id, + source: 'sample', + action: item.action || '', + title: item.title || '送检信息', + content: item.content || '', + reason: item.reason || '', + operator_name: item.operator_name || '', + operator_type: item.operator_type || '', + create_time: item.create_time + })); + return auditLogs.concat(logs).sort((a, b) => { + const ta = new Date(a.create_time || 0).getTime(); + const tb = new Date(b.create_time || 0).getTime(); + if (tb !== ta) return tb - ta; + return (b.id || 0) - (a.id || 0); + }); + } + + async _addSampleLog(patientId, action, title, content, reason = '') { + const operator = this._adminOperator(); + return this.model('patient_sample_log').addLog({ + patient_id: patientId, + action, + title, + content, + reason, + operator_type: 'admin', + operator_id: operator.operator_id, + operator_name: operator.operator_name + }); + } + // 患者列表页面 async indexAction() { this.assign('currentPage', 'patient'); @@ -60,6 +166,8 @@ module.exports = class extends Base { const cName = regionMap[item.city_code] || ''; const dName = regionMap[item.district_code] || ''; item.region_name = [pName, cName, dName].filter(Boolean).join(' '); + item.sample_info_status_text = this._sampleInfoStatusText(item); + item.sample_info_status_type = this._sampleInfoStatusType(item); }); const counts = await model.getStatusCounts({ keyword, tag, startDate, endDate, province_code, city_code, district_code }); @@ -125,13 +233,21 @@ module.exports = class extends Base { patient.hospital_district_name = regionMap[patient.hospital_district_code] || ''; } - // 获取审核记录 + patient.sample_info_status_text = this._sampleInfoStatusText(patient); + patient.sample_info_status_type = this._sampleInfoStatusType(patient); + + // 获取审核记录和送检流转日志 const audits = await this.model('patient_audit') .where({ patient_id: id }) .order('id DESC') .select(); + const sampleLogs = await this.model('patient_sample_log') + .where({ patient_id: id }) + .order('id DESC') + .select(); + const flowLogs = this._buildFlowLogs(audits, sampleLogs); - return this.success({ patient, audits }); + return this.success({ patient, audits: flowLogs, flowLogs }); } async sampleReceiverConfigAction() { @@ -372,9 +488,14 @@ module.exports = class extends Base { await this.model('patient').where({ id }).update({ sample_info_status: 0, + sample_edit_reason: '', + sample_edit_reject_reason: '', + sample_edit_apply_time: null, + sample_edit_audit_time: null, update_by: this.adminUser?.id || 0 }); + await this._addSampleLog(id, 'sample_reset_edit', '重置送检信息编辑', '后台重置送检信息为可修改'); await this.log('edit', '患者管理', `重置送检信息编辑权限(ID:${id})`); return this.success(); } @@ -409,10 +530,119 @@ module.exports = class extends Base { is_read: 0 }); + await this._addSampleLog(id, 'sample_return', Number(patient.sample_info_status) === 2 ? '修改回寄物流单号' : '填写回寄物流单号', `回寄物流单号:${trackingNo}`); await this.log('edit', '患者管理', `填写回寄物流单号(ID:${id}):${trackingNo}`); return this.success(); } + async approveSampleEditAction() { + if (!this._canEditPatient()) return this.fail('暂无权限'); + const { id } = this.post(); + if (!id) return this.fail('参数错误'); + + const patient = await this.model('patient').where({ id, is_deleted: 0 }).find(); + if (think.isEmpty(patient)) return this.fail('患者不存在'); + if (Number(patient.sample_info_status) !== 3) return this.fail('当前没有待审核的送检修改申请'); + + const reason = patient.sample_edit_reason || ''; + await this.model('patient').where({ id }).update({ + sample_info_status: 0, + sample_edit_reason: '', + sample_edit_reject_reason: '', + sample_edit_apply_time: null, + sample_edit_audit_time: null, + update_by: this.adminUser?.id || 0 + }); + + await this._addSampleLog(id, 'sample_approve_edit', '送检修改申请通过', '后台通过送检信息修改申请,患者可重新编辑送检信息', reason); + await this._addSampleEditMessage(id, true); + await this._sendSampleEditSubscribeMessage(id, patient.name, '审核通过', '请重新提交送检信息'); + await this.log('edit', '患者管理', `通过送检信息修改申请(ID:${id})`); + return this.success(); + } + + async rejectSampleEditAction() { + if (!this._canEditPatient()) return this.fail('暂无权限'); + const { id, reason } = this.post(); + if (!id) return this.fail('参数错误'); + if (!reason || !reason.trim()) return this.fail('请填写驳回原因'); + + const patient = await this.model('patient').where({ id, is_deleted: 0 }).find(); + if (think.isEmpty(patient)) return this.fail('患者不存在'); + if (Number(patient.sample_info_status) !== 3) return this.fail('当前没有待审核的送检修改申请'); + + const rejectReason = reason.trim(); + const now = think.datetime(new Date()); + await this.model('patient').where({ id }).update({ + sample_info_status: 1, + sample_edit_reject_reason: rejectReason, + sample_edit_audit_time: now, + update_by: this.adminUser?.id || 0 + }); + + await this._addSampleLog(id, 'sample_reject_edit', '送检修改申请驳回', '后台驳回送检信息修改申请', rejectReason); + await this._addSampleEditMessage(id, false, rejectReason); + await this._sendSampleEditSubscribeMessage(id, patient.name, '审核拒绝', '修改申请未通过,请查看详情'); + await this.log('edit', '患者管理', `驳回送检信息修改申请(ID:${id}),原因:${rejectReason}`); + return this.success(); + } + + async editSampleInfoAction() { + if (!this._canEditPatient()) return this.fail('暂无权限'); + const data = this.post(); + const { id, sample_types, wax_return, return_name, return_phone, return_province_code, return_city_code, return_district_code, return_address, report_email, sample_tracking_no, sample_photos } = data; + if (!id) return this.fail('参数错误'); + + const patient = await this.model('patient').where({ id, is_deleted: 0 }).find(); + if (think.isEmpty(patient)) return this.fail('患者不存在'); + + const finalSampleTypes = this._parseArrayValue(sample_types); + const finalSamplePhotos = this._parseArrayValue(sample_photos); + const hasSample = finalSampleTypes.length > 0; + const needReturn = hasSample && Number(wax_return) === 1; + const finalReportEmail = hasSample ? (report_email || '').trim() : ''; + + if (needReturn) { + if (!return_name || !return_name.trim()) return this.fail('请填写回寄收件人'); + if (!return_phone || !return_phone.trim()) return this.fail('请填写回寄收件电话'); + if (!return_province_code || !return_city_code || !return_district_code) return this.fail('请选择回寄收件地址'); + if (!return_address || !return_address.trim()) return this.fail('请填写回寄详细地址'); + } + if (finalReportEmail) { + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(finalReportEmail)) return this.fail('邮箱格式不正确'); + const dup = await this.model('patient') + .where({ report_email: finalReportEmail, is_deleted: 0, id: ['!=', id] }) + .find(); + if (!think.isEmpty(dup)) return this.fail('该邮箱已被其他患者使用'); + } + + await this.model('patient').where({ id }).update({ + sample_types: JSON.stringify(finalSampleTypes), + wax_return: needReturn ? 1 : 0, + return_name: needReturn ? (return_name || '').trim() : '', + return_phone: needReturn ? (return_phone || '').trim() : '', + return_province_code: needReturn ? (return_province_code || '') : '', + return_city_code: needReturn ? (return_city_code || '') : '', + return_district_code: needReturn ? (return_district_code || '') : '', + return_address: needReturn ? (return_address || '').trim() : '', + report_email: hasSample ? finalReportEmail : '', + sample_tracking_no: hasSample ? (sample_tracking_no || '').trim() : '', + sample_photos: hasSample ? JSON.stringify(finalSamplePhotos) : '[]', + sample_info_status: 1, + return_tracking_no: '', + return_time: null, + sample_edit_reason: '', + sample_edit_reject_reason: '', + sample_edit_apply_time: null, + sample_edit_audit_time: null, + update_by: this.adminUser?.id || 0 + }); + + await this._addSampleLog(id, 'sample_admin_edit', '后台编辑送检信息', '后台直接编辑送检信息,状态已生效'); + await this.log('edit', '患者管理', `后台编辑送检信息(ID:${id})`); + return this.success(); + } + // 编辑患者 async editAction() { const data = this.post(); @@ -617,7 +847,6 @@ module.exports = class extends Base { // @private 发送审核结果订阅消息 async _sendAuditSubscribeMessage(patientId, patientName, result) { try { - const APP_REMARK = 'pap_mini_cytx'; // 查找该患者关联的微信用户 const wechatUser = await this.model('wechat_user') .where({ patient_id: patientId, app_remark: APP_REMARK, status: 1 }) @@ -654,6 +883,53 @@ module.exports = class extends Base { } } + async _addSampleEditMessage(patientId, approved, reason = '') { + await this.model('message').add({ + patient_id: patientId, + type: 4, + title: approved ? '送检信息修改申请已通过' : '送检信息修改申请未通过', + content: approved + ? '您的送检信息修改申请已通过,请前往送检信息页面重新提交。' + : '您的送检信息修改申请未通过,请查看原因。', + reason: approved ? '' : reason, + is_read: 0 + }); + } + + // @private 发送送检修改申请审核结果订阅消息 + async _sendSampleEditSubscribeMessage(patientId, patientName, result, remark) { + try { + const wechatUser = await this.model('wechat_user') + .where({ patient_id: patientId, app_remark: APP_REMARK, status: 1 }) + .find(); + if (think.isEmpty(wechatUser) || !wechatUser.open_id) return; + + const wechatService = this.service('wechat'); + const templates = await wechatService.getSubscribeTemplates(APP_REMARK); + const templateId = templates.sample_edit_audit || SAMPLE_EDIT_AUDIT_TEMPLATE_ID; + if (!templateId) return; + + const envMap = { develop: 'developer', trial: 'trial', release: 'formal' }; + const miniprogramState = envMap[wechatUser.mp_env_version] || 'formal'; + const limitThing = value => String(value || '').slice(0, 20); + await wechatService.sendSubscribeMessage({ + remark: APP_REMARK, + openid: wechatUser.open_id, + templateId, + page: 'pages/profile/profile', + miniprogramState, + data: { + thing4: { value: limitThing(remark || '送检信息修改申请已处理') }, + phrase1: { value: result }, + thing16: { value: limitThing(patientName || '患者') }, + time13: { value: dayjs().format('YYYY-MM-DD HH:mm:ss') } + } + }); + } catch (error) { + think.logger.error('[Subscribe] 送检修改申请审核消息发送失败:', error); + } + } + // @private 发送审核结果短信通知 async _sendAuditSms(phone, type, reason) { if (!phone) return; diff --git a/src/controller/mp.js b/src/controller/mp.js index a661dcd..3a4304e 100644 --- a/src/controller/mp.js +++ b/src/controller/mp.js @@ -7,8 +7,29 @@ const cosConfig = require('../config/cos.js'); const APP_REMARK = 'pap_mini_cytx'; const AUDIT_APPROVED_CONTENT = '您提交的肠愈同行患者关爱项目资料已审核通过,请于7天内在小程序-个人中心-送检信息中提交相关信息。'; +const SAMPLE_EDIT_AUDIT_TEMPLATE_ID = 'OcMLIGS8pMnpGzZmPkdMELDgFefqOeQ-1jsit8Ldw2w'; module.exports = class extends Base { + _parseArrayValue(value) { + if (Array.isArray(value)) return value; + if (!value) return []; + try { return JSON.parse(value || '[]') || []; } catch (e) { return []; } + } + + _hasSampleInfo(patient = {}) { + const sampleTypes = this._parseArrayValue(patient.sample_types); + const samplePhotos = this._parseArrayValue(patient.sample_photos); + return !!( + sampleTypes.length || + samplePhotos.length || + patient.report_email || + patient.sample_tracking_no || + patient.return_name || + patient.return_phone || + patient.return_address + ); + } + // POST /api/mp/login async loginAction() { const code = this.post('code'); @@ -672,10 +693,11 @@ module.exports = class extends Base { try { const wechatService = this.service('wechat'); const templates = await wechatService.getSubscribeTemplates(APP_REMARK); + if (!templates.sample_edit_audit) templates.sample_edit_audit = SAMPLE_EDIT_AUDIT_TEMPLATE_ID; return this.json({ code: 0, data: templates }); } catch (error) { think.logger.error('subscribeConfig error:', error); - return this.json({ code: 0, data: {} }); + return this.json({ code: 0, data: { sample_edit_audit: SAMPLE_EDIT_AUDIT_TEMPLATE_ID } }); } } @@ -729,6 +751,10 @@ module.exports = class extends Base { sample_info_status: Number(patient.sample_info_status) || 0, return_tracking_no: patient.return_tracking_no || '', return_time: patient.return_time || '', + sample_edit_reason: patient.sample_edit_reason || '', + sample_edit_reject_reason: patient.sample_edit_reject_reason || '', + sample_edit_apply_time: patient.sample_edit_apply_time || '', + sample_edit_audit_time: patient.sample_edit_audit_time || '', sample_receiver_info: sampleReceiverInfo }}); } catch (error) { @@ -768,16 +794,30 @@ module.exports = class extends Base { } if (sampleInfoStatus === 1) { const contactPhone = sampleReceiverInfo.contact_phone || ''; + const msg = '送检信息已生效,如需修改请点击页面的【申请修改送检信息】按钮申请,通过后可重新提交送检信息。' + + (contactPhone ? `详情咨询${contactPhone}。` : ''); return this.json({ code: 1010, data: { sample_info_status: 1, sample_receiver_info: sampleReceiverInfo }, - msg: contactPhone - ? `送检信息已生效,如需修改请联系平台【${contactPhone}】重置修改权限。` - : '送检信息已生效,如需修改请联系平台重置修改权限。' + msg + }); + } + if (sampleInfoStatus === 3) { + return this.json({ + code: 1010, + data: { + sample_info_status: 3, + sample_edit_reason: patient.sample_edit_reason || '', + sample_edit_apply_time: patient.sample_edit_apply_time || '', + sample_receiver_info: sampleReceiverInfo + }, + msg: '送检信息修改申请正在审核中,请等待平台处理。' }); } const hasSample = sample_types && sample_types.length > 0; + const hadSampleInfo = this._hasSampleInfo(patient); + const now = think.datetime(new Date()); // 邮箱格式与重复校验 if (hasSample && report_email) { @@ -802,7 +842,22 @@ module.exports = class extends Base { report_email: hasSample ? (report_email || '') : '', sample_tracking_no: hasSample ? (sample_tracking_no || '') : '', sample_photos: hasSample ? JSON.stringify(sample_photos || []) : '[]', - sample_info_status: 1 + sample_info_status: 1, + sample_edit_reason: '', + sample_edit_reject_reason: '', + sample_edit_apply_time: null, + sample_edit_audit_time: null, + update_time: now + }); + + await this.model('patient_sample_log').addLog({ + patient_id: user.patient_id, + action: hadSampleInfo ? 'sample_update' : 'sample_submit', + title: hadSampleInfo ? '修改送检信息' : '提交送检信息', + content: hadSampleInfo ? '患者修改并提交送检信息' : '患者提交送检信息', + operator_type: 'patient', + operator_id: user.id || 0, + operator_name: patient.name || '患者' }); return this.json({ code: 0, data: {}, msg: '提交成功' }); @@ -812,6 +867,60 @@ module.exports = class extends Base { } } + /** + * 申请修改送检信息 + * POST /api/mp/applySampleInfoEdit + */ + async applySampleInfoEditAction() { + const mpUser = this.mpUser; + if (!mpUser) return this.json({ code: 1009, msg: '请先登录' }); + const { reason, mp_env_version } = this.post(); + if (!reason || !reason.trim()) return this.json({ code: 1, msg: '请填写申请原因' }); + + try { + const user = await this.model('wechat_user').where({ id: mpUser.id, status: 1 }).find(); + if (think.isEmpty(user) || !user.patient_id) return this.json({ code: 1, msg: '请先完成实名认证' }); + const patient = await this.model('patient').where({ id: user.patient_id, is_deleted: 0 }).find(); + if (think.isEmpty(patient)) return this.json({ code: 1, msg: '患者信息不存在' }); + if (patient.status !== 1) return this.json({ code: 1, msg: '请先通过审核' }); + + const sampleInfoStatus = Number(patient.sample_info_status) || 0; + if (sampleInfoStatus === 3) return this.json({ code: 1, msg: '修改申请正在审核中,请勿重复提交' }); + if (sampleInfoStatus === 2) return this.json({ code: 1, msg: '样品已经寄回,暂不支持申请修改送检信息' }); + if (sampleInfoStatus !== 1) return this.json({ code: 1, msg: '当前送检信息无需申请修改' }); + + const now = think.datetime(new Date()); + const applyReason = reason.trim(); + if (mp_env_version) { + await this.model('wechat_user').where({ id: user.id }).update({ mp_env_version }); + } + await this.model('patient').where({ id: user.patient_id }).update({ + sample_info_status: 3, + sample_edit_reason: applyReason, + sample_edit_reject_reason: '', + sample_edit_apply_time: now, + sample_edit_audit_time: null, + update_time: now + }); + + await this.model('patient_sample_log').addLog({ + patient_id: user.patient_id, + action: 'sample_apply_edit', + title: '申请修改送检信息', + content: '患者申请修改送检信息', + reason: applyReason, + operator_type: 'patient', + operator_id: user.id || 0, + operator_name: patient.name || '患者' + }); + + return this.json({ code: 0, data: { sample_info_status: 3 }, msg: '申请已提交,请等待平台审核' }); + } catch (error) { + think.logger.error('applySampleInfoEdit error:', error); + return this.json({ code: 1, msg: '提交失败: ' + error.message }); + } + } + /** * 批量重新生成声明与承诺签署图并更新数据库 * GET /api/mp/regenerateSign diff --git a/src/model/patient_sample_log.js b/src/model/patient_sample_log.js new file mode 100644 index 0000000..133e8e9 --- /dev/null +++ b/src/model/patient_sample_log.js @@ -0,0 +1,19 @@ +module.exports = class extends think.Model { + get tableName() { + return 'patient_sample_log'; + } + + async addLog(data = {}) { + return this.add({ + patient_id: data.patient_id || 0, + action: data.action || '', + title: data.title || '', + content: data.content || '', + reason: data.reason || '', + operator_type: data.operator_type || '', + operator_id: data.operator_id || 0, + operator_name: data.operator_name || '', + create_time: data.create_time || think.datetime(new Date()) + }); + } +}; diff --git a/view/admin/patient_detail.html b/view/admin/patient_detail.html index 4598539..4b2b9cf 100644 --- a/view/admin/patient_detail.html +++ b/view/admin/patient_detail.html @@ -20,8 +20,12 @@ .timeline-list li::before { content: ''; position: absolute; left: -6px; top: 4px; width: 10px; height: 10px; border-radius: 50%; background: var(--el-color-primary); } .timeline-list li .time { font-size: 12px; color: #909399; margin-bottom: 4px; } .timeline-list li .desc { font-size: 14px; color: #303133; } + .timeline-list li .subdesc { font-size: 13px; color: #606266; margin-top: 4px; } .timeline-list li .reason { font-size: 13px; color: #F56C6C; margin-top: 4px; } .fixed-bottom-bar { position: fixed; bottom: 0; left: 220px; right: 0; height: 64px; background: #fff; box-shadow: 0 -2px 8px rgba(0,0,0,0.08); display: flex; align-items: center; justify-content: center; gap: 16px; z-index: 10; padding: 0 24px; } + .sample-photo-edit { position: relative; width: 80px; height: 80px; } + .sample-photo-edit .el-image { width: 80px; height: 80px; border-radius: 6px; border: 1px solid #eee; } + .sample-photo-edit .del { position: absolute; top: -6px; right: -6px; cursor: pointer; background: rgba(0,0,0,0.6); color: #fff; border-radius: 50%; width: 20px; height: 20px; display:flex; align-items:center; justify-content:center; font-size:12px; z-index:10; line-height:1; } {% endblock %} @@ -172,6 +176,10 @@