Files
zyt/server/app/adminapi/logic/tcm/PrescriptionLogic.php
T
2026-05-16 15:50:06 +08:00

930 lines
36 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace app\adminapi\logic\tcm;
use app\common\model\auth\Admin;
use app\common\model\doctor\Appointment;
use app\common\model\tcm\Prescription;
use app\common\model\tcm\PrescriptionOrder;
use app\common\model\tcm\Diagnosis;
use app\common\service\wechat\WechatWorkAppMessageService;
use app\adminapi\logic\auth\AuthLogic;
use think\facade\Config;
use think\facade\Log;
class PrescriptionLogic
{
private static $error = '';
/** @var array{ok: bool, message?: string, errcode?: int}|null 最近一次审核成功后的企微通知结果 */
private static ?array $lastAuditWecomNotify = null;
public static function setError(string $msg): void
{
self::$error = $msg;
}
public static function getError(): string
{
return self::$error;
}
/**
* 可见角色 ID 存库为逗号分隔字符串
*
* @param array|string $raw
*/
public static function normalizeVisibleRoleIds($raw): string
{
if (is_string($raw)) {
$parts = array_filter(array_map('intval', explode(',', $raw)));
} elseif (is_array($raw)) {
$parts = array_filter(array_map('intval', $raw));
} else {
$parts = [];
}
return implode(',', array_values(array_unique($parts)));
}
/**
* 当前管理员是否可审核处方(通过/驳回)
*/
public static function canAuditPrescription(int $adminId, array $adminInfo): bool
{
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
return true;
}
$allow = Config::get('project.prescription_audit_roles', []);
if ($allow === [] || $allow === null) {
$allow = Config::get('project.prescription_library_manage_all_roles', [0, 3]);
}
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
$allow = array_map('intval', $allow);
return count(array_intersect($myRoles, $allow)) > 0;
}
/**
* 是否可查看该处方(列表/详情)
*
* @param Prescription|array<string,mixed> $row
*/
public static function canViewPrescription($row, int $adminId, array $adminInfo): bool
{
// 超级管理员
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
return true;
}
// 创建人
$creatorId = is_array($row) ? (int) ($row['creator_id'] ?? 0) : (int) $row->creator_id;
if ($creatorId === $adminId) {
return true;
}
// 医助
$assistantId = is_array($row) ? (int) ($row['assistant_id'] ?? 0) : (int) ($row->assistant_id ?? 0);
if ($assistantId > 0 && $assistantId === $adminId) {
return true;
}
// 共享处方
$isShared = is_array($row) ? (int) ($row['is_shared'] ?? 0) : (int) $row->is_shared;
if ($isShared === 1) {
return true;
}
// 可见角色
$vis = is_array($row) ? (string) ($row['visible_role_ids'] ?? '') : (string) ($row->visible_role_ids ?? '');
$allowed = array_filter(array_map('intval', explode(',', $vis)));
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
if (count(array_intersect($myRoles, $allowed)) > 0) {
return true;
}
// 有开方权限的医生可以查看所有处方(只读)
// 这样其他医生可以查看患者的历史处方记录
$permissions = $adminInfo['permissions'] ?? [];
if (in_array('tcm.diagnosis/kaifang', $permissions, true)) {
return true;
}
// 有业务订单管理权限的角色(如药师)可以查看处方(只读)
// 这样药师可以在业务订单中查看关联的处方信息
$orderEditAllRoles = Config::get('project.order_edit_all_roles', [0, 3]);
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
$allowedRoles = array_map('intval', is_array($orderEditAllRoles) ? $orderEditAllRoles : []);
if (count(array_intersect($myRoles, $allowedRoles)) > 0) {
return true;
}
// 有业务订单详情权限的用户可以查看关联处方(只读)
$perms = AuthLogic::getAuthByAdminId($adminId);
if (in_array('tcm.prescriptionOrder/detail', $perms, true)) {
return true;
}
return false;
}
private static function generateSn(): string
{
return 'RX' . date('Ymd') . str_pad((string)mt_rand(1, 9999), 4, '0', STR_PAD_LEFT);
}
/**
* 处方日期统一为 Y-m-d(与库表 prescription_date 一致)
*
* @param mixed $raw
*/
private static function normalizePrescriptionDate($raw): string
{
if ($raw === null || $raw === '') {
return date('Y-m-d');
}
if (is_numeric($raw) && (float) $raw > 1e9) {
return date('Y-m-d', (int) $raw);
}
$s = (string) $raw;
if (preg_match('/^\d{4}-\d{2}-\d{2}/', $s)) {
return substr($s, 0, 10);
}
$ts = strtotime($s);
return $ts ? date('Y-m-d', $ts) : date('Y-m-d');
}
private static function normalizeDosageBagCount($raw): int
{
$count = (int) $raw;
if ($count < 1) {
return 1;
}
if ($count > 5) {
return 5;
}
return $count;
}
/**
* 同一诊单、同一开方人、同一处方日期:仅允许一张「未作废」的未删除记录;已作废的可再新开一张。
*
* @param int|null $excludeId 编辑时排除自身 id
*/
private static function assertUniquePrescriptionPerDiagnosisDay(int $diagnosisId, int $creatorId, string $prescriptionDateYmd, ?int $excludeId): bool
{
if ($diagnosisId <= 0 || $creatorId <= 0) {
return true;
}
$q = Prescription::where('diagnosis_id', $diagnosisId)
->where('creator_id', $creatorId)
->where('prescription_date', $prescriptionDateYmd)
->where('void_status', 0)
->whereNull('delete_time');
if ($excludeId !== null && $excludeId > 0) {
$q->where('id', '<>', $excludeId);
}
if ($q->count() > 0) {
self::setError('同一诊单同一天已存在未作废处方,请先作废后再新开');
return false;
}
return true;
}
/**
* 添加处方
*/
public static function add(array $params, int $adminId): ?int
{
// 如果没有诊单ID,允许直接创建处方模板
if (!empty($params['diagnosis_id'])) {
$diagnosis = Diagnosis::find($params['diagnosis_id']);
if (!$diagnosis) {
self::setError('诊单不存在');
return null;
}
}
$dateYmd = self::normalizePrescriptionDate($params['prescription_date'] ?? date('Y-m-d'));
$diagnosisIdRule = (int) ($params['diagnosis_id'] ?? 0);
if ($diagnosisIdRule > 0 && !self::assertUniquePrescriptionPerDiagnosisDay($diagnosisIdRule, $adminId, $dateYmd, null)) {
return null;
}
$herbs = $params['herbs'] ?? [];
if (empty($herbs) || !is_array($herbs)) {
self::setError('请添加中药');
return null;
}
foreach ($herbs as $h) {
if (empty($h['name'])) {
self::setError('中药名称不能为空');
return null;
}
}
$sn = self::generateSn();
while (Prescription::where('sn', $sn)->find()) {
$sn = self::generateSn();
}
$assistantIdForRx = 0;
if ($diagnosisIdRule > 0) {
$diagAssistant = Diagnosis::where('id', $diagnosisIdRule)->value('assistant_id');
$assistantIdForRx = (int) ($diagAssistant ?? 0);
}
$data = [
'sn' => $sn,
'prescription_name' => $params['prescription_name'] ?? '',
'prescription_type' => $params['prescription_type'] ?? '浓缩水丸',
'dosage_amount' => isset($params['dosage_amount']) ? (float)$params['dosage_amount'] : null,
'dosage_unit' => $params['dosage_unit'] ?? '',
'dosage_bag_count' => self::normalizeDosageBagCount($params['dosage_bag_count'] ?? 1),
'need_decoction' => (int)($params['need_decoction'] ?? 0),
'bags_per_dose' => isset($params['bags_per_dose']) ? (int)$params['bags_per_dose'] : 1,
'diagnosis_id' => (int)($params['diagnosis_id'] ?? 0),
'appointment_id' => (int)($params['appointment_id'] ?? 0),
'patient_id' => (int)($params['patient_id'] ?? 0),
'patient_name' => $params['patient_name'] ?? '',
'gender' => (int)($params['gender'] ?? 1),
'age' => (int)($params['age'] ?? 0),
'phone' => $params['phone'] ?? '',
'visit_no' => $params['visit_no'] ?? $sn,
'prescription_date' => $dateYmd,
'pulse' => $params['pulse'] ?? '',
'pulse_condition' => $params['pulse_condition'] ?? '',
'tongue' => $params['tongue'] ?? '',
'tongue_image' => $params['tongue_image'] ?? '',
'clinical_diagnosis' => $params['clinical_diagnosis'] ?? '',
'case_record' => $params['case_record'] ?? null,
'herbs' => $herbs,
'dose_count' => (int)($params['dose_count'] ?? 1),
'dose_unit' => $params['dose_unit'] ?? '剂',
'usage_days' => (int)($params['usage_days'] ?? 7),
'times_per_day' => (int)($params['times_per_day'] ?? 2),
'usage_instruction' => $params['usage_instruction'] ?? '水煎服一日二次',
'usage_time' => $params['usage_time'] ?? '饭前',
'usage_way' => $params['usage_way'] ?? '温水送服',
'dietary_taboo' => is_array($params['dietary_taboo'] ?? null) ? implode(',', $params['dietary_taboo']) : ($params['dietary_taboo'] ?? ''),
'usage_notes' => $params['usage_notes'] ?? '',
'amount' => (float)($params['amount'] ?? 0),
'doctor_name' => $params['doctor_name'] ?? '',
'doctor_signature' => $params['doctor_signature'] ?? '',
'template_id' => (int)($params['template_id'] ?? 0),
'is_shared' => (int)($params['is_shared'] ?? 0),
'visible_role_ids' => self::normalizeVisibleRoleIds($params['visible_role_ids'] ?? []),
// 新开方一律待审核(忽略客户端传入的 audit_status,防止绕过审核)
'audit_status' => 0,
'audit_time' => null,
'audit_by' => null,
'audit_by_name' => '',
'audit_remark' => '',
'creator_id' => $adminId,
'assistant_id' => $assistantIdForRx,
];
$prescription = new Prescription();
$prescription->save($data);
return (int) $prescription->id;
}
/**
* 编辑处方
*/
public static function edit(array $params, int $adminId): bool
{
try {
$prescription = Prescription::find($params['id']);
if (!$prescription) {
self::setError('处方不存在');
return false;
}
// 已通过且未作废:不可编辑
if ((int) ($prescription->audit_status ?? 1) === 1 && (int) ($prescription->void_status ?? 0) === 0) {
self::setError('该处方已通过审核,不可编辑');
return false;
}
// 已驳回:仅当该诊单+当天+同一创建人下没有另一条「已通过且未作废」的处方时允许重新编辑(改后待审核、作废一并清除)
if ((int) ($prescription->audit_status ?? 1) === 2) {
$rejDiagnosisId = (int) ($params['diagnosis_id'] ?? $prescription->diagnosis_id);
$rejDateYmd = self::normalizePrescriptionDate($params['prescription_date'] ?? $prescription->prescription_date);
if ($rejDiagnosisId > 0) {
$hasOtherApproved = Prescription::where('diagnosis_id', $rejDiagnosisId)
->where('creator_id', (int) $prescription->creator_id)
->where('prescription_date', $rejDateYmd)
->where('audit_status', 1)
->where('void_status', 0)
->whereNull('delete_time')
->where('id', '<>', (int) $params['id'])
->count() > 0;
if ($hasOtherApproved) {
self::setError('该诊单当天已有审核通过的处方,不可再编辑本条已驳回记录');
return false;
}
}
}
// 检查权限:只有创建者或共享的处方才能编辑
if ($prescription->creator_id != $adminId && $prescription->is_shared != 1) {
self::setError('无权限编辑此处方');
return false;
}
$herbs = $params['herbs'] ?? [];
if (empty($herbs) || !is_array($herbs)) {
self::setError('请添加中药');
return false;
}
foreach ($herbs as $h) {
if (empty($h['name'])) {
self::setError('中药名称不能为空');
return false;
}
}
$newDiagnosisId = (int) ($params['diagnosis_id'] ?? $prescription->diagnosis_id);
$newDateYmd = self::normalizePrescriptionDate($params['prescription_date'] ?? $prescription->prescription_date);
if ($newDiagnosisId > 0 && !self::assertUniquePrescriptionPerDiagnosisDay($newDiagnosisId, (int) $prescription->creator_id, $newDateYmd, (int) $params['id'])) {
return false;
}
$wasVoid = (int) ($prescription->void_status ?? 0) === 1;
$assistantIdForRx = (int) ($prescription->assistant_id ?? 0);
if ($newDiagnosisId > 0) {
$diagAssistant = Diagnosis::where('id', $newDiagnosisId)->value('assistant_id');
$assistantIdForRx = (int) ($diagAssistant ?? 0);
} else {
$assistantIdForRx = 0;
}
$data = [
'diagnosis_id' => $newDiagnosisId,
'assistant_id' => $assistantIdForRx,
'prescription_name' => $params['prescription_name'] ?? $prescription->prescription_name,
'prescription_type' => $params['prescription_type'] ?? $prescription->prescription_type,
'dosage_amount' => isset($params['dosage_amount']) ? (float)$params['dosage_amount'] : $prescription->dosage_amount,
'dosage_unit' => $params['dosage_unit'] ?? $prescription->dosage_unit,
'dosage_bag_count' => self::normalizeDosageBagCount($params['dosage_bag_count'] ?? ($prescription->dosage_bag_count ?? 1)),
'need_decoction' => isset($params['need_decoction']) ? (int)$params['need_decoction'] : (int)($prescription->need_decoction ?? 0),
'bags_per_dose' => isset($params['bags_per_dose']) ? (int)$params['bags_per_dose'] : (int)($prescription->bags_per_dose ?? 1),
'patient_name' => $params['patient_name'] ?? $prescription->patient_name,
'gender' => (int)($params['gender'] ?? $prescription->gender),
'age' => (int)($params['age'] ?? $prescription->age),
'visit_no' => $params['visit_no'] ?? $prescription->visit_no,
'prescription_date' => $newDateYmd,
'tongue' => $params['tongue'] ?? $prescription->tongue,
'tongue_image' => $params['tongue_image'] ?? $prescription->tongue_image,
'pulse' => $params['pulse'] ?? $prescription->pulse,
'pulse_condition' => $params['pulse_condition'] ?? $prescription->pulse_condition,
'clinical_diagnosis' => $params['clinical_diagnosis'] ?? $prescription->clinical_diagnosis,
'herbs' => $herbs,
'dose_count' => (int)($params['dose_count'] ?? $prescription->dose_count),
'dose_unit' => $params['dose_unit'] ?? $prescription->dose_unit,
'usage_days' => (int)($params['usage_days'] ?? $prescription->usage_days),
'times_per_day' => (int)($params['times_per_day'] ?? $prescription->times_per_day ?? 2),
'usage_instruction' => $params['usage_instruction'] ?? $prescription->usage_instruction,
'usage_time' => $params['usage_time'] ?? $prescription->usage_time,
'usage_way' => $params['usage_way'] ?? $prescription->usage_way,
'dietary_taboo' => is_array($params['dietary_taboo'] ?? null) ? implode(',', $params['dietary_taboo']) : ($params['dietary_taboo'] ?? $prescription->dietary_taboo),
'usage_notes' => $params['usage_notes'] ?? $prescription->usage_notes,
'doctor_name' => $params['doctor_name'] ?? $prescription->doctor_name,
'doctor_signature' => $params['doctor_signature'] ?? $prescription->doctor_signature,
'is_shared' => (int)($params['is_shared'] ?? $prescription->is_shared),
'visible_role_ids' => array_key_exists('visible_role_ids', $params)
? self::normalizeVisibleRoleIds($params['visible_role_ids'])
: (string) ($prescription->visible_role_ids ?? ''),
// 后台编辑保存后视为手工处方(原系统代开标记清除)
'is_system_auto' => 0,
// 开方医生修改后重新进入待审核
'audit_status' => 0,
'audit_time' => null,
'audit_by' => null,
'audit_by_name' => '',
'audit_remark' => '',
];
if ($wasVoid) {
$data['void_status'] = 0;
$data['void_time'] = null;
$data['void_by'] = null;
$data['void_by_name'] = '';
}
$prescription->save($data);
PrescriptionOrderLogic::onConsumerPrescriptionSaved((int) $params['id']);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* 删除处方
*/
public static function delete(int $id): bool
{
try {
$prescription = Prescription::find($id);
if (!$prescription) {
self::setError('处方不存在');
return false;
}
if ((int) ($prescription->audit_status ?? 1) === 1 && (int) ($prescription->void_status ?? 0) === 0) {
self::setError('该处方已通过审核,不可删除');
return false;
}
$prescription->delete();
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* 处方详情(带查看权限校验,供后台管理端)
*/
public static function detail(int $id, int $viewerAdminId, array $viewerAdminInfo): ?array
{
self::$error = '';
$row = Prescription::find($id);
if (!$row) {
return null;
}
if (!self::canViewPrescription($row, $viewerAdminId, $viewerAdminInfo)) {
self::setError('无权限查看此处方');
return null;
}
$arr = $row->toArray();
$arr['gender_desc'] = $arr['gender'] == 1 ? '男' : '女';
$arr['visible_role_ids'] = self::visibleRoleIdsToArray($arr['visible_role_ids'] ?? '');
$bizPo = PrescriptionOrder::where('prescription_id', $id)
->where('prescription_audit_status', 2)
->whereNull('delete_time')
->where('fulfillment_status', '<>', 4)
->order('id', 'desc')
->find();
$arr['business_prescription_audit_rejected'] = $bizPo ? 1 : 0;
$arr['business_prescription_audit_remark'] = $bizPo ? (string) ($bizPo->prescription_audit_remark ?? '') : '';
// 与 PrescriptionLists「业务订单」角标一致:未删除且非已取消(4) 即视为存在有效业务订单
$hasBizOrder = PrescriptionOrder::where('prescription_id', $id)
->whereNull('delete_time')
->where('fulfillment_status', '<>', 4)
->count() > 0;
$arr['has_prescription_order'] = $hasBizOrder ? 1 : 0;
/**
* 处方笺展示所需的"收件信息/订单号/合计"等字段,
* 来源是该处方关联的「业务处方订单」(zyt_tcm_prescription_order)。
* 取最近一条未删除且未取消的订单:
* - recipient_name / recipient_phone / shipping_address 用于「收件信息」一行
* - order_no 用于处方笺右上角「编号」
* - amount 作为「合计」金额回填(前端再做 0 兜底展示)
*/
$latestPo = PrescriptionOrder::where('prescription_id', $id)
->whereNull('delete_time')
->where('fulfillment_status', '<>', 4)
->order('id', 'desc')
->find();
if ($latestPo) {
$arr['recipient_name'] = (string) ($latestPo->recipient_name ?? '');
$arr['recipient_phone'] = (string) ($latestPo->recipient_phone ?? '');
$arr['shipping_address'] = (string) ($latestPo->shipping_address ?? '');
$arr['order_no'] = (string) ($latestPo->order_no ?? '');
$arr['order_amount'] = round((float) ($latestPo->amount ?? 0), 2);
}
return $arr;
}
/**
* 业务订单「处方审核」通过时:消费者处方仍为待审核则一并记为已通过,
* 避免消费者处方列表筛选「未通过」仍包含该条、与业务侧已通过不一致。
*/
public static function syncApproveConsumerPrescriptionIfPending(
int $prescriptionId,
int $adminId,
array $adminInfo,
string $remark
): void {
if ($prescriptionId <= 0) {
return;
}
$row = Prescription::find($prescriptionId);
if (!$row) {
return;
}
if ((int) ($row->void_status ?? 0) === 1) {
return;
}
if ((int) ($row->audit_status ?? 1) !== 0) {
return;
}
self::$lastAuditWecomNotify = null;
$name = (string) ($adminInfo['name'] ?? '');
$now = time();
$finalRemark = trim($remark) !== '' ? trim($remark) : '业务订单处方审核通过';
$finalRemark = mb_substr($finalRemark, 0, 500);
$row->audit_status = 1;
$row->audit_time = $now;
$row->audit_by = $adminId;
$row->audit_by_name = $name;
$row->audit_remark = $finalRemark;
try {
if ($row->save()) {
self::$lastAuditWecomNotify = self::notifyCreatorAuditResult($row, 'approve', $finalRemark, $adminInfo);
}
} catch (\Throwable $e) {
Log::error('syncApproveConsumerPrescriptionIfPending: ' . $e->getMessage());
}
}
/**
* @return int[]
*/
public static function visibleRoleIdsToArray(string $csv): array
{
if ($csv === '') {
return [];
}
return array_values(array_unique(array_filter(array_map('intval', explode(',', $csv)))));
}
/**
* 审核:通过 / 驳回(驳回同时作废处方)
*/
public static function audit(int $id, string $action, string $remark, int $adminId, array $adminInfo): bool
{
self::$error = '';
self::$lastAuditWecomNotify = null;
if (!self::canAuditPrescription($adminId, $adminInfo)) {
self::setError('无审核权限');
return false;
}
$row = Prescription::find($id);
if (!$row) {
self::setError('处方不存在');
return false;
}
if (!self::canViewPrescription($row, $adminId, $adminInfo)) {
self::setError('无权限查看此处方,无法审核');
return false;
}
if ((int) ($row->audit_status ?? 1) !== 0) {
self::setError('当前状态不可审核');
return false;
}
$name = (string) ($adminInfo['name'] ?? '');
$now = time();
if ($action === 'approve') {
$row->audit_status = 1;
$row->audit_time = $now;
$row->audit_by = $adminId;
$row->audit_by_name = $name;
$row->audit_remark = $remark;
$ok = (bool) $row->save();
if ($ok) {
self::$lastAuditWecomNotify = self::notifyCreatorAuditResult($row, 'approve', $remark, $adminInfo);
}
return $ok;
}
if ($action === 'reject') {
if (trim($remark) === '') {
self::setError('驳回时请填写审核意见');
return false;
}
$row->audit_status = 2;
$row->audit_time = $now;
$row->audit_by = $adminId;
$row->audit_by_name = $name;
$row->audit_remark = $remark;
$row->void_status = 1;
$row->void_time = $now;
$row->void_by = $adminId;
$row->void_by_name = $name;
$ok = (bool) $row->save();
if ($ok) {
self::$lastAuditWecomNotify = self::notifyCreatorAuditResult($row, 'reject', $remark, $adminInfo);
}
return $ok;
}
self::setError('无效的审核操作');
return false;
}
/**
* 审核通过/驳回后,给企业微信中的开方人发一条应用消息(需绑定 work_wechat_userid
*
* @return array{ok: bool, message?: string, errcode?: int}
*/
private static function notifyCreatorAuditResult(Prescription $rx, string $action, string $remark, array $auditorInfo): array
{
try {
$creatorId = (int) ($rx->creator_id ?? 0);
if ($creatorId <= 0) {
return ['ok' => false, 'message' => '无法通知:处方无开方人信息'];
}
$creator = Admin::whereNull('delete_time')->find($creatorId);
if (!$creator) {
return ['ok' => false, 'message' => '无法通知:开方人账号不存在'];
}
$wxId = trim((string) ($creator->work_wechat_userid ?? ''));
if ($wxId === '') {
Log::warning("处方审核企业微信通知跳过: 开方人 admin_id={$creatorId} 未绑定 work_wechat_userid");
return [
'ok' => false,
'message' => '开方人未绑定企业微信账号,无法推送消息(请其在后台「扫码绑定企业微信」后再试)',
];
}
$sn = (string) ($rx->sn ?? '');
$patient = (string) ($rx->patient_name ?? '');
$auditorName = (string) ($auditorInfo['name'] ?? '');
if ($action === 'approve') {
$text = "【处方审核】您开具的处方已通过审核。\n编号:{$sn}\n患者:{$patient}\n审核人:{$auditorName}";
if (trim($remark) !== '') {
$text .= "\n备注:" . trim($remark);
}
} else {
$text = "【处方审核】您开具的处方已被驳回并已作废。\n编号:{$sn}\n患者:{$patient}\n审核人:{$auditorName}\n意见:" . trim($remark);
}
$result = WechatWorkAppMessageService::sendTextToUser($wxId, $text);
if (empty($result['ok'])) {
Log::warning('处方审核企业微信通知失败: ' . ($result['message'] ?? ''));
}
return $result;
} catch (\Throwable $e) {
Log::error('处方审核企业微信通知异常: ' . $e->getMessage());
return ['ok' => false, 'message' => '企业微信通知异常:' . $e->getMessage()];
}
}
/**
* 取出并清空最近一次审核后的企微通知结果(供接口返回给前端提示)
*
* @return array{ok: bool, message?: string, errcode?: int}|null
*/
public static function consumeLastAuditWecomNotify(): ?array
{
$v = self::$lastAuditWecomNotify;
self::$lastAuditWecomNotify = null;
return $v;
}
/**
* 根据诊单ID获取处方列表
*/
public static function listByDiagnosis(int $diagnosisId): array
{
return Prescription::where('diagnosis_id', $diagnosisId)
->whereNull('delete_time')
->order('id', 'desc')
->select()
->toArray();
}
/**
* 根据预约ID获取处方(带权限检查)
*/
public static function getByAppointment(int $appointmentId, int $viewerAdminId, array $viewerAdminInfo): ?array
{
self::$error = '';
$row = Prescription::where('appointment_id', $appointmentId)
->where('creator_id',$viewerAdminId)
->whereNull('delete_time')
->order('id', 'desc')
->find();
if (!$row) {
return null;
}
// 权限检查:只返回当前用户有权限查看的处方
if (!self::canViewPrescription($row, $viewerAdminId, $viewerAdminInfo)) {
self::setError('无权限查看此处方');
return null;
}
return $row->toArray();
}
/**
* 挂号标记「完成」后自动生成处方:无药材配方,is_system_auto=1,其余字段与常规开方一致(患者/诊单来自预约关联诊单)。
* 若该预约已有未作废处方、或同日同诊单同医师已存在未作废处方,则跳过(不抛错)。
*
* @return int|null 新建处方 id;跳过返回 null
*/
public static function createSystemAutoFromCompletedAppointment(int $appointmentId): ?int
{
if ($appointmentId <= 0) {
return null;
}
$appointment = Appointment::find($appointmentId);
if (!$appointment) {
return null;
}
$diagnosisId = (int) ($appointment->patient_id ?? 0);
if ($diagnosisId <= 0) {
Log::info('createSystemAutoFromCompletedAppointment: no diagnosis (patient_id), skip appt=' . $appointmentId);
return null;
}
$doctorId = (int) ($appointment->doctor_id ?? 0);
if ($doctorId <= 0) {
return null;
}
$dupAppt = Prescription::where('appointment_id', $appointmentId)
->where('void_status', 0)
->whereNull('delete_time')
->find();
if ($dupAppt) {
return null;
}
$diagnosis = Diagnosis::find($diagnosisId);
if (!$diagnosis) {
self::setError('诊单不存在');
return null;
}
$apptDateRaw = $appointment->appointment_date ?? '';
$dateYmd = is_string($apptDateRaw) && preg_match('/^\d{4}-\d{2}-\d{2}/', trim($apptDateRaw))
? substr(trim($apptDateRaw), 0, 10)
: self::normalizePrescriptionDate(date('Y-m-d'));
if (!self::assertUniquePrescriptionPerDiagnosisDay($diagnosisId, $doctorId, $dateYmd, null)) {
Log::info("createSystemAutoFromCompletedAppointment: unique rule skip diagnosis={$diagnosisId} doctor={$doctorId} date={$dateYmd}");
return null;
}
$sn = self::generateSn();
while (Prescription::where('sn', $sn)->find()) {
$sn = self::generateSn();
}
$clinical = trim((string) ($diagnosis->symptoms ?? ''));
if ($clinical === '') {
$clinical = '就诊完成系统生成(暂无药材配方,请医师补充诊断与药材)';
}
$doctorName = (string) (Admin::where('id', $doctorId)->value('name') ?? '');
$assistantIdForRx = (int) ($diagnosis->assistant_id ?? 0);
$visitNo = '1K' . str_pad((string) $diagnosisId, 8, '0', STR_PAD_LEFT);
$herbs = [];
/** 与手动开方一致:存一份当前诊单详情快照(/admin 开方时用 tcmDiagnosisDetail → 同源 DiagnosisLogic::detail */
$caseSnapshot = [];
try {
$snap = DiagnosisLogic::detail(['id' => $diagnosisId]);
$caseSnapshot = is_array($snap) ? $snap : [];
} catch (\Throwable $e) {
Log::warning(
'createSystemAutoFromCompletedAppointment: case_record snapshot failed diagnosis='
. $diagnosisId . ' ' . $e->getMessage()
);
}
$data = [
'sn' => $sn,
'prescription_name' => '系统代开',
'prescription_type' => '浓缩水丸',
'dosage_amount' => 1.0,
'dosage_unit' => 'g',
'dosage_bag_count' => 1,
'need_decoction' => 0,
'bags_per_dose' => 1,
'diagnosis_id' => $diagnosisId,
'appointment_id' => $appointmentId,
'patient_id' => 0,
'patient_name' => (string) ($diagnosis->patient_name ?? ''),
'gender' => (int) ($diagnosis->gender ?? 0),
'age' => (int) ($diagnosis->age ?? 0),
'phone' => (string) ($diagnosis->phone ?? ''),
'visit_no' => $visitNo,
'prescription_date' => $dateYmd,
'pulse' => (string) ($diagnosis->pulse ?? ''),
'pulse_condition' => '',
'tongue' => (string) ($diagnosis->tongue ?? ''),
'tongue_image' => '',
'clinical_diagnosis' => $clinical,
'case_record' => $caseSnapshot,
'herbs' => $herbs,
'dose_count' => 1,
'dose_unit' => '剂',
'usage_days' => 7,
'times_per_day' => 2,
'usage_instruction' => '',
'usage_time' => '饭后',
'usage_way' => '温水送服',
'dietary_taboo' => '',
'usage_notes' => '',
'amount' => 0.0,
'doctor_name' => $doctorName,
'doctor_signature' => '',
'template_id' => 0,
'is_shared' => 0,
'is_system_auto' => 1,
'visible_role_ids' => self::normalizeVisibleRoleIds([]),
'audit_status' => 0,
'audit_time' => null,
'audit_by' => null,
'audit_by_name' => '',
'audit_remark' => '',
'creator_id' => $doctorId,
'assistant_id' => $assistantIdForRx,
];
$prescription = new Prescription();
$prescription->save($data);
return (int) $prescription->id;
}
/**
* 作废处方
*/
public static function void(int $id, int $adminId, string $adminName): bool
{
$row = Prescription::find($id);
if (!$row) {
self::setError('处方不存在');
return false;
}
if ((int)($row->void_status ?? 0) === 1) {
self::setError('该处方已作废');
return false;
}
$rxId = (int) ($row->id ?? 0);
$bizCount = (int) PrescriptionOrder::where('prescription_id', $rxId)
->whereNull('delete_time')
->where('fulfillment_status', '<>', 4)
->count();
if ($bizCount > 0) {
self::setError('该处方已存在业务订单,无法作废');
return false;
}
$row->void_status = 1;
$row->void_time = time();
$row->void_by = $adminId;
$row->void_by_name = $adminName;
return $row->save();
}
}