This commit is contained in:
Your Name
2026-03-27 18:06:12 +08:00
parent 9160c36735
commit 099bc1dd22
645 changed files with 276473 additions and 957 deletions
@@ -5,6 +5,7 @@ namespace app\adminapi\logic\doctor;
use app\common\logic\BaseLogic;
use app\common\model\doctor\Appointment;
use app\common\model\doctor\Roster;
use app\common\service\doctor\RosterSegmentService;
use app\common\model\tcm\Diagnosis;
use app\common\model\auth\Admin;
use app\api\logic\ChatNotifyLogic;
@@ -70,72 +71,46 @@ class AppointmentLogic extends BaseLogic
return ['slots' => []];
}
// 2. 根据排班时段生成时间段
// 2. 按每段排班的起止时间与号源间隔生成时间段
$slots = [];
foreach ($rosters as $roster) {
// 再次确认状态为出诊(双重检查)
if ($roster['status'] != 1) {
if ((int) $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
'status' => $roster['status'],
]);
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
];
}
$window = RosterSegmentService::resolveWindow($roster);
if ($window === null) {
\think\facade\Log::warning('无法解析排班起止时间,跳过', [
'roster_id' => $roster['id'],
'period' => $roster['period'] ?? null,
]);
continue;
}
[$startHm, $endHm] = $window;
$slotMinutes = RosterSegmentService::normalizeSlotMinutes($roster['slot_minutes'] ?? 15);
$times = RosterSegmentService::generateSlotTimes($startHm, $endHm, $slotMinutes);
$times = RosterSegmentService::applyQuotaCap($times, (int) ($roster['quota'] ?? 0));
\think\facade\Log::info('处理排班时段', [
'roster_id' => $roster['id'],
'window' => $window,
'slot_minutes' => $slotMinutes,
'slot_count' => count($times),
]);
foreach ($times as $time) {
$slots[] = [
'time' => $time,
'available' => true,
'quota' => 1,
'period' => $roster['period'] ?? 'segment',
];
}
}
@@ -164,14 +139,8 @@ class AppointmentLogic extends BaseLogic
\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)
'last_3_slots' => array_slice($slots, -3),
]);
// 4. 查询已预约的时间段
@@ -237,6 +206,7 @@ class AppointmentLogic extends BaseLogic
}
$data = [
'patient_id' => $params['patient_id'],
'assistant_id'=>$params['assistant_id'],
'doctor_id' => $params['doctor_id'],
'roster_id' => 0, // 不再关联排班表
'appointment_date' => $params['appointment_date'],
@@ -244,6 +214,7 @@ class AppointmentLogic extends BaseLogic
'appointment_time' => $appointmentTime,
'appointment_type' => $params['appointment_type'] ?? 'video',
'remark' => $params['remark'] ?? '',
'channel_source' => $params['channel_source'] ?? '',
'status' => 1,
'create_time' => time(),
'update_time' => time(),
@@ -339,28 +310,41 @@ class AppointmentLogic extends BaseLogic
public static function getDoctorAvailability($doctorId, $date)
{
try {
$totalAvailable = 0;
// 查询上午和下午的排班
$rosters = Roster::where([
'doctor_id' => $doctorId,
'date' => $date,
'status' => 1 // 出诊
])->select();
'status' => 1,
])->select()->toArray();
if (empty($rosters)) {
return 0;
}
$bookedHm = Appointment::where([
'doctor_id' => $doctorId,
'appointment_date' => $date,
'status' => 1,
])->column('appointment_time');
$bookedHm = array_map(static function ($t) {
return substr((string) $t, 0, 5);
}, $bookedHm);
$bookedSet = array_fill_keys($bookedHm, true);
$totalAvailable = 0;
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;
$window = RosterSegmentService::resolveWindow($roster);
if ($window === null) {
continue;
}
[$startHm, $endHm] = $window;
$slotMinutes = RosterSegmentService::normalizeSlotMinutes($roster['slot_minutes'] ?? 15);
$times = RosterSegmentService::generateSlotTimes($startHm, $endHm, $slotMinutes);
$times = RosterSegmentService::applyQuotaCap($times, (int) ($roster['quota'] ?? 0));
foreach ($times as $t) {
if (empty($bookedSet[$t])) {
++$totalAvailable;
}
}
}
@@ -0,0 +1,81 @@
<?php
namespace app\adminapi\logic\doctor;
use app\common\logic\BaseLogic;
use app\common\model\doctor\Medicine;
use app\common\service\doctor\MedicineNameAbbrService;
/**
* 药品库逻辑层
*/
class MedicineLogic extends BaseLogic
{
/**
* 添加药品
*/
public static function add(array $params): bool
{
try {
Medicine::create([
'name' => $params['name'],
'name_pinyin_abbr' => MedicineNameAbbrService::build($params['name']),
'supplier' => $params['supplier'],
'unit' => $params['unit'],
'settlement_price' => $params['settlement_price'],
'retail_price' => $params['retail_price'],
'stock' => $params['stock'] ?? 0,
'image' => $params['image'] ?? '',
'status' => $params['status'] ?? 1,
'remark' => $params['remark'] ?? '',
]);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* 编辑药品
*/
public static function edit(array $params): bool
{
try {
Medicine::update([
'id' => $params['id'],
'name' => $params['name'],
'name_pinyin_abbr' => MedicineNameAbbrService::build($params['name']),
'supplier' => $params['supplier'],
'unit' => $params['unit'],
'settlement_price' => $params['settlement_price'],
'retail_price' => $params['retail_price'],
'stock' => $params['stock'] ?? 0,
'image' => $params['image'] ?? '',
'status' => $params['status'] ?? 1,
'remark' => $params['remark'] ?? '',
]);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* 删除药品
*/
public static function delete(array $params): bool
{
Medicine::destroy($params['id']);
return true;
}
/**
* 药品详情
*/
public static function detail(array $params): array
{
return Medicine::findOrEmpty($params['id'])->toArray();
}
}
+133 -49
View File
@@ -4,6 +4,7 @@ namespace app\adminapi\logic\doctor;
use app\common\logic\BaseLogic;
use app\common\model\doctor\Roster;
use app\common\service\doctor\RosterSegmentService;
use think\facade\Db;
/**
@@ -13,6 +14,80 @@ use think\facade\Db;
*/
class RosterLogic extends BaseLogic
{
/**
* 组装单条排班数据(period 缺省为 segment
*/
protected static function buildRowData(array $params): array
{
$period = $params['period'] ?? '';
if (!in_array($period, ['morning', 'afternoon', 'night', 'segment'], true)) {
$period = 'segment';
}
$start = trim((string) ($params['start_time'] ?? ''));
$end = trim((string) ($params['end_time'] ?? ''));
$status = (int) $params['status'];
$slotMinutes = RosterSegmentService::normalizeSlotMinutes($params['slot_minutes'] ?? 15);
if ($status === 1) {
if ($start === '' || $end === '') {
throw new \InvalidArgumentException('出诊须填写接诊开始与结束时间');
}
if ($start >= $end) {
throw new \InvalidArgumentException('结束时间须晚于开始时间');
}
} else {
if ($start === '' || $end === '') {
throw new \InvalidArgumentException('请填写时段开始与结束时间');
}
if ($start >= $end) {
throw new \InvalidArgumentException('结束时间须晚于开始时间');
}
}
$shiftType = $params['shift_type'] ?? '';
if ($shiftType !== '' && !in_array($shiftType, ['day', 'night'], true)) {
$shiftType = '';
}
$quota = (int) ($params['quota'] ?? 0);
if ($status !== 1) {
$quota = 0;
}
return [
'doctor_id' => (int) $params['doctor_id'],
'date' => $params['date'],
'period' => $period,
'start_time' => $start,
'end_time' => $end,
'shift_type' => $shiftType !== '' ? $shiftType : null,
'slot_minutes' => $slotMinutes,
'status' => $status,
'quota' => $quota,
'max_patients' => $status === 1 ? (int) ($params['max_patients'] ?? 0) : 0,
'remark' => (string) ($params['remark'] ?? ''),
];
}
/**
* 是否存在相同医生、日期、起止时间的记录(排除指定 id)
*/
protected static function duplicateExists(int $doctorId, string $date, string $start, string $end, ?int $excludeId = null): bool
{
$q = Roster::where([
['doctor_id', '=', $doctorId],
['date', '=', $date],
['start_time', '=', $start],
['end_time', '=', $end],
]);
if ($excludeId) {
$q->where('id', '<>', $excludeId);
}
return (bool) $q->find();
}
/**
* @notes 保存排班
* @param array $params
@@ -21,30 +96,33 @@ class RosterLogic extends BaseLogic
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'] ?? '',
];
$data = self::buildRowData($params);
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];
if (self::duplicateExists($data['doctor_id'], $data['date'], $data['start_time'], $data['end_time'], !empty($params['id']) ? (int) $params['id'] : null)) {
self::setError('该医生在同一天已存在相同的接诊时段');
return false;
}
if (!empty($params['id'])) {
$data['update_time'] = time();
Roster::where('id', (int) $params['id'])->update($data);
return ['id' => (int) $params['id']];
}
$data['create_time'] = time();
$data['update_time'] = time();
$roster = Roster::create($data);
return ['id' => $roster->id];
} catch (\InvalidArgumentException $e) {
self::setError($e->getMessage());
return false;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
@@ -58,9 +136,11 @@ class RosterLogic extends BaseLogic
{
try {
Roster::destroy($params['id']);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
@@ -84,53 +164,50 @@ class RosterLogic extends BaseLogic
{
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'] ?? '',
];
// 检查是否已存在
foreach ($params['rosters'] as $roster) {
$data = self::buildRowData($roster);
$exists = Roster::where([
['doctor_id', '=', $data['doctor_id']],
['date', '=', $data['date']],
['period', '=', $data['period']],
['start_time', '=', $data['start_time']],
['end_time', '=', $data['end_time']],
])->find();
if ($exists) {
// 更新
$data['update_time'] = time();
$exists->save($data);
$updateCount++;
++$updateCount;
} else {
// 新增
$data['create_time'] = time();
$data['update_time'] = time();
Roster::create($data);
$createCount++;
++$createCount;
}
$successCount++;
++$successCount;
}
Db::commit();
return [
'success_count' => $successCount,
'create_count' => $createCount,
'update_count' => $updateCount,
];
} catch (\InvalidArgumentException $e) {
Db::rollback();
self::setError($e->getMessage());
return false;
} catch (\Exception $e) {
Db::rollback();
self::setError($e->getMessage());
return false;
}
}
@@ -145,28 +222,29 @@ class RosterLogic extends BaseLogic
try {
Db::startTrans();
// 获取源排班数据
$where = [
['date', 'between', [$params['source_start_date'], $params['source_end_date']]]
['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,
'start_time' => $roster->start_time,
'end_time' => $roster->end_time,
'shift_type' => $roster->shift_type,
'slot_minutes' => $roster->slot_minutes ?: 15,
'status' => $roster->status,
'quota' => $roster->quota,
'max_patients' => $roster->max_patients,
@@ -175,23 +253,29 @@ class RosterLogic extends BaseLogic
'update_time' => time(),
];
// 检查目标日期是否已存在排班
$exists = Roster::where([
if (empty($data['start_time']) || empty($data['end_time'])) {
continue;
}
$dup = Roster::where([
['doctor_id', '=', $data['doctor_id']],
['date', '=', $data['date']],
['period', '=', $data['period']],
['start_time', '=', $data['start_time']],
['end_time', '=', $data['end_time']],
])->find();
if (!$exists) {
if (!$dup) {
Roster::create($data);
}
}
Db::commit();
return true;
} catch (\Exception $e) {
Db::rollback();
self::setError($e->getMessage());
return false;
}
}