first commit
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
namespace app\adminapi\logic\doctor;
|
||||
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\doctor\DoctorNote;
|
||||
use app\common\service\FileService;
|
||||
|
||||
class DoctorNoteLogic extends BaseLogic
|
||||
{
|
||||
/**
|
||||
* 按 diagnosis_id + 当天 find-or-create,追加 content / tongue_images / report_files
|
||||
*/
|
||||
public static function addOrAppend(array $params): bool
|
||||
{
|
||||
try {
|
||||
$diagnosisId = (int) $params['diagnosis_id'];
|
||||
$doctorId = (int) ($params['doctor_id'] ?? 0);
|
||||
$today = date('Y-m-d');
|
||||
$time = date('H:i');
|
||||
|
||||
$existing = DoctorNote::where('diagnosis_id', $diagnosisId)
|
||||
->where('note_date', $today)
|
||||
->whereNull('delete_time')
|
||||
->find();
|
||||
|
||||
$newContent = trim($params['content'] ?? '');
|
||||
$newImages = array_map([self::class, 'toRelativePath'], self::parseJsonArray($params['tongue_images'] ?? []));
|
||||
$newReports = array_map([self::class, 'toRelativePath'], self::parseJsonArray($params['report_files'] ?? []));
|
||||
|
||||
if ($existing) {
|
||||
$data = [];
|
||||
|
||||
if ($newContent !== '') {
|
||||
$prev = trim($existing->content ?? '');
|
||||
$line = "[{$time}] {$newContent}";
|
||||
$data['content'] = $prev !== '' ? ($prev . "\n" . $line) : $line;
|
||||
}
|
||||
|
||||
if (!empty($newImages)) {
|
||||
$prev = self::parseJsonArray($existing->tongue_images);
|
||||
$merged = array_values(array_unique(array_merge($prev, $newImages)));
|
||||
$data['tongue_images'] = json_encode($merged, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
if (!empty($newReports)) {
|
||||
$prev = self::parseJsonArray($existing->report_files);
|
||||
$merged = array_values(array_unique(array_merge($prev, $newReports)));
|
||||
$data['report_files'] = json_encode($merged, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
if (!empty($data)) {
|
||||
$existing->save($data);
|
||||
}
|
||||
} else {
|
||||
$content = $newContent !== '' ? "[{$time}] {$newContent}" : '';
|
||||
DoctorNote::create([
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'doctor_id' => $doctorId,
|
||||
'note_date' => $today,
|
||||
'content' => $content,
|
||||
'tongue_images' => !empty($newImages)
|
||||
? json_encode($newImages, JSON_UNESCAPED_UNICODE)
|
||||
: null,
|
||||
'report_files' => !empty($newReports)
|
||||
? json_encode($newReports, JSON_UNESCAPED_UNICODE)
|
||||
: null,
|
||||
]);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 diagnosis_id 获取备注列表(note_date DESC)
|
||||
*/
|
||||
public static function getByDiagnosis(int $diagnosisId, int $limit = 30): array
|
||||
{
|
||||
try {
|
||||
if ($diagnosisId <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$records = DoctorNote::where('diagnosis_id', $diagnosisId)
|
||||
->whereNull('delete_time')
|
||||
->order('note_date', 'desc')
|
||||
->limit($limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($records as &$record) {
|
||||
$record['tongue_images'] = array_map(function ($url) {
|
||||
return empty($url) ? $url : FileService::getFileUrl($url);
|
||||
}, self::parseJsonArray($record['tongue_images'] ?? []));
|
||||
$record['report_files'] = array_map(function ($url) {
|
||||
return empty($url) ? $url : FileService::getFileUrl($url);
|
||||
}, self::parseJsonArray($record['report_files'] ?? []));
|
||||
}
|
||||
|
||||
return $records;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除备注中的单张图片
|
||||
*/
|
||||
public static function deleteImage(int $noteId, string $imageType, string $imagePath): bool
|
||||
{
|
||||
try {
|
||||
$note = DoctorNote::where('id', $noteId)->whereNull('delete_time')->find();
|
||||
if (!$note) {
|
||||
self::setError('记录不存在');
|
||||
return false;
|
||||
}
|
||||
if (!in_array($imageType, ['tongue_images', 'report_files'])) {
|
||||
self::setError('类型无效');
|
||||
return false;
|
||||
}
|
||||
$images = self::parseJsonArray($note->$imageType);
|
||||
// 统一转为相对路径再匹配
|
||||
$targetPath = self::toRelativePath($imagePath);
|
||||
$images = array_values(array_filter($images, fn($url) => self::toRelativePath($url) !== $targetPath));
|
||||
$note->$imageType = empty($images) ? null : json_encode($images, JSON_UNESCAPED_UNICODE);
|
||||
$note->save();
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 聚合某诊单所有备注中的图片(供 DiagnosisLogic::detail 使用)
|
||||
*/
|
||||
public static function getAggregatedImages(int $diagnosisId): array
|
||||
{
|
||||
$records = DoctorNote::where('diagnosis_id', $diagnosisId)
|
||||
->whereNull('delete_time')
|
||||
->select();
|
||||
|
||||
$tongueImages = [];
|
||||
$reportFiles = [];
|
||||
foreach ($records as $r) {
|
||||
$tongueImages = array_merge($tongueImages, self::parseJsonArray($r->tongue_images));
|
||||
$reportFiles = array_merge($reportFiles, self::parseJsonArray($r->report_files));
|
||||
}
|
||||
|
||||
return [
|
||||
'tongue_images' => array_map(
|
||||
fn($u) => empty($u) ? $u : FileService::getFileUrl($u),
|
||||
array_values(array_unique($tongueImages))
|
||||
),
|
||||
'report_files' => array_map(
|
||||
fn($u) => empty($u) ? $u : FileService::getFileUrl($u),
|
||||
array_values(array_unique($reportFiles))
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果 URL 的域名是当前配置的存储域名则去掉,否则原样保留
|
||||
*/
|
||||
private static function toRelativePath(string $url): string
|
||||
{
|
||||
if (empty($url)) return $url;
|
||||
if (stripos($url, 'http://') !== 0 && stripos($url, 'https://') !== 0) {
|
||||
return $url;
|
||||
}
|
||||
// 获取当前存储域名
|
||||
$domain = self::getStorageDomain();
|
||||
if ($domain && stripos($url, rtrim($domain, '/')) === 0) {
|
||||
$relative = substr($url, strlen(rtrim($domain, '/')));
|
||||
return ltrim($relative, '/');
|
||||
}
|
||||
// 非当前存储域名,保留完整 URL
|
||||
return $url;
|
||||
}
|
||||
|
||||
private static function getStorageDomain(): string
|
||||
{
|
||||
$default = \app\common\service\ConfigService::get('storage', 'default', 'local');
|
||||
if ($default === 'local') {
|
||||
return request()->domain() . '/';
|
||||
}
|
||||
$storage = \app\common\service\ConfigService::get('storage', $default);
|
||||
return $storage ? ($storage['domain'] ?? '') : '';
|
||||
}
|
||||
|
||||
private static function parseJsonArray($value): array
|
||||
{
|
||||
if (is_array($value)) return $value;
|
||||
if (is_string($value)) {
|
||||
$decoded = json_decode($value, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 医生排班逻辑
|
||||
* Class RosterLogic
|
||||
* @package app\adminapi\logic\doctor
|
||||
*/
|
||||
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
|
||||
* @return array|bool
|
||||
*/
|
||||
public static function save(array $params)
|
||||
{
|
||||
try {
|
||||
$data = self::buildRowData($params);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 = self::buildRowData($roster);
|
||||
|
||||
$exists = Roster::where([
|
||||
['doctor_id', '=', $data['doctor_id']],
|
||||
['date', '=', $data['date']],
|
||||
['start_time', '=', $data['start_time']],
|
||||
['end_time', '=', $data['end_time']],
|
||||
])->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 (\InvalidArgumentException $e) {
|
||||
Db::rollback();
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
} 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();
|
||||
|
||||
$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,
|
||||
'remark' => $roster->remark,
|
||||
'create_time' => time(),
|
||||
'update_time' => time(),
|
||||
];
|
||||
|
||||
if (empty($data['start_time']) || empty($data['end_time'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dup = Roster::where([
|
||||
['doctor_id', '=', $data['doctor_id']],
|
||||
['date', '=', $data['date']],
|
||||
['start_time', '=', $data['start_time']],
|
||||
['end_time', '=', $data['end_time']],
|
||||
])->find();
|
||||
|
||||
if (!$dup) {
|
||||
Roster::create($data);
|
||||
}
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user