更新
This commit is contained in:
@@ -64,6 +64,24 @@ class AppointmentController extends BaseAdminController
|
||||
return $this->dataLists(new AppointmentLists());
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台编辑挂号记录(消费者处方-挂号列表等,perms: doctor.appointment/edit)
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
$params = (new AppointmentValidate())->post()->goCheck('adminEdit');
|
||||
$post = $this->request->post();
|
||||
if (array_key_exists('assistant_id', $post)) {
|
||||
$params['assistant_id'] = $post['assistant_id'];
|
||||
}
|
||||
$result = AppointmentLogic::adminEdit($params, $this->adminId, $this->adminInfo);
|
||||
if ($result === false) {
|
||||
return $this->fail(AppointmentLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('保存成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 预约详情
|
||||
* @return \think\response\Json
|
||||
|
||||
@@ -20,6 +20,14 @@ class PrescriptionOrderController extends BaseAdminController
|
||||
return $this->dataLists(new PrescriptionOrderLists());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处方业务订单导出(与 lists 相同筛选条件,Excel;参数 export=1 预估 export=2 下载)
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
return $this->dataLists(new PrescriptionOrderLists());
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定诊单下可关联的支付单(待支付/已支付,创建/编辑业务订单时多选关联)
|
||||
*/
|
||||
|
||||
@@ -28,12 +28,9 @@ abstract class BaseAdminDataLists extends BaseDataLists
|
||||
protected array $adminInfo;
|
||||
protected int $adminId;
|
||||
|
||||
public function __construct()
|
||||
protected function initAdminIdentity(): void
|
||||
{
|
||||
parent::__construct();
|
||||
$this->adminInfo = $this->request->adminInfo;
|
||||
$this->adminId = $this->request->adminId;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use app\common\model\DiagnosisViewRecord;
|
||||
use app\common\model\doctor\Appointment;
|
||||
use app\common\model\tcm\Prescription;
|
||||
use app\common\model\auth\AdminRole;
|
||||
use app\common\model\dict\DictData;
|
||||
use app\common\lists\ListsExtendInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
@@ -122,7 +123,7 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
->leftJoin('tcm_diagnosis u', 'a.patient_id = u.id')
|
||||
->leftJoin('admin ad', 'a.doctor_id = ad.id')
|
||||
->leftJoin('admin asst', 'u.assistant_id = asst.id')
|
||||
->field('a.*, u.patient_name as patient_name, u.phone as patient_phone, u.gender as gender, u.age as age, u.weight as weight, u.height as height, u.assistant_id as assistant_id, ad.name as doctor_name, asst.name as assistant_name, u.id as diagnosis_id');
|
||||
->field('a.*, u.patient_name as patient_name, u.phone as patient_phone, u.gender as gender, u.age as age, u.weight as weight, u.height as height, u.assistant_id as assistant_id, ad.name as doctor_name, asst.name as assistant_name, u.id as diagnosis_id, a.assistant_id as appointment_assistant_id');
|
||||
if ($this->searchWhere !== []) {
|
||||
$query->where($this->searchWhere);
|
||||
}
|
||||
@@ -212,6 +213,9 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$channelNameByValue = DictData::where('type_value', 'channels')->column('name', 'value');
|
||||
|
||||
foreach ($lists as &$item) {
|
||||
$statusMap = [
|
||||
1 => '已预约',
|
||||
@@ -228,6 +232,24 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
];
|
||||
$item['appointment_type_desc'] = $typeMap[$item['appointment_type']] ?? '未知';
|
||||
|
||||
$periodRaw = (string) ($item['period'] ?? ($item['type'] ?? ''));
|
||||
$periodMap = [
|
||||
'morning' => '上午',
|
||||
'afternoon' => '下午',
|
||||
'all' => '全天',
|
||||
];
|
||||
$item['period_desc'] = $periodMap[$periodRaw] ?? ($periodRaw !== '' ? $periodRaw : '—');
|
||||
|
||||
$srcKey = trim((string) ($item['channel_source'] ?? ''));
|
||||
if ($srcKey === '' && isset($item['channels']) && $item['channels'] !== '' && $item['channels'] !== null) {
|
||||
$srcKey = trim((string) $item['channels']);
|
||||
}
|
||||
if ($srcKey !== '') {
|
||||
$item['channel_source_desc'] = (string) ($channelNameByValue[$srcKey] ?? $channelNameByValue[(string) (int) $srcKey] ?? $srcKey);
|
||||
} else {
|
||||
$item['channel_source_desc'] = '—';
|
||||
}
|
||||
|
||||
$item['diagnosis_confirmed'] = isset($confirmedMap[$item['diagnosis_id'] ?? 0]) ? 1 : 0;
|
||||
$item['has_prescription'] = isset($prescribedDiagnosisIds[$item['diagnosis_id'] ?? 0]) ? 1 : 0;
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\dept\DeptLogic;
|
||||
use app\adminapi\logic\stats\YejiStatsLogic;
|
||||
use app\adminapi\logic\tcm\PrescriptionOrderLogic;
|
||||
use app\common\enum\ExportEnum;
|
||||
use app\common\lists\ListsExcelInterface;
|
||||
use app\common\lists\ListsExtendInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
@@ -23,7 +25,7 @@ use think\facade\Config;
|
||||
use think\facade\Db;
|
||||
use think\db\Query;
|
||||
|
||||
class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
||||
class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface, ListsExcelInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
|
||||
@@ -558,6 +560,10 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
PrescriptionOrderLogic::appendLinkedAppointmentsToListRows($lists, $this->params);
|
||||
}
|
||||
|
||||
if ((int) $this->export === ExportEnum::EXPORT) {
|
||||
PrescriptionOrderLogic::appendPrescriptionOrderListExportRows($lists, $this->adminInfo);
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
@@ -601,6 +607,39 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
return $base;
|
||||
}
|
||||
|
||||
public function setFileName(): string
|
||||
{
|
||||
return '处方业务订单';
|
||||
}
|
||||
|
||||
public function setExcelFields(): array
|
||||
{
|
||||
return [
|
||||
'export_fulfillment_status_text' => '履约状态',
|
||||
'export_order_time' => '创建时间',
|
||||
'doctor_name' => '医生姓名',
|
||||
'export_patient_name' => '患者姓名',
|
||||
'export_patient_gender' => '性别',
|
||||
'export_patient_age' => '年龄',
|
||||
'export_patient_phone' => '手机号',
|
||||
'assistant_name' => '医助名字',
|
||||
'export_center_label' => '一中心还是二中心',
|
||||
'export_assistant_dept' => '医助部门',
|
||||
'export_channel' => '渠道',
|
||||
'export_medication_form' => '药品形态',
|
||||
'export_medication_days' => '天数',
|
||||
'export_amount' => '总金额',
|
||||
'export_paid_amount' => '已付金额',
|
||||
'export_agency_collect' => '代收金额',
|
||||
'export_tracking_number' => '快递单号',
|
||||
'export_supply_mode' => '甘草还是自营',
|
||||
'export_gancao_prescription_cost' => '处方成本',
|
||||
'export_first_visit_assistant' => '初诊医助',
|
||||
'export_rx_audit_time' => '审核时间',
|
||||
'export_rx_auditor' => '审核人',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 业绩看板侧栏专用:约诊/接诊条数,与 YejiStatsLogic::applyConsultFiltersAlignedWithAppointmentLists 同口径
|
||||
* (doctor_appointment.status=3,appointment_date 落入区间,有效医助 COALESCE(挂号.assistant_id,诊单.assistant_id))。
|
||||
|
||||
@@ -9,6 +9,8 @@ use app\common\service\doctor\RosterSegmentService;
|
||||
use app\common\model\tcm\Diagnosis;
|
||||
use app\common\model\tcm\DiagnosisGuahaoLog;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\auth\AdminRole;
|
||||
use app\common\service\DataScope\DataScopeService;
|
||||
use app\api\logic\ChatNotifyLogic;
|
||||
use app\adminapi\logic\tcm\PrescriptionLogic;
|
||||
use app\adminapi\logic\tcm\DiagnosisLogic;
|
||||
@@ -42,6 +44,61 @@ class AppointmentLogic extends BaseLogic
|
||||
return in_array($name, self::CHANNEL_NAMES_REQUIRING_SOURCE_DETAIL, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed> $cols Db::name('doctor_appointment')->getTableFields()
|
||||
*/
|
||||
private static function assertAppointmentChannelWritable(array $cols, string $chSrc): ?string
|
||||
{
|
||||
$hasChannelSource = in_array('channel_source', $cols, true);
|
||||
$hasChannels = in_array('channels', $cols, true);
|
||||
if (!$hasChannelSource && !$hasChannels) {
|
||||
return '挂号表缺少渠道字段(channel_source 或 channels),无法保存渠道来源';
|
||||
}
|
||||
if (!$hasChannelSource && $hasChannels && $chSrc !== '' && !is_numeric($chSrc)) {
|
||||
return '当前挂号表仅有数值型渠道字段 channels,无法写入该渠道;请在数据库增加 channel_source 列';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
* @param array<int|string, mixed> $cols
|
||||
*/
|
||||
private static function mergeAppointmentChannelIntoRow(array &$row, array $cols, string $chSrc, string $chDetail): void
|
||||
{
|
||||
$hasChannelSource = in_array('channel_source', $cols, true);
|
||||
$hasChannelDetail = in_array('channel_source_detail', $cols, true);
|
||||
$hasChannels = in_array('channels', $cols, true);
|
||||
if ($hasChannelSource) {
|
||||
$row['channel_source'] = $chSrc;
|
||||
}
|
||||
if ($hasChannelDetail) {
|
||||
$row['channel_source_detail'] = $chDetail;
|
||||
}
|
||||
if ($hasChannels && is_numeric($chSrc)) {
|
||||
$row['channels'] = (int) $chSrc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
* @param array<int|string, mixed> $cols
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function filterAppointmentRowByExistingColumns(array $row, array $cols): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($row as $k => $v) {
|
||||
if (in_array((string) $k, $cols, true)) {
|
||||
$out[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取可用时间段
|
||||
* @param array $params
|
||||
@@ -262,25 +319,49 @@ class AppointmentLogic extends BaseLogic
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'patient_id' => $params['patient_id'],
|
||||
'assistant_id'=>$params['assistant_id'],
|
||||
'doctor_id' => $params['doctor_id'],
|
||||
'roster_id' => 0, // 不再关联排班表
|
||||
$cols = Db::name('doctor_appointment')->getTableFields();
|
||||
$cols = is_array($cols) ? $cols : [];
|
||||
$channelErr = self::assertAppointmentChannelWritable($cols, $chSrc);
|
||||
if ($channelErr !== null) {
|
||||
self::setError($channelErr);
|
||||
Db::rollback();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$insert = [
|
||||
'patient_id' => (int) $params['patient_id'],
|
||||
'doctor_id' => (int) $params['doctor_id'],
|
||||
'appointment_date' => $params['appointment_date'],
|
||||
'period' => $params['period'] ?? 'all',
|
||||
'appointment_time' => $appointmentTime,
|
||||
'appointment_type' => $params['appointment_type'] ?? 'video',
|
||||
'remark' => $params['remark'] ?? '',
|
||||
// 表字段为 channel_source(与字典 type_value=channels 的 value 一致);勿再写入不存在的 channels 列
|
||||
'channel_source' => $chSrc,
|
||||
'channel_source_detail' => $chDetail,
|
||||
'status' => 1,
|
||||
'create_time' => time(),
|
||||
'update_time' => time(),
|
||||
];
|
||||
if (in_array('roster_id', $cols, true)) {
|
||||
$insert['roster_id'] = 0;
|
||||
}
|
||||
if (in_array('assistant_id', $cols, true)) {
|
||||
$insert['assistant_id'] = (int) ($params['assistant_id'] ?? 0);
|
||||
}
|
||||
$periodVal = $params['period'] ?? 'all';
|
||||
if (in_array('period', $cols, true)) {
|
||||
$insert['period'] = $periodVal;
|
||||
} elseif (in_array('type', $cols, true)) {
|
||||
$insert['type'] = $periodVal;
|
||||
}
|
||||
self::mergeAppointmentChannelIntoRow($insert, $cols, $chSrc, $chDetail);
|
||||
|
||||
$appointment = Appointment::create($data);
|
||||
$insert = self::filterAppointmentRowByExistingColumns($insert, $cols);
|
||||
$newId = (int) Db::name('doctor_appointment')->insertGetId($insert);
|
||||
if ($newId <= 0) {
|
||||
self::setError('预约创建失败');
|
||||
Db::rollback();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
|
||||
@@ -288,21 +369,21 @@ class AppointmentLogic extends BaseLogic
|
||||
$hm = substr((string) $appointmentTime, 0, 5);
|
||||
$summary = sprintf(
|
||||
'挂号 #%d:医生「%s」,%s %s',
|
||||
(int) $appointment->id,
|
||||
$newId,
|
||||
$doctorName !== '' ? $doctorName : ('ID:' . (int) $params['doctor_id']),
|
||||
(string) $params['appointment_date'],
|
||||
$hm
|
||||
);
|
||||
self::appendDiagnosisGuahaoLog(
|
||||
(int) $params['patient_id'],
|
||||
(int) $appointment->id,
|
||||
$newId,
|
||||
$operatorAdminId,
|
||||
$operatorAdminInfo,
|
||||
'create',
|
||||
$summary
|
||||
);
|
||||
|
||||
return ['id' => $appointment->id];
|
||||
return ['id' => $newId];
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
self::setError($e->getMessage());
|
||||
@@ -787,6 +868,244 @@ class AppointmentLogic extends BaseLogic
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 与 AppointmentLists 一致的可见性(不含 progress_board / diag_scope_relax)
|
||||
*/
|
||||
private static function appointmentRowManageableByAdmin(
|
||||
Appointment $appointment,
|
||||
?Diagnosis $diag,
|
||||
int $adminId,
|
||||
array $adminInfo
|
||||
): bool {
|
||||
$roleIds = array_map('intval', AdminRole::where('admin_id', $adminId)->column('role_id'));
|
||||
|
||||
if (in_array(1, $roleIds, true) && (int) $appointment->doctor_id !== $adminId) {
|
||||
return false;
|
||||
}
|
||||
if (in_array(2, $roleIds, true)) {
|
||||
$asst = $diag ? (int) $diag->assistant_id : 0;
|
||||
if ($asst !== $adminId) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!DataScopeService::isEnabled()) {
|
||||
return true;
|
||||
}
|
||||
$ids = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
if ($ids === []) {
|
||||
return false;
|
||||
}
|
||||
if ($ids === null) {
|
||||
return true;
|
||||
}
|
||||
$docId = (int) $appointment->doctor_id;
|
||||
$asstId = $diag ? (int) $diag->assistant_id : 0;
|
||||
|
||||
return in_array($docId, $ids, true)
|
||||
|| ($asstId > 0 && in_array($asstId, $ids, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台编辑挂号(预约日期/时段/类型/状态/备注/医助)
|
||||
*
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public static function adminEdit(array $params, int $adminId, array $adminInfo): bool
|
||||
{
|
||||
try {
|
||||
$id = (int) ($params['id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
self::setError('参数错误');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$appointment = Appointment::findOrEmpty($id);
|
||||
if ($appointment->isEmpty()) {
|
||||
self::setError('预约记录不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$diag = Diagnosis::where('id', (int) $appointment->patient_id)->whereNull('delete_time')->find();
|
||||
|
||||
if (!self::appointmentRowManageableByAdmin($appointment, $diag ?: null, $adminId, $adminInfo)) {
|
||||
self::setError('无权限编辑');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$period = trim((string) ($params['period'] ?? ''));
|
||||
if (!in_array($period, ['morning', 'afternoon', 'all'], true)) {
|
||||
self::setError('时段无效');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$appointmentDate = trim((string) ($params['appointment_date'] ?? ''));
|
||||
if ($appointmentDate === '') {
|
||||
self::setError('预约日期不能为空');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$rawTime = trim((string) ($params['appointment_time'] ?? ''));
|
||||
if ($rawTime === '') {
|
||||
self::setError('预约时间不能为空');
|
||||
|
||||
return false;
|
||||
}
|
||||
$appointmentTime = strlen($rawTime) === 5 ? $rawTime . ':00' : $rawTime;
|
||||
|
||||
$status = (int) ($params['status'] ?? 0);
|
||||
if ($status < 1 || $status > 4) {
|
||||
self::setError('状态无效');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$appointmentType = trim((string) ($params['appointment_type'] ?? ''));
|
||||
if (!in_array($appointmentType, ['video', 'text', 'phone'], true)) {
|
||||
self::setError('预约类型无效');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$remark = isset($params['remark']) ? trim((string) $params['remark']) : '';
|
||||
if (mb_strlen($remark) > 500) {
|
||||
self::setError('备注过长');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$chSrc = trim((string) ($params['channel_source'] ?? ''));
|
||||
if ($chSrc === '') {
|
||||
self::setError('请选择渠道来源');
|
||||
|
||||
return false;
|
||||
}
|
||||
$chDetail = trim((string) ($params['channel_source_detail'] ?? ''));
|
||||
$channelDictName = trim((string) (DictData::where('type_value', 'channels')
|
||||
->where('value', $chSrc)
|
||||
->value('name') ?? ''));
|
||||
if ($channelDictName !== '' && self::channelDictNameRequiresSourceDetail($channelDictName)) {
|
||||
if ($chDetail === '') {
|
||||
self::setError('请填写自媒体渠道补充信息');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (mb_strlen($chDetail) > 128) {
|
||||
self::setError('渠道补充说明过长');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$postAssistant = array_key_exists('assistant_id', $params);
|
||||
$assistantId = null;
|
||||
if ($postAssistant) {
|
||||
$assistantRaw = $params['assistant_id'];
|
||||
if ($assistantRaw !== null && $assistantRaw !== '') {
|
||||
$aid = (int) $assistantRaw;
|
||||
$assistantId = $aid > 0 ? $aid : null;
|
||||
}
|
||||
}
|
||||
|
||||
$patientId = (int) $appointment->patient_id;
|
||||
$doctorId = (int) $appointment->doctor_id;
|
||||
|
||||
if (in_array($status, [1, 4], true)) {
|
||||
$patientDup = Appointment::where('patient_id', $patientId)
|
||||
->where('appointment_date', $appointmentDate)
|
||||
->whereIn('status', [1, 4])
|
||||
->where('id', '<>', $id)
|
||||
->find();
|
||||
if ($patientDup) {
|
||||
self::setError('该诊单在所选日期已有其他有效挂号(已预约/已过号)');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($status === 1) {
|
||||
$slotDup = Appointment::where('doctor_id', $doctorId)
|
||||
->where('appointment_date', $appointmentDate)
|
||||
->where('appointment_time', $appointmentTime)
|
||||
->where('status', 1)
|
||||
->where('id', '<>', $id)
|
||||
->find();
|
||||
if ($slotDup) {
|
||||
self::setError('该医生在所选时段已被占用');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$beforeDate = (string) ($appointment->appointment_date ?? '');
|
||||
$beforeTimeRaw = (string) ($appointment->appointment_time ?? '');
|
||||
$beforeHm = strlen($beforeTimeRaw) >= 5 ? substr($beforeTimeRaw, 0, 5) : $beforeTimeRaw;
|
||||
$snap = $appointment->toArray();
|
||||
$beforePeriod = (string) ($snap['period'] ?? $snap['type'] ?? '');
|
||||
$beforeStatus = (int) $appointment->status;
|
||||
|
||||
$tblFields = Db::name('doctor_appointment')->getTableFields();
|
||||
$cols = is_array($tblFields) ? $tblFields : [];
|
||||
$channelErr = self::assertAppointmentChannelWritable($cols, $chSrc);
|
||||
if ($channelErr !== null) {
|
||||
self::setError($channelErr);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
|
||||
$saveData = [
|
||||
'appointment_date' => $appointmentDate,
|
||||
'appointment_time' => $appointmentTime,
|
||||
'appointment_type' => $appointmentType,
|
||||
'status' => $status,
|
||||
'remark' => $remark,
|
||||
'update_time' => time(),
|
||||
];
|
||||
if (in_array('assistant_id', $cols, true) && $postAssistant) {
|
||||
$saveData['assistant_id'] = $assistantId;
|
||||
}
|
||||
if (in_array('period', $cols, true)) {
|
||||
$saveData['period'] = $period;
|
||||
} elseif (in_array('type', $cols, true)) {
|
||||
$saveData['type'] = $period;
|
||||
}
|
||||
self::mergeAppointmentChannelIntoRow($saveData, $cols, $chSrc, $chDetail);
|
||||
|
||||
Db::name('doctor_appointment')->where('id', $id)->update($saveData);
|
||||
|
||||
Db::commit();
|
||||
|
||||
$hm = substr($appointmentTime, 0, 5);
|
||||
$summary = sprintf(
|
||||
'后台修改挂号 #%d:日期 %s %s→%s %s,时段 %s→%s,状态 %d→%d',
|
||||
$id,
|
||||
$beforeDate,
|
||||
$beforeHm,
|
||||
$appointmentDate,
|
||||
$hm,
|
||||
$beforePeriod !== '' ? $beforePeriod : '—',
|
||||
$period,
|
||||
$beforeStatus,
|
||||
$status
|
||||
);
|
||||
self::appendDiagnosisGuahaoLog($patientId, $id, $adminId, $adminInfo, 'admin_edit', $summary);
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊单列表维度:记录谁在后台发起挂号 / 取消挂号(写入失败不影响主流程)
|
||||
*/
|
||||
|
||||
@@ -2424,7 +2424,7 @@ class PrescriptionOrderLogic
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function fulfillmentStatusLabel(int $fs): string
|
||||
public static function fulfillmentStatusLabel(int $fs): string
|
||||
{
|
||||
$m = [
|
||||
1 => '待双审通过',
|
||||
@@ -2444,6 +2444,209 @@ class PrescriptionOrderLogic
|
||||
return $m[$fs] ?? ('状态' . (string) $fs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用:诊单医助在 admin_dept 中的部门路径(多部门「;」、路径内「 / 」)
|
||||
*/
|
||||
public static function formatAssistantDeptPathForExport(int $assistantAdminId): string
|
||||
{
|
||||
if ($assistantAdminId <= 0) {
|
||||
return '';
|
||||
}
|
||||
$deptIds = Db::name('admin_dept')->where('admin_id', $assistantAdminId)->column('dept_id');
|
||||
$pathSegments = [];
|
||||
foreach ($deptIds as $did) {
|
||||
$p = self::buildDeptPath((int) $did);
|
||||
if ($p !== '') {
|
||||
$pathSegments[] = $p;
|
||||
}
|
||||
}
|
||||
$pathSegments = array_values(array_unique($pathSegments));
|
||||
|
||||
return $pathSegments === [] ? '' : implode(';', $pathSegments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用:药品形态(丸剂 / 饮片+代煎|自煎等)
|
||||
*
|
||||
* @param array<string, mixed> $rx
|
||||
*/
|
||||
public static function formatMedicationFormForExport(array $rx): string
|
||||
{
|
||||
$type = trim((string) ($rx['prescription_type'] ?? ''));
|
||||
if ($type === '') {
|
||||
return '';
|
||||
}
|
||||
if ($type === '饮片') {
|
||||
$nd = (int) ($rx['need_decoction'] ?? 0);
|
||||
|
||||
return $type . ($nd === 1 ? '(代煎)' : '(自煎)');
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务订单列表导出:写入与 PrescriptionOrderLists::setExcelFields 对应的字段(export=2 时由列表类调用)
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $lists
|
||||
*/
|
||||
public static function appendPrescriptionOrderListExportRows(array &$lists, array $adminInfo): void
|
||||
{
|
||||
if ($lists === []) {
|
||||
return;
|
||||
}
|
||||
$diagIds = [];
|
||||
$rxIds = [];
|
||||
$poIds = [];
|
||||
foreach ($lists as $row) {
|
||||
$poIds[] = (int) ($row['id'] ?? 0);
|
||||
$d = (int) ($row['diagnosis_id'] ?? 0);
|
||||
if ($d > 0) {
|
||||
$diagIds[$d] = true;
|
||||
}
|
||||
$r = (int) ($row['prescription_id'] ?? 0);
|
||||
if ($r > 0) {
|
||||
$rxIds[$r] = true;
|
||||
}
|
||||
}
|
||||
$poIds = array_values(array_filter(array_unique($poIds), static fn (int $id): bool => $id > 0));
|
||||
$diagIdList = array_keys($diagIds);
|
||||
$rxIdList = array_keys($rxIds);
|
||||
|
||||
$diagById = [];
|
||||
if ($diagIdList !== []) {
|
||||
$diagRows = Diagnosis::whereIn('id', $diagIdList)->whereNull('delete_time')
|
||||
->field(['id', 'patient_name', 'gender', 'age', 'phone', 'assistant_id'])
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($diagRows as $dr) {
|
||||
$diagById[(int) $dr['id']] = $dr;
|
||||
}
|
||||
}
|
||||
|
||||
$rxById = [];
|
||||
if ($rxIdList !== []) {
|
||||
$rxRows = Prescription::whereIn('id', $rxIdList)->whereNull('delete_time')
|
||||
->field(['id', 'prescription_type', 'need_decoction', 'dose_unit'])
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($rxRows as $xr) {
|
||||
$rxById[(int) $xr['id']] = $xr;
|
||||
}
|
||||
}
|
||||
|
||||
$logRows = PrescriptionOrderLog::whereIn('prescription_order_id', $poIds)
|
||||
->whereIn('action', ['audit_rx_approve', 'audit_rx_reject', 'revoke_rx_audit'])
|
||||
->field(['prescription_order_id', 'id', 'action', 'admin_name', 'create_time'])
|
||||
->order('id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
$auditEffectiveByPo = [];
|
||||
foreach ($logRows as $lg) {
|
||||
$pid = (int) ($lg['prescription_order_id'] ?? 0);
|
||||
if ($pid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$act = (string) ($lg['action'] ?? '');
|
||||
if ($act === 'revoke_rx_audit') {
|
||||
$auditEffectiveByPo[$pid] = null;
|
||||
continue;
|
||||
}
|
||||
if ($act === 'audit_rx_approve' || $act === 'audit_rx_reject') {
|
||||
$auditEffectiveByPo[$pid] = $lg;
|
||||
}
|
||||
}
|
||||
|
||||
$assistantDeptCache = [];
|
||||
|
||||
foreach ($lists as &$item) {
|
||||
$poId = (int) ($item['id'] ?? 0);
|
||||
$fs = (int) ($item['fulfillment_status'] ?? 0);
|
||||
$item['export_fulfillment_status_text'] = self::fulfillmentStatusLabel($fs);
|
||||
$ct = (int) ($item['create_time'] ?? 0);
|
||||
$item['export_order_time'] = $ct > 0 ? date('Y-m-d H:i:s', $ct) : '';
|
||||
|
||||
$diagId = (int) ($item['diagnosis_id'] ?? 0);
|
||||
$dg = $diagById[$diagId] ?? null;
|
||||
$item['export_patient_name'] = \is_array($dg) ? (string) ($dg['patient_name'] ?? '') : '';
|
||||
if (\is_array($dg)) {
|
||||
$g = (int) ($dg['gender'] ?? 0);
|
||||
$item['export_patient_gender'] = $g === 1 ? '男' : '女';
|
||||
$item['export_patient_age'] = (string) ($dg['age'] ?? '');
|
||||
$item['export_patient_phone'] = (string) ($dg['phone'] ?? '');
|
||||
$astId = (int) ($dg['assistant_id'] ?? 0);
|
||||
if ($astId > 0) {
|
||||
if (!isset($assistantDeptCache[$astId])) {
|
||||
$assistantDeptCache[$astId] = self::formatAssistantDeptPathForExport($astId);
|
||||
}
|
||||
$item['export_assistant_dept'] = $assistantDeptCache[$astId];
|
||||
} else {
|
||||
$item['export_assistant_dept'] = '';
|
||||
}
|
||||
} else {
|
||||
$item['export_patient_gender'] = '';
|
||||
$item['export_patient_age'] = '';
|
||||
$item['export_patient_phone'] = '';
|
||||
$item['export_assistant_dept'] = '';
|
||||
}
|
||||
|
||||
$rxId = (int) ($item['prescription_id'] ?? 0);
|
||||
$rx = $rxById[$rxId] ?? [];
|
||||
$item['export_medication_form'] = self::formatMedicationFormForExport(\is_array($rx) ? $rx : []);
|
||||
|
||||
$item['export_channel'] = (string) ($item['service_channel'] ?? '');
|
||||
$md = $item['medication_days'] ?? null;
|
||||
$item['export_medication_days'] = $md !== null && $md !== '' ? (string) $md : '';
|
||||
|
||||
$amt = round((float) ($item['amount'] ?? 0), 2);
|
||||
$paid = round((float) ($item['linked_pay_paid_total'] ?? 0), 2);
|
||||
$item['export_amount'] = number_format($amt, 2, '.', '');
|
||||
$item['export_paid_amount'] = number_format($paid, 2, '.', '');
|
||||
$item['export_agency_collect'] = number_format(round($amt - $paid, 2), 2, '.', '');
|
||||
|
||||
$item['export_tracking_number'] = (string) ($item['tracking_number'] ?? '');
|
||||
$isGc = trim((string) ($item['gancao_reciperl_order_no'] ?? '')) !== '';
|
||||
$item['export_supply_mode'] = $isGc ? '甘草' : '自营';
|
||||
// 「处方成本」列:与列表/详情一致,取订单 internal_cost(含「测试价格」预报价写入;甘草/自营均导出)
|
||||
if (self::canViewInternalCost($adminInfo)) {
|
||||
$rawCost = $item['internal_cost'] ?? null;
|
||||
$item['export_gancao_prescription_cost'] = $rawCost !== null && $rawCost !== ''
|
||||
? number_format(round((float) $rawCost, 2), 2, '.', '')
|
||||
: '';
|
||||
} else {
|
||||
$item['export_gancao_prescription_cost'] = '';
|
||||
}
|
||||
|
||||
$isFu = (int) ($item['is_follow_up'] ?? 0) === 1;
|
||||
$item['export_first_visit_assistant'] = $isFu ? (string) ($item['prev_staff'] ?? '') : '';
|
||||
|
||||
$hit = (int) ($item['po_assign_snapshot_hit'] ?? 0);
|
||||
$rel = (int) ($item['po_assign_is_release'] ?? 0);
|
||||
$er = (int) ($item['po_assign_to_er_center'] ?? 0);
|
||||
if ($hit !== 1) {
|
||||
$item['export_center_label'] = '—';
|
||||
} elseif ($rel === 1) {
|
||||
$item['export_center_label'] = '已释放';
|
||||
} elseif ($er === 1) {
|
||||
$item['export_center_label'] = '二中心';
|
||||
} else {
|
||||
$item['export_center_label'] = '一中心';
|
||||
}
|
||||
|
||||
$paSt = (int) ($item['prescription_audit_status'] ?? 0);
|
||||
$aud = $auditEffectiveByPo[$poId] ?? null;
|
||||
if (($paSt === 1 || $paSt === 2) && \is_array($aud)) {
|
||||
$at = (int) ($aud['create_time'] ?? 0);
|
||||
$item['export_rx_audit_time'] = $at > 0 ? date('Y-m-d H:i:s', $at) : '';
|
||||
$item['export_rx_auditor'] = (string) ($aud['admin_name'] ?? '');
|
||||
} else {
|
||||
$item['export_rx_audit_time'] = '';
|
||||
$item['export_rx_auditor'] = '';
|
||||
}
|
||||
}
|
||||
unset($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成订单时把关联诊单的 assistant_id 清空,让患者回到「待分配」状态
|
||||
* 仅当该患者没有其它进行中的处方订单且无指派日志时才清空,避免误覆盖
|
||||
|
||||
@@ -21,9 +21,11 @@ class AppointmentValidate extends BaseValidate
|
||||
'doctor_id' => 'require|integer',
|
||||
'diagnosis_id' => 'require|integer',
|
||||
'appointment_date' => 'require|date',
|
||||
'period' => 'in:morning,afternoon,all',
|
||||
'appointment_time' => 'require',
|
||||
'appointment_type' => 'in:video,text,phone',
|
||||
'period' => 'in:morning,afternoon,all',
|
||||
'appointment_type' => 'require|in:video,text,phone',
|
||||
'status' => 'require|integer|between:1,4',
|
||||
'remark' => 'max:500',
|
||||
'channel_source' => 'require',
|
||||
'channel_source_detail' => 'max:128',
|
||||
'note_id' => 'require|integer',
|
||||
@@ -41,9 +43,12 @@ class AppointmentValidate extends BaseValidate
|
||||
'doctor_id' => '医生ID',
|
||||
'diagnosis_id' => '诊单ID',
|
||||
'appointment_date' => '预约日期',
|
||||
'period' => '时段',
|
||||
'appointment_time' => '预约时间',
|
||||
'period' => '时段',
|
||||
'appointment_type' => '预约类型',
|
||||
'status' => '状态',
|
||||
'remark' => '备注',
|
||||
'assistant_id' => '医助',
|
||||
'channel_source' => '渠道来源',
|
||||
'channel_source_detail' => '渠道补充说明',
|
||||
];
|
||||
@@ -125,4 +130,22 @@ class AppointmentValidate extends BaseValidate
|
||||
{
|
||||
return $this->only(['note_id', 'image_type', 'image_path']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台挂号编辑(消费者处方-挂号列表等)
|
||||
*/
|
||||
public function sceneAdminEdit()
|
||||
{
|
||||
return $this->only([
|
||||
'id',
|
||||
'appointment_date',
|
||||
'appointment_time',
|
||||
'period',
|
||||
'appointment_type',
|
||||
'status',
|
||||
'remark',
|
||||
'channel_source',
|
||||
'channel_source_detail',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user