新增功能

This commit is contained in:
Your Name
2026-03-04 15:32:30 +08:00
parent a07e844c47
commit ab77f5488d
2266 changed files with 177942 additions and 3444 deletions
@@ -0,0 +1,399 @@
<?php
namespace app\adminapi\logic\doctor;
use app\common\logic\BaseLogic;
use app\common\model\doctor\Appointment;
use app\common\model\doctor\Roster;
use think\facade\Db;
/**
* 医生预约逻辑
* Class AppointmentLogic
* @package app\adminapi\logic\doctor
*/
class AppointmentLogic extends BaseLogic
{
/**
* @notes 获取可用时间段
* @param array $params
* @return array
*/
public static function getAvailableSlots(array $params)
{
try {
$doctorId = $params['doctor_id'];
$date = $params['appointment_date'];
$period = $params['period'] ?? 'all';
// 记录请求参数
\think\facade\Log::info('获取可用时段 - 请求参数', [
'doctor_id' => $doctorId,
'date' => $date,
'period' => $period
]);
// 1. 检查医生在该日期是否有出诊排班(只查询status=1的记录)
$rosters = Roster::where([
'doctor_id' => $doctorId,
'date' => $date,
'status' => 1 // 只查询出诊状态
])->select()->toArray();
// 记录查询到的排班
\think\facade\Log::info('查询到的排班数据', [
'doctor_id' => $doctorId,
'date' => $date,
'count' => count($rosters),
'rosters' => $rosters
]);
// 额外记录:查询该日期所有排班(用于调试)
$allRosters = Roster::where([
'doctor_id' => $doctorId,
'date' => $date
])->select()->toArray();
\think\facade\Log::info('该日期所有排班数据(包括非出诊)', [
'count' => count($allRosters),
'all_rosters' => $allRosters
]);
// 如果没有出诊排班,返回空数组
if (empty($rosters)) {
\think\facade\Log::warning('没有找到出诊排班', [
'doctor_id' => $doctorId,
'date' => $date
]);
return ['slots' => []];
}
// 2. 根据排班时段生成时间段
$slots = [];
foreach ($rosters as $roster) {
// 再次确认状态为出诊(双重检查)
if ($roster['status'] != 1) {
\think\facade\Log::warning('跳过非出诊排班', [
'roster_id' => $roster['id'],
'status' => $roster['status']
]);
continue;
}
$periodValue = $roster['period'];
\think\facade\Log::info('处理排班时段', [
'roster_id' => $roster['id'],
'period' => $periodValue,
'status' => $roster['status'],
'period_type' => gettype($periodValue)
]);
// 根据时段生成时间段(支持数字和字符串格式)
$startHour = null;
$endHour = null;
if ($periodValue == 1 || $periodValue === 'morning' || $periodValue === '上午') {
// 上午:9:00-12:00
$startHour = 9;
$endHour = 12;
\think\facade\Log::info('识别为上午时段', ['period' => $periodValue]);
} elseif ($periodValue == 2 || $periodValue === 'afternoon' || $periodValue === '下午') {
// 下午:14:00-18:00
$startHour = 14;
$endHour = 18;
\think\facade\Log::info('识别为下午时段', ['period' => $periodValue]);
} else {
\think\facade\Log::warning('未知的时段值,跳过此记录', [
'roster_id' => $roster['id'],
'period' => $periodValue,
'period_type' => gettype($periodValue)
]);
continue;
}
// 确保startHour和endHour已设置
if ($startHour === null || $endHour === null) {
\think\facade\Log::error('时段参数未正确设置', [
'roster_id' => $roster['id'],
'period' => $periodValue
]);
continue;
}
// 生成15分钟间隔的时间段
for ($hour = $startHour; $hour < $endHour; $hour++) {
for ($minute = 0; $minute < 60; $minute += 15) {
$time = sprintf('%02d:%02d', $hour, $minute);
$slots[] = [
'time' => $time,
'available' => true,
'quota' => 1,
'period' => $periodValue
];
}
}
}
\think\facade\Log::info('生成的时间段数量(去重前)', [
'count' => count($slots),
'sample_slots' => array_slice($slots, 0, 3)
]);
// 如果没有生成任何时间段,返回空数组
if (empty($slots)) {
\think\facade\Log::warning('没有生成任何时间段');
return ['slots' => []];
}
// 3. 去重:按时间去重(因为可能有重复的排班记录)
$uniqueSlots = [];
$timeMap = [];
foreach ($slots as $slot) {
$time = $slot['time'];
if (!isset($timeMap[$time])) {
$timeMap[$time] = true;
$uniqueSlots[] = $slot;
}
}
$slots = $uniqueSlots;
\think\facade\Log::info('生成的时间段数量(去重后)', [
'count' => count($slots),
'morning_slots' => count(array_filter($slots, function($s) {
return $s['time'] < '12:00';
})),
'afternoon_slots' => count(array_filter($slots, function($s) {
return $s['time'] >= '14:00';
})),
'first_3_slots' => array_slice($slots, 0, 3),
'last_3_slots' => array_slice($slots, -3)
]);
// 4. 查询已预约的时间段
$appointmentTimes = Appointment::where([
'doctor_id' => $doctorId,
'appointment_date' => $date,
'status' => 1 // 只查询有效预约
])->column('appointment_time');
// 将时间格式统一为 HH:MM(去掉秒)
$appointments = array_map(function($time) {
// 如果是 HH:MM:SS 格式,截取前5位
return substr($time, 0, 5);
}, $appointmentTimes);
// 4. 标记已占用的时间段
foreach ($slots as &$slot) {
if (in_array($slot['time'], $appointments)) {
$slot['available'] = false;
$slot['quota'] = 0;
}
}
// 5. 按时间排序
usort($slots, function($a, $b) {
return strcmp($a['time'], $b['time']);
});
return ['slots' => $slots];
} catch (\Exception $e) {
self::setError($e->getMessage());
return ['slots' => []];
}
}
/**
* @notes 创建预约
* @param array $params
* @return array|bool
*/
public static function create(array $params)
{
try {
Db::startTrans();
// 1. 检查时间段是否已被预约(每个时间段只能预约1次)
// 统一时间格式为 HH:MM:SS
$appointmentTime = strlen($params['appointment_time']) == 5
? $params['appointment_time'] . ':00'
: $params['appointment_time'];
$exists = Appointment::where([
'doctor_id' => $params['doctor_id'],
'appointment_date' => $params['appointment_date'],
'status' => 1
])->where('appointment_time', $appointmentTime)
->find();
if ($exists) {
self::setError('该时间段已被预约');
Db::rollback();
return false;
}
$data = [
'patient_id' => $params['patient_id'],
'doctor_id' => $params['doctor_id'],
'roster_id' => 0, // 不再关联排班表
'appointment_date' => $params['appointment_date'],
'period' => $params['period'] ?? 'all',
'appointment_time' => $appointmentTime,
'appointment_type' => $params['appointment_type'] ?? 'video',
'remark' => $params['remark'] ?? '',
'status' => 1,
'create_time' => time(),
'update_time' => time(),
];
$appointment = Appointment::create($data);
Db::commit();
return ['id' => $appointment->id];
} catch (\Exception $e) {
Db::rollback();
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 取消预约
* @param array $params
* @return bool
*/
public static function cancel(array $params)
{
try {
$appointment = Appointment::findOrEmpty($params['id']);
if ($appointment->isEmpty()) {
self::setError('预约记录不存在');
return false;
}
$appointment->status = 2; // 已取消
$appointment->update_time = time();
$appointment->save();
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 预约详情
* @param array $params
* @return array
*/
public static function detail(array $params)
{
$appointment = Appointment::alias('a')
->leftJoin('la_user u', 'a.patient_id = u.id')
->leftJoin('la_admin ad', 'a.doctor_id = ad.id')
->field('a.*, u.nickname as patient_name, u.mobile as patient_phone, ad.name as doctor_name')
->where('a.id', $params['id'])
->findOrEmpty()
->toArray();
if (empty($appointment)) {
return [];
}
// 添加状态和类型描述
$statusMap = [
1 => '已预约',
2 => '已取消',
3 => '已完成',
];
$appointment['status_desc'] = $statusMap[$appointment['status']] ?? '未知';
$typeMap = [
'video' => '视频问诊',
'text' => '图文问诊',
'phone' => '电话问诊',
];
$appointment['appointment_type_desc'] = $typeMap[$appointment['appointment_type']] ?? '未知';
// 格式化时间戳为日期时间
if (isset($appointment['create_time']) && is_numeric($appointment['create_time'])) {
$appointment['create_time'] = date('Y-m-d H:i:s', $appointment['create_time']);
}
if (isset($appointment['update_time']) && is_numeric($appointment['update_time'])) {
$appointment['update_time'] = date('Y-m-d H:i:s', $appointment['update_time']);
}
return $appointment;
}
/**
* @notes 获取医生某天的可用号源总数
* @param int $doctorId
* @param string $date
* @return int
*/
public static function getDoctorAvailability($doctorId, $date)
{
try {
$totalAvailable = 0;
// 查询上午和下午的排班
$rosters = Roster::where([
'doctor_id' => $doctorId,
'date' => $date,
'status' => 1 // 出诊
])->select();
foreach ($rosters as $roster) {
// 查询该时段已预约数量
$appointmentCount = Appointment::where([
'doctor_id' => $doctorId,
'appointment_date' => $date,
'period' => $roster->period,
'status' => 1
])->count();
// 计算剩余号源
$remaining = $roster->quota - $appointmentCount;
if ($remaining > 0) {
$totalAvailable += $remaining;
}
}
return $totalAvailable;
} catch (\Exception $e) {
return 0;
}
}
/**
* @notes 完成预约
* @param array $params
* @return bool
*/
public static function complete(array $params)
{
try {
$appointment = Appointment::findOrEmpty($params['id']);
if ($appointment->isEmpty()) {
self::setError('预约记录不存在');
return false;
}
if ($appointment->status != 1) {
self::setError('该预约已处理,无法完成');
return false;
}
$appointment->status = 3; // 已完成
$appointment->update_time = time();
$appointment->save();
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
}
@@ -0,0 +1,198 @@
<?php
namespace app\adminapi\logic\doctor;
use app\common\logic\BaseLogic;
use app\common\model\doctor\Roster;
use think\facade\Db;
/**
* 医生排班逻辑
* Class RosterLogic
* @package app\adminapi\logic\doctor
*/
class RosterLogic extends BaseLogic
{
/**
* @notes 保存排班
* @param array $params
* @return array|bool
*/
public static function save(array $params)
{
try {
$data = [
'doctor_id' => $params['doctor_id'],
'date' => $params['date'],
'period' => $params['period'],
'status' => $params['status'],
'quota' => $params['quota'] ?? 0,
'max_patients' => $params['max_patients'] ?? 0,
'remark' => $params['remark'] ?? '',
];
if (isset($params['id']) && $params['id']) {
// 更新
$data['update_time'] = time();
Roster::where('id', $params['id'])->update($data);
return ['id' => $params['id']];
} else {
// 新增
$data['create_time'] = time();
$data['update_time'] = time();
$roster = Roster::create($data);
return ['id' => $roster->id];
}
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 删除排班
* @param array $params
* @return bool
*/
public static function delete(array $params)
{
try {
Roster::destroy($params['id']);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 排班详情
* @param array $params
* @return array
*/
public static function detail(array $params)
{
return Roster::findOrEmpty($params['id'])->toArray();
}
/**
* @notes 批量保存排班
* @param array $params
* @return array|bool
*/
public static function batchSave(array $params)
{
try {
Db::startTrans();
$successCount = 0;
$updateCount = 0;
$createCount = 0;
foreach ($params['rosters'] as $roster) {
$data = [
'doctor_id' => $roster['doctor_id'],
'date' => $roster['date'],
'period' => $roster['period'],
'status' => $roster['status'],
'quota' => $roster['quota'] ?? 0,
'max_patients' => $roster['max_patients'] ?? 0,
'remark' => $roster['remark'] ?? '',
];
// 检查是否已存在
$exists = Roster::where([
['doctor_id', '=', $data['doctor_id']],
['date', '=', $data['date']],
['period', '=', $data['period']],
])->find();
if ($exists) {
// 更新
$data['update_time'] = time();
$exists->save($data);
$updateCount++;
} else {
// 新增
$data['create_time'] = time();
$data['update_time'] = time();
Roster::create($data);
$createCount++;
}
$successCount++;
}
Db::commit();
return [
'success_count' => $successCount,
'create_count' => $createCount,
'update_count' => $updateCount,
];
} catch (\Exception $e) {
Db::rollback();
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 复制排班
* @param array $params
* @return bool
*/
public static function copy(array $params)
{
try {
Db::startTrans();
// 获取源排班数据
$where = [
['date', 'between', [$params['source_start_date'], $params['source_end_date']]]
];
if (isset($params['doctor_id']) && $params['doctor_id']) {
$where[] = ['doctor_id', '=', $params['doctor_id']];
}
$sourceRosters = Roster::where($where)->select();
// 计算日期差
$sourceDays = (strtotime($params['source_end_date']) - strtotime($params['source_start_date'])) / 86400;
$targetDays = (strtotime($params['target_start_date']) - strtotime($params['source_start_date'])) / 86400;
foreach ($sourceRosters as $roster) {
$newDate = date('Y-m-d', strtotime($roster->date) + ($targetDays * 86400));
$data = [
'doctor_id' => $roster->doctor_id,
'date' => $newDate,
'period' => $roster->period,
'status' => $roster->status,
'quota' => $roster->quota,
'max_patients' => $roster->max_patients,
'remark' => $roster->remark,
'create_time' => time(),
'update_time' => time(),
];
// 检查目标日期是否已存在排班
$exists = Roster::where([
['doctor_id', '=', $data['doctor_id']],
['date', '=', $data['date']],
['period', '=', $data['period']],
])->find();
if (!$exists) {
Roster::create($data);
}
}
Db::commit();
return true;
} catch (\Exception $e) {
Db::rollback();
self::setError($e->getMessage());
return false;
}
}
}