first commit
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
|
||||
namespace app\adminapi\lists;
|
||||
|
||||
|
||||
use app\common\lists\BaseDataLists;
|
||||
|
||||
/**
|
||||
* 管理员模块数据列表基类
|
||||
* Class BaseAdminDataLists
|
||||
* @package app\adminapi\lists
|
||||
*/
|
||||
abstract class BaseAdminDataLists extends BaseDataLists
|
||||
{
|
||||
protected array $adminInfo;
|
||||
protected int $adminId;
|
||||
|
||||
protected function initAdminIdentity(): void
|
||||
{
|
||||
$this->adminInfo = $this->request->adminInfo;
|
||||
$this->adminId = $this->request->adminId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace app\adminapi\lists;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\model\Fan;
|
||||
use app\common\model\FanVisitRecord;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
|
||||
class FanLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['name', 'phone'],
|
||||
'=' => ['gender', 'status'],
|
||||
];
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = Fan::where($this->searchWhere)
|
||||
->field(['id', 'name', 'phone', 'id_card', 'age', 'gender', 'remark', 'creator_id', 'creator_name', 'status', 'create_time', 'update_time'])
|
||||
->append(['gender_desc', 'status_desc'])
|
||||
->order(['id' => 'desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$fanIds = array_column($lists, 'id');
|
||||
if (!empty($fanIds)) {
|
||||
$visitCounts = FanVisitRecord::where('fan_id', 'in', $fanIds)
|
||||
->where('delete_time', null)
|
||||
->group('fan_id')
|
||||
->column('count(*) as cnt', 'fan_id');
|
||||
|
||||
foreach ($lists as &$item) {
|
||||
$item['visit_count'] = $visitCounts[$item['id']] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return Fan::where($this->searchWhere)->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\article;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\ListsSortInterface;
|
||||
use app\common\model\article\ArticleCate;
|
||||
|
||||
/**
|
||||
* 资讯分类列表
|
||||
* Class ArticleCateLists
|
||||
* @package app\adminapi\lists\article
|
||||
*/
|
||||
class ArticleCateLists extends BaseAdminDataLists implements ListsSearchInterface, ListsSortInterface
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
* @return array
|
||||
* @author heshihu
|
||||
* @date 2022/2/8 18:39
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 设置支持排序字段
|
||||
* @return array
|
||||
* @author heshihu
|
||||
* @date 2022/2/9 15:11
|
||||
*/
|
||||
public function setSortFields(): array
|
||||
{
|
||||
return ['create_time' => 'create_time', 'id' => 'id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 设置默认排序
|
||||
* @return array
|
||||
* @author heshihu
|
||||
* @date 2022/2/9 15:08
|
||||
*/
|
||||
public function setDefaultOrder(): array
|
||||
{
|
||||
return ['sort' => 'desc','id' => 'desc'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取管理列表
|
||||
* @return array
|
||||
* @author heshihu
|
||||
* @date 2022/2/21 17:11
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$ArticleCateLists = ArticleCate::where($this->searchWhere)
|
||||
->append(['is_show_desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->order($this->sortOrder)
|
||||
->append(['article_count'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $ArticleCateLists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author heshihu
|
||||
* @date 2022/2/9 15:12
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return ArticleCate::where($this->searchWhere)->count();
|
||||
}
|
||||
|
||||
public function extend()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\article;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\ListsSortInterface;
|
||||
use app\common\model\article\Article;
|
||||
|
||||
/**
|
||||
* 资讯列表
|
||||
* Class ArticleLists
|
||||
* @package app\adminapi\lists\article
|
||||
*/
|
||||
class ArticleLists extends BaseAdminDataLists implements ListsSearchInterface, ListsSortInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
* @return array
|
||||
* @author heshihu
|
||||
* @date 2022/2/8 18:39
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['title'],
|
||||
'=' => ['cid', 'is_show']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 设置支持排序字段
|
||||
* @return array
|
||||
* @author heshihu
|
||||
* @date 2022/2/9 15:11
|
||||
*/
|
||||
public function setSortFields(): array
|
||||
{
|
||||
return ['create_time' => 'create_time', 'id' => 'id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 设置默认排序
|
||||
* @return array
|
||||
* @author heshihu
|
||||
* @date 2022/2/9 15:08
|
||||
*/
|
||||
public function setDefaultOrder(): array
|
||||
{
|
||||
return ['sort' => 'desc', 'id' => 'desc'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取管理列表
|
||||
* @return array
|
||||
* @author heshihu
|
||||
* @date 2022/2/21 17:11
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$ArticleLists = Article::where($this->searchWhere)
|
||||
->append(['cate_name', 'click'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->order($this->sortOrder)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $ArticleLists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author heshihu
|
||||
* @date 2022/2/9 15:12
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return Article::where($this->searchWhere)->count();
|
||||
}
|
||||
|
||||
public function extend()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\auth;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsExcelInterface;
|
||||
use app\common\lists\ListsExtendInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\ListsSortInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\auth\AdminRole;
|
||||
use app\common\model\auth\SystemRole;
|
||||
use app\common\model\dept\Dept;
|
||||
use app\common\model\dept\Jobs;
|
||||
use app\common\model\doctor\Appointment;
|
||||
use app\common\model\tcm\Diagnosis;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 管理员列表
|
||||
* Class AdminLists
|
||||
* @package app\adminapi\lists\auth
|
||||
*/
|
||||
class AdminLists extends BaseAdminDataLists implements ListsExtendInterface, ListsSearchInterface, ListsSortInterface,ListsExcelInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
/**
|
||||
* @notes 设置导出字段
|
||||
* @return string[]
|
||||
* @author 段誉
|
||||
* @date 2021/12/29 10:08
|
||||
*/
|
||||
public function setExcelFields(): array
|
||||
{
|
||||
return [
|
||||
'account' => '账号',
|
||||
'name' => '名称',
|
||||
'role_name' => '角色',
|
||||
'dept_name' => '部门',
|
||||
'create_time' => '创建时间',
|
||||
'login_time' => '最近登录时间',
|
||||
'login_ip' => '最近登录IP',
|
||||
'disable_desc' => '状态',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 设置导出文件名
|
||||
* @return string
|
||||
* @author 段誉
|
||||
* @date 2021/12/29 10:08
|
||||
*/
|
||||
public function setFileName(): string
|
||||
{
|
||||
return '管理员列表';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
* @return \string[][]
|
||||
* @author 段誉
|
||||
* @date 2021/12/29 10:07
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['name', 'account'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 设置支持排序字段
|
||||
* @return string[]
|
||||
* @author 段誉
|
||||
* @date 2021/12/29 10:07
|
||||
* @remark 格式: ['前端传过来的字段名' => '数据库中的字段名'];
|
||||
*/
|
||||
public function setSortFields(): array
|
||||
{
|
||||
return ['create_time' => 'create_time', 'id' => 'id'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 设置默认排序
|
||||
* @return string[]
|
||||
* @author 段誉
|
||||
* @date 2021/12/29 10:06
|
||||
*/
|
||||
public function setDefaultOrder(): array
|
||||
{
|
||||
return ['id' => 'desc'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 查询条件
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2022/11/29 11:33
|
||||
*/
|
||||
public function queryWhere()
|
||||
{
|
||||
$where = [];
|
||||
$progressBoard = (int) ($this->params['progress_board'] ?? 0) === 1;
|
||||
|
||||
if ($progressBoard) {
|
||||
// 面诊进度:固定只拉「医生」角色且未禁用,忽略客户端篡改的 role_id
|
||||
$adminIds = array_map('intval', AdminRole::where('role_id', 1)->column('admin_id'));
|
||||
|
||||
// 数据范围:医生与业务/医助通常不在同一部门,按 admin id 直接取交集会全空。
|
||||
// 这里改为「按当前账号可见的挂号反查涉及的医生」,与 AppointmentLists 的可见性一致:
|
||||
// 可见挂号 = a.doctor_id ∈ visible OR diagnosis.assistant_id ∈ visible
|
||||
$visibleIds = $this->getDataScopeVisibleAdminIds();
|
||||
if ($visibleIds !== null) {
|
||||
if ($visibleIds === [] || $adminIds === []) {
|
||||
$adminIds = [];
|
||||
} else {
|
||||
$aTbl = (new Appointment())->getTable();
|
||||
$dTbl = (new Diagnosis())->getTable();
|
||||
$inList = implode(',', array_map('intval', $visibleIds));
|
||||
$rows = Db::query(
|
||||
"SELECT DISTINCT a.doctor_id FROM `{$aTbl}` a "
|
||||
. "LEFT JOIN `{$dTbl}` u ON a.patient_id = u.id "
|
||||
. "WHERE (a.doctor_id IN ({$inList}) "
|
||||
. " OR (u.id IS NOT NULL AND u.delete_time IS NULL AND u.assistant_id IN ({$inList})))"
|
||||
);
|
||||
$apptDoctorIds = array_filter(array_map(static function ($r): int {
|
||||
return (int) ($r['doctor_id'] ?? 0);
|
||||
}, $rows ?: []));
|
||||
$adminIds = array_values(array_intersect($adminIds, $apptDoctorIds));
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($adminIds)) {
|
||||
$where[] = ['id', 'in', $adminIds];
|
||||
} else {
|
||||
// 数据范围下没有可见医生:强制空集
|
||||
$where[] = ['id', '=', 0];
|
||||
}
|
||||
$where[] = ['disable', '=', 0];
|
||||
} else {
|
||||
if (isset($this->params['role_id']) && $this->params['role_id'] != '') {
|
||||
$adminIds = AdminRole::where('role_id', $this->params['role_id'])->column('admin_id');
|
||||
if (!empty($adminIds)) {
|
||||
$where[] = ['id', 'in', $adminIds];
|
||||
}
|
||||
}
|
||||
// 排除禁止登录(disable=1),医生选择器等场景
|
||||
if ((int) ($this->params['exclude_disabled'] ?? 0) === 1) {
|
||||
$where[] = ['disable', '=', 0];
|
||||
}
|
||||
}
|
||||
|
||||
// 数据范围:仅当调用方主动传 apply_data_scope=1 时启用
|
||||
// (管理员表被多场景共用:医生/医助选择器、看板、组织架构等;不主动启用避免误伤)
|
||||
if ((int) ($this->params['apply_data_scope'] ?? 0) === 1) {
|
||||
$visibleIds = $this->getDataScopeVisibleAdminIds();
|
||||
if ($visibleIds !== null) {
|
||||
if ($visibleIds === []) {
|
||||
$where[] = ['id', '=', 0]; // 强制空
|
||||
} else {
|
||||
$where[] = ['id', 'in', $visibleIds];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取管理列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2021/12/29 10:05
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$field = [
|
||||
'id', 'name', 'account', 'create_time', 'disable', 'root',
|
||||
'login_time', 'login_ip', 'multipoint_login', 'avatar',
|
||||
'gender', 'age', 'phone', 'title', 'department',
|
||||
'specialty', 'education', 'experience', 'honors'
|
||||
];
|
||||
|
||||
$adminLists = Admin::field($field)
|
||||
->where($this->searchWhere)
|
||||
->where($this->queryWhere())
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->order($this->sortOrder)
|
||||
->append(['role_id', 'dept_id', 'jobs_id', 'disable_desc'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 角色数组('角色id'=>'角色名称')
|
||||
$roleLists = SystemRole::column('name', 'id');
|
||||
// 部门列表
|
||||
$deptLists = Dept::column('name', 'id');
|
||||
// 岗位列表
|
||||
$jobsLists = Jobs::column('name', 'id');
|
||||
|
||||
//管理员列表增加角色名称
|
||||
foreach ($adminLists as $k => $v) {
|
||||
$roleName = '';
|
||||
if ($v['root'] == 1) {
|
||||
$roleName = '系统管理员';
|
||||
} else {
|
||||
foreach ($v['role_id'] as $roleId) {
|
||||
$roleName .= $roleLists[$roleId] ?? '';
|
||||
$roleName .= '/';
|
||||
}
|
||||
}
|
||||
|
||||
$deptName = '';
|
||||
foreach ($v['dept_id'] as $deptId) {
|
||||
$deptName .= $deptLists[$deptId] ?? '';
|
||||
$deptName .= '/';
|
||||
}
|
||||
|
||||
$jobsName = '';
|
||||
foreach ($v['jobs_id'] as $jobsId) {
|
||||
$jobsName .= $jobsLists[$jobsId] ?? '';
|
||||
$jobsName .= '/';
|
||||
}
|
||||
|
||||
$adminLists[$k]['role_name'] = trim($roleName, '/');
|
||||
$adminLists[$k]['dept_name'] = trim($deptName, '/');
|
||||
$adminLists[$k]['jobs_name'] = trim($jobsName, '/');
|
||||
}
|
||||
|
||||
return $adminLists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 令狐冲
|
||||
* @date 2021/7/13 00:52
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return Admin::where($this->searchWhere)
|
||||
->where($this->queryWhere())
|
||||
->count();
|
||||
}
|
||||
|
||||
public function extend()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\auth;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\model\auth\SystemMenu;
|
||||
|
||||
|
||||
/**
|
||||
* 菜单列表
|
||||
* Class MenuLists
|
||||
* @package app\adminapi\lists\auth
|
||||
*/
|
||||
class MenuLists extends BaseAdminDataLists
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 获取菜单列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/6/29 16:41
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = SystemMenu::order(['sort' => 'desc', 'id' => 'asc'])
|
||||
->select()
|
||||
->toArray();
|
||||
return linear_to_tree($lists, 'children');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取菜单数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2022/6/29 16:41
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return SystemMenu::count();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\auth;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\model\auth\AdminRole;
|
||||
use app\common\model\auth\SystemRole;
|
||||
|
||||
/**
|
||||
* 角色列表
|
||||
* Class RoleLists
|
||||
* @package app\adminapi\lists\auth
|
||||
*/
|
||||
class RoleLists extends BaseAdminDataLists
|
||||
{
|
||||
/**
|
||||
* @notes 导出字段
|
||||
* @return string[]
|
||||
* @author Tab
|
||||
* @date 2021/9/22 18:52
|
||||
*/
|
||||
public function setExcelFields(): array
|
||||
{
|
||||
return [
|
||||
'name' => '角色名称',
|
||||
'desc' => '备注',
|
||||
'create_time' => '创建时间'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 导出表名
|
||||
* @return string
|
||||
* @author Tab
|
||||
* @date 2021/9/22 18:52
|
||||
*/
|
||||
public function setFileName(): string
|
||||
{
|
||||
return '角色表';
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 角色列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author cjhao
|
||||
* @date 2021/8/25 18:00
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = SystemRole::with(['role_menu_index'])
|
||||
->field('id,name,desc,sort,data_scope,create_time')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->order(['sort' => 'desc', 'id' => 'desc'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($lists as $key => $role) {
|
||||
//使用角色的人数
|
||||
$lists[$key]['num'] = AdminRole::where('role_id', $role['id'])->count();
|
||||
$menuId = array_column($role['role_menu_index'], 'menu_id');
|
||||
$lists[$key]['menu_id'] = $menuId;
|
||||
unset($lists[$key]['role_menu_index']);
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 总记录数
|
||||
* @return int
|
||||
* @author Tab
|
||||
* @date 2021/7/13 11:26
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return SystemRole::count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\channel;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\channel\OfficialAccountReply;
|
||||
|
||||
/**
|
||||
* 微信公众号回复列表
|
||||
* Class OfficialAccountLists
|
||||
* @package app\adminapi\lists
|
||||
*/
|
||||
class OfficialAccountReplyLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 设置搜索
|
||||
* @return \string[][]
|
||||
* @author 段誉
|
||||
* @date 2022/3/30 15:02
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['reply_type']
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 回复列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/3/30 15:02
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$field = 'id,name,keyword,matching_type,content,content_type,status,sort';
|
||||
$field .= ',matching_type as matching_type_desc,content_type as content_type_desc,status as status_desc';
|
||||
|
||||
$lists = OfficialAccountReply::field($field)
|
||||
->where($this->searchWhere)
|
||||
->order(['sort' => 'desc', 'id' => 'desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 回复记录数
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2022/3/30 15:02
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$count = OfficialAccountReply::where($this->searchWhere)->count();
|
||||
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\crontab;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\model\Crontab;
|
||||
|
||||
/**
|
||||
* 定时任务列表
|
||||
* Class CrontabLists
|
||||
* @package app\adminapi\lists\crontab
|
||||
*/
|
||||
class CrontabLists extends BaseAdminDataLists
|
||||
{
|
||||
/**
|
||||
* @notes 定时任务列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/3/29 14:30
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$field = 'id,name,type,type as type_desc,command,params,expression,
|
||||
status,status as status_desc,error,last_time,time,max_time';
|
||||
|
||||
$lists = Crontab::field($field)
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->order('id', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 定时任务数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2022/3/29 14:38
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return Crontab::count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\decorate;
|
||||
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\enum\MenuEnum;
|
||||
use app\common\model\decorate\Menu;
|
||||
|
||||
/**
|
||||
* 菜单列表
|
||||
* Class MenuLists
|
||||
* @package app\adminapi\lists\decorate
|
||||
*/
|
||||
class MenuLists extends BaseAdminDataLists
|
||||
{
|
||||
/**
|
||||
* @notes 菜单列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author ljj
|
||||
* @date 2022/2/14 11:29 上午
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = (new Menu())->field('id,name,image,link_type,link_address,sort,status')
|
||||
->order(['sort'=>'asc','id'=>'desc'])
|
||||
->append(['link_address_desc','status_desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($lists as &$list) {
|
||||
$list['link_address_desc'] = MenuEnum::getLinkDesc($list['link_type']).':'.$list['link_address_desc'];
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 菜单总数
|
||||
* @return int
|
||||
* @author ljj
|
||||
* @date 2022/2/14 11:29 上午
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return (new Menu())->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\decorate;
|
||||
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\model\decorate\Navigation;
|
||||
|
||||
/**
|
||||
* 底部导航列表
|
||||
* Class NavigationLists
|
||||
* @package app\adminapi\lists\decorate
|
||||
*/
|
||||
class NavigationLists extends BaseAdminDataLists
|
||||
{
|
||||
/**
|
||||
* @notes 底部导航列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author ljj
|
||||
* @date 2022/2/14 10:12 上午
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
return (new Navigation())->select()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 底部导航总数
|
||||
* @return int
|
||||
* @author ljj
|
||||
* @date 2022/2/14 10:13 上午
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return (new Navigation())->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\dept;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsExcelInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\dept\Jobs;
|
||||
|
||||
/**
|
||||
* 岗位列表
|
||||
* Class JobsLists
|
||||
* @package app\adminapi\lists\dept
|
||||
*/
|
||||
class JobsLists extends BaseAdminDataLists implements ListsSearchInterface,ListsExcelInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
* @return \string[][]
|
||||
* @author 段誉
|
||||
* @date 2022/5/26 9:46
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['name'],
|
||||
'=' => ['code', 'status']
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取管理列表
|
||||
* @return array
|
||||
* @author heshihu
|
||||
* @date 2022/2/21 17:11
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = Jobs::where($this->searchWhere)
|
||||
->append(['status_desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->order(['sort' => 'desc', 'id' => 'desc'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2022/5/26 9:48
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return Jobs::where($this->searchWhere)->count();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 导出文件名
|
||||
* @return string
|
||||
* @author 段誉
|
||||
* @date 2022/11/24 16:17
|
||||
*/
|
||||
public function setFileName(): string
|
||||
{
|
||||
return '岗位列表';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 导出字段
|
||||
* @return string[]
|
||||
* @author 段誉
|
||||
* @date 2022/11/24 16:17
|
||||
*/
|
||||
public function setExcelFields(): array
|
||||
{
|
||||
return [
|
||||
'code' => '岗位编码',
|
||||
'name' => '岗位名称',
|
||||
'remark' => '备注',
|
||||
'status_desc' => '状态',
|
||||
'create_time' => '添加时间',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
<?php
|
||||
|
||||
namespace app\adminapi\lists\doctor;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\dept\DeptLogic;
|
||||
use app\common\model\auth\AdminDept;
|
||||
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;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 医生预约列表
|
||||
* Class AppointmentLists
|
||||
* @package app\adminapi\lists\doctor
|
||||
*/
|
||||
class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
|
||||
/**
|
||||
* 诊单编辑/详情「挂号记录」Tab:按诊单 ID 拉全量挂号,不做医生/医助角色收窄与数据范围过滤。
|
||||
* 须同时传 patient_id(挂号表存的是诊单 id)与本开关,避免列表页被滥用拓宽可见范围。
|
||||
*/
|
||||
private function appointmentListsScopeRelaxedForDiagnosis(): bool
|
||||
{
|
||||
return (int) ($this->params['diag_scope_relax'] ?? 0) === 1
|
||||
&& (int) ($this->params['patient_id'] ?? 0) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据隔离:医生或医助(u.assistant_id = diag.assistant_id)命中可见集合;progress_board 场景放开(看板跨医生查看)
|
||||
*/
|
||||
private function applyDataScopeForAppointment($query, bool $progressBoard): void
|
||||
{
|
||||
if ($progressBoard) {
|
||||
return;
|
||||
}
|
||||
if ($this->appointmentListsScopeRelaxedForDiagnosis()) {
|
||||
return;
|
||||
}
|
||||
if (!$this->dataScopeShouldApply()) {
|
||||
return;
|
||||
}
|
||||
$ids = $this->getDataScopeVisibleAdminIds();
|
||||
if ($ids === null) {
|
||||
return;
|
||||
}
|
||||
if ($ids === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
$inList = implode(',', $ids);
|
||||
$query->whereRaw("(a.doctor_id IN ({$inList}) OR u.assistant_id IN ({$inList}))");
|
||||
}
|
||||
|
||||
/**
|
||||
* 按医助筛选:诊单指派医助或挂号记录上的医助任一命中即可
|
||||
*
|
||||
* @param mixed $query
|
||||
*/
|
||||
private function applyAssistantIdFilter($query): void
|
||||
{
|
||||
$aid = (int) ($this->params['assistant_id'] ?? 0);
|
||||
if ($aid <= 0) {
|
||||
return;
|
||||
}
|
||||
$query->where(function ($q) use ($aid): void {
|
||||
$q->where('u.assistant_id', $aid)->whereOr('a.assistant_id', $aid);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 按部门筛选:接诊医生、诊单医助或挂号医助所属部门命中子树即可(选父级含子级)
|
||||
*
|
||||
* @param mixed $query
|
||||
*/
|
||||
private function applyAssistantDeptIdFilter($query): void
|
||||
{
|
||||
if (!isset($this->params['assistant_dept_id']) || $this->params['assistant_dept_id'] === '' || (int) $this->params['assistant_dept_id'] <= 0) {
|
||||
return;
|
||||
}
|
||||
$rootDeptId = (int) $this->params['assistant_dept_id'];
|
||||
$deptIds = DeptLogic::getSelfAndDescendantIds($rootDeptId);
|
||||
$deptIds = array_values(array_filter(array_map('intval', $deptIds), static function (int $id): bool {
|
||||
return $id > 0;
|
||||
}));
|
||||
if ($deptIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
$inList = implode(',', $deptIds);
|
||||
$adTbl = (new AdminDept())->getTable();
|
||||
$query->whereRaw(
|
||||
"(EXISTS (SELECT 1 FROM `{$adTbl}` ad WHERE ad.`admin_id` = a.`doctor_id` AND ad.`dept_id` IN ({$inList}))"
|
||||
. " OR EXISTS (SELECT 1 FROM `{$adTbl}` ad WHERE ad.`admin_id` = u.`assistant_id` AND ad.`dept_id` IN ({$inList}))"
|
||||
. " OR EXISTS (SELECT 1 FROM `{$adTbl}` ad WHERE ad.`admin_id` = a.`assistant_id` AND ad.`dept_id` IN ({$inList})))"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渠道筛选:与 AppointmentLogic 一致,兼容仅有 channel_source、仅有 channels、或两者皆有的表结构
|
||||
*
|
||||
* @param mixed $query
|
||||
*/
|
||||
private function applyChannelSourceFilter($query, string $chFilter): void
|
||||
{
|
||||
if ($chFilter === '') {
|
||||
return;
|
||||
}
|
||||
$tblFields = Db::name('doctor_appointment')->getTableFields();
|
||||
$cols = \is_array($tblFields) ? $tblFields : [];
|
||||
$hasChannelSource = \in_array('channel_source', $cols, true);
|
||||
$hasChannels = \in_array('channels', $cols, true);
|
||||
if (!$hasChannelSource && !$hasChannels) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query->where(function ($q) use ($chFilter, $hasChannelSource, $hasChannels): void {
|
||||
if ($hasChannelSource && $hasChannels) {
|
||||
$q->where('a.channel_source', '=', $chFilter)
|
||||
->whereOr('a.channels', '=', $chFilter);
|
||||
if (is_numeric($chFilter)) {
|
||||
$q->whereOr('a.channels', '=', (int) $chFilter);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if ($hasChannelSource) {
|
||||
$q->where('a.channel_source', '=', $chFilter);
|
||||
|
||||
return;
|
||||
}
|
||||
$q->where('a.channels', '=', $chFilter);
|
||||
if (is_numeric($chFilter)) {
|
||||
$q->whereOr('a.channels', '=', (int) $chFilter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
* @return array
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
* @return array
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
// 获取当前管理员的角色ID
|
||||
$roleIds = AdminRole::where('admin_id', $this->adminId)->column('role_id');
|
||||
|
||||
// 处理患者姓名搜索
|
||||
if (!empty($this->params['patient_name'])) {
|
||||
$this->searchWhere[] = ['u.patient_name', 'like', '%' . $this->params['patient_name'] . '%'];
|
||||
}
|
||||
|
||||
// 处理医生姓名搜索
|
||||
if (!empty($this->params['doctor_name'])) {
|
||||
$this->searchWhere[] = ['ad.name', 'like', '%' . $this->params['doctor_name'] . '%'];
|
||||
}
|
||||
|
||||
// 处理状态搜索
|
||||
if (isset($this->params['status']) && $this->params['status'] !== '') {
|
||||
$this->searchWhere[] = ['a.status', '=', $this->params['status']];
|
||||
}
|
||||
|
||||
// 渠道字典 value(命中 channel_source 或 legacy channels)
|
||||
$chFilter = isset($this->params['channel_source']) ? trim((string) $this->params['channel_source']) : '';
|
||||
|
||||
// 处理日期范围搜索
|
||||
if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
|
||||
$this->searchWhere[] = ['a.appointment_date', 'between', [$this->params['start_date'], $this->params['end_date']]];
|
||||
} elseif (!empty($this->params['start_date'])) {
|
||||
$this->searchWhere[] = ['a.appointment_date', '>=', $this->params['start_date']];
|
||||
} elseif (!empty($this->params['end_date'])) {
|
||||
$this->searchWhere[] = ['a.appointment_date', '<=', $this->params['end_date']];
|
||||
}
|
||||
|
||||
// 诊单维度患者(挂号表 patient_id 存诊单 id)
|
||||
if (!empty($this->params['patient_id'])) {
|
||||
$this->searchWhere[] = ['a.patient_id', '=', (int) $this->params['patient_id']];
|
||||
}
|
||||
|
||||
// 按接诊医生筛选(管理端医生进度看板等)
|
||||
if (!empty($this->params['doctor_id'])) {
|
||||
$this->searchWhere[] = ['a.doctor_id', '=', (int) $this->params['doctor_id']];
|
||||
}
|
||||
|
||||
// 排除已取消挂号 status=2(医生进度看板等;未显式筛选「已取消」时生效)
|
||||
if ((int) ($this->params['exclude_cancelled'] ?? 0) === 1) {
|
||||
$sf = $this->params['status'] ?? '';
|
||||
if ($sf === '' || (int) $sf !== 2) {
|
||||
$this->searchWhere[] = ['a.status', '<>', 2];
|
||||
}
|
||||
}
|
||||
|
||||
// 构建查询(searchWhere 为空时避免部分环境下 where([]) 异常)
|
||||
$query = Appointment::alias('a')
|
||||
->with('diagnosis')
|
||||
->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, a.assistant_id as appointment_assistant_id');
|
||||
if ($this->searchWhere !== []) {
|
||||
$query->where($this->searchWhere);
|
||||
}
|
||||
|
||||
$this->applyAssistantIdFilter($query);
|
||||
|
||||
$this->applyAssistantDeptIdFilter($query);
|
||||
|
||||
$this->applyChannelSourceFilter($query, $chFilter);
|
||||
|
||||
// 是否确认诊单:1=已确认 0=未确认
|
||||
if (isset($this->params['diagnosis_confirmed']) && $this->params['diagnosis_confirmed'] !== '') {
|
||||
$confirmed = (int)$this->params['diagnosis_confirmed'];
|
||||
$tbl = (new DiagnosisViewRecord())->getTable();
|
||||
$subSql = "SELECT 1 FROM {$tbl} dvr WHERE dvr.diagnosis_id = u.id AND dvr.is_confirmed = 1 AND dvr.delete_time IS NULL";
|
||||
if ($confirmed === 1) {
|
||||
$query->whereExists($subSql);
|
||||
} else {
|
||||
$query->whereNotExists($subSql);
|
||||
}
|
||||
}
|
||||
|
||||
// 面诊进度看板:可切换查看各医生挂号,不按当前账号角色收窄
|
||||
$progressBoard = (int) ($this->params['progress_board'] ?? 0) === 1;
|
||||
$diagScopeRelax = $this->appointmentListsScopeRelaxedForDiagnosis();
|
||||
if (!$progressBoard && !$diagScopeRelax) {
|
||||
// 如果是医生角色(role_id=1),只显示挂自己号的预约
|
||||
if (in_array(1, $roleIds)) {
|
||||
$query->where('a.doctor_id', $this->adminId);
|
||||
}
|
||||
|
||||
// 如果是医助角色(role_id=2),只显示自己添加的患者的预约
|
||||
if (in_array(2, $roleIds)) {
|
||||
$query->where('u.assistant_id', $this->adminId);
|
||||
}
|
||||
}
|
||||
|
||||
$this->applyDataScopeForAppointment($query, $progressBoard);
|
||||
|
||||
// 诊单软删除后不再展示对应挂号(leftJoin 时无诊单或诊单未删)
|
||||
$query->whereRaw('(u.id IS NULL OR u.delete_time IS NULL)');
|
||||
|
||||
$lists = $query
|
||||
->order('a.status', 'asc')
|
||||
->order('a.appointment_date', 'asc')
|
||||
->order('a.appointment_time', 'asc')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 添加状态、类型描述及确认诊单状态
|
||||
$diagnosisIds = array_filter(array_unique(array_column($lists, 'diagnosis_id')));
|
||||
$confirmedMap = [];
|
||||
if (!empty($diagnosisIds)) {
|
||||
$confirmedIds = \think\facade\Db::name('diagnosis_view_records')
|
||||
->whereIn('diagnosis_id', $diagnosisIds)
|
||||
->where('is_confirmed', 1)
|
||||
->whereNull('delete_time')
|
||||
->column('diagnosis_id');
|
||||
$confirmedMap = array_flip($confirmedIds ?: []);
|
||||
}
|
||||
// 开方状态:诊单是否有处方(按 diagnosis_id 查)
|
||||
// prescription_today_only=1:仅统计「今日创建」的处方(医生进度看板用,与历史处方区分)
|
||||
$rxTodayOnly = (int) ($this->params['prescription_today_only'] ?? 0) === 1;
|
||||
$prescribedDiagnosisIds = [];
|
||||
if (!empty($diagnosisIds)) {
|
||||
$rxQ = Prescription::whereIn('diagnosis_id', $diagnosisIds)
|
||||
->whereNull('delete_time')
|
||||
->where('void_status', 0);
|
||||
if ($rxTodayOnly) {
|
||||
$dayStart = strtotime(date('Y-m-d 00:00:00'));
|
||||
$dayEnd = strtotime(date('Y-m-d 23:59:59'));
|
||||
$rxQ->whereBetween('create_time', [$dayStart, $dayEnd]);
|
||||
}
|
||||
$prescribedDiagnosisIds = $rxQ->column('diagnosis_id');
|
||||
$prescribedDiagnosisIds = array_flip($prescribedDiagnosisIds ?: []);
|
||||
}
|
||||
// 当前页预约关联的处方(按 appointment_id,取最新一条):用于「开方/查看」与审核状态
|
||||
$appointmentIds = array_filter(array_map('intval', array_column($lists, 'id')));
|
||||
$rxByAppointmentId = [];
|
||||
if (!empty($appointmentIds)) {
|
||||
$rxRows = Prescription::whereIn('appointment_id', $appointmentIds)
|
||||
->whereNull('delete_time')
|
||||
->where('void_status', 0)
|
||||
->order('id', 'desc')
|
||||
->field(['id', 'appointment_id', 'audit_status', 'void_status', 'is_system_auto'])
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($rxRows as $rx) {
|
||||
$aid = (int) ($rx['appointment_id'] ?? 0);
|
||||
if ($aid > 0 && !isset($rxByAppointmentId[$aid])) {
|
||||
$rxByAppointmentId[$aid] = $rx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$channelNameByValue = DictData::where('type_value', 'channels')->column('name', 'value');
|
||||
|
||||
foreach ($lists as &$item) {
|
||||
$statusMap = [
|
||||
1 => '已预约',
|
||||
2 => '已取消',
|
||||
3 => '已完成',
|
||||
4 => '已过号',
|
||||
];
|
||||
$item['status_desc'] = $statusMap[$item['status']] ?? '未知';
|
||||
|
||||
$typeMap = [
|
||||
'video' => '视频问诊',
|
||||
'text' => '图文问诊',
|
||||
'phone' => '电话问诊',
|
||||
];
|
||||
$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;
|
||||
|
||||
$apptId = (int) ($item['id'] ?? 0);
|
||||
$apptRx = $rxByAppointmentId[$apptId] ?? null;
|
||||
$item['prescription_audit_status'] = $apptRx !== null ? (int) ($apptRx['audit_status'] ?? -1) : -1;
|
||||
$item['prescription_void_status'] = $apptRx !== null ? (int) ($apptRx['void_status'] ?? 0) : 0;
|
||||
$item['prescription_is_system_auto'] = $apptRx !== null ? (int) ($apptRx['is_system_auto'] ?? 0) : 0;
|
||||
|
||||
// 格式化时间戳为日期时间
|
||||
if (isset($item['create_time']) && is_numeric($item['create_time'])) {
|
||||
$item['create_time'] = date('Y-m-d H:i:s', $item['create_time']);
|
||||
}
|
||||
if (isset($item['update_time']) && is_numeric($item['update_time'])) {
|
||||
$item['update_time'] = date('Y-m-d H:i:s', $item['update_time']);
|
||||
}
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表 count / Tab 角标统计共用的过滤条件(不依赖 lists() 对 searchWhere 的副作用)
|
||||
* @param mixed $query
|
||||
* @param bool $applyStatusFilter 为 false 时按状态分组统计各 Tab 数量
|
||||
*/
|
||||
private function applyAppointmentCountFilters($query, bool $applyStatusFilter): void
|
||||
{
|
||||
$roleIds = AdminRole::where('admin_id', $this->adminId)->column('role_id');
|
||||
|
||||
if (!empty($this->params['patient_name'])) {
|
||||
$query->where('u.patient_name', 'like', '%' . $this->params['patient_name'] . '%');
|
||||
}
|
||||
if (!empty($this->params['doctor_name'])) {
|
||||
$query->where('ad.name', 'like', '%' . $this->params['doctor_name'] . '%');
|
||||
}
|
||||
if ($applyStatusFilter && isset($this->params['status']) && $this->params['status'] !== '') {
|
||||
$query->where('a.status', '=', $this->params['status']);
|
||||
}
|
||||
$chFilter = isset($this->params['channel_source']) ? trim((string) $this->params['channel_source']) : '';
|
||||
if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
|
||||
$query->whereBetween('a.appointment_date', [$this->params['start_date'], $this->params['end_date']]);
|
||||
} elseif (!empty($this->params['start_date'])) {
|
||||
$query->where('a.appointment_date', '>=', $this->params['start_date']);
|
||||
} elseif (!empty($this->params['end_date'])) {
|
||||
$query->where('a.appointment_date', '<=', $this->params['end_date']);
|
||||
}
|
||||
|
||||
if (!empty($this->params['patient_id'])) {
|
||||
$query->where('a.patient_id', '=', (int) $this->params['patient_id']);
|
||||
}
|
||||
|
||||
if (!empty($this->params['doctor_id'])) {
|
||||
$query->where('a.doctor_id', '=', (int) $this->params['doctor_id']);
|
||||
}
|
||||
|
||||
$this->applyAssistantIdFilter($query);
|
||||
|
||||
$this->applyAssistantDeptIdFilter($query);
|
||||
|
||||
$this->applyChannelSourceFilter($query, $chFilter);
|
||||
|
||||
if ((int) ($this->params['exclude_cancelled'] ?? 0) === 1) {
|
||||
$sf = $this->params['status'] ?? '';
|
||||
if ($sf === '' || (int) $sf !== 2) {
|
||||
$query->where('a.status', '<>', 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->params['diagnosis_confirmed']) && $this->params['diagnosis_confirmed'] !== '') {
|
||||
$confirmed = (int)$this->params['diagnosis_confirmed'];
|
||||
$tbl = (new DiagnosisViewRecord())->getTable();
|
||||
$subSql = "SELECT 1 FROM {$tbl} dvr WHERE dvr.diagnosis_id = u.id AND dvr.is_confirmed = 1 AND dvr.delete_time IS NULL";
|
||||
if ($confirmed === 1) {
|
||||
$query->whereExists($subSql);
|
||||
} else {
|
||||
$query->whereNotExists($subSql);
|
||||
}
|
||||
}
|
||||
|
||||
$progressBoard = (int) ($this->params['progress_board'] ?? 0) === 1;
|
||||
$diagScopeRelax = $this->appointmentListsScopeRelaxedForDiagnosis();
|
||||
if (!$progressBoard && !$diagScopeRelax) {
|
||||
if (in_array(1, $roleIds)) {
|
||||
$query->where('a.doctor_id', $this->adminId);
|
||||
}
|
||||
if (in_array(2, $roleIds)) {
|
||||
$query->where('u.assistant_id', $this->adminId);
|
||||
}
|
||||
}
|
||||
|
||||
$this->applyDataScopeForAppointment($query, $progressBoard);
|
||||
|
||||
$query->whereRaw('(u.id IS NULL OR u.delete_time IS NULL)');
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$query = Appointment::alias('a')
|
||||
->leftJoin('tcm_diagnosis u', 'a.patient_id = u.id')
|
||||
->leftJoin('admin ad', 'a.doctor_id = ad.id');
|
||||
$this->applyAppointmentCountFilters($query, true);
|
||||
|
||||
return (int)$query->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 扩展字段:按需返回各状态数量(一次 GROUP BY,替代前端 4 次列表请求)
|
||||
* @return array
|
||||
*/
|
||||
public function extend(): array
|
||||
{
|
||||
if (empty($this->params['include_status_counts'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = Appointment::alias('a')
|
||||
->leftJoin('tcm_diagnosis u', 'a.patient_id = u.id')
|
||||
->leftJoin('admin ad', 'a.doctor_id = ad.id');
|
||||
$this->applyAppointmentCountFilters($query, false);
|
||||
|
||||
$rows = $query->field('a.status, COUNT(*) AS cnt')
|
||||
->group('a.status')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$out = [1 => 0, 2 => 0, 3 => 0, 4 => 0];
|
||||
foreach ($rows as $r) {
|
||||
$s = (int)($r['status'] ?? 0);
|
||||
if (array_key_exists($s, $out)) {
|
||||
$out[$s] = (int)($r['cnt'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
return ['status_count' => $out];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace app\adminapi\lists\doctor;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\model\doctor\Medicine;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
|
||||
/**
|
||||
* 药品库列表
|
||||
*/
|
||||
class MedicineLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
/**
|
||||
* 设置搜索条件
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['supplier'],
|
||||
'=' => ['status'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* name 参数:中文等按名称模糊;纯英文字母按拼音首字母字段 + 名称模糊(OR)
|
||||
*/
|
||||
private function appendMedicineNameSearch($query): void
|
||||
{
|
||||
$keyword = trim((string) ($this->params['name'] ?? ''));
|
||||
if ($keyword === '') {
|
||||
return;
|
||||
}
|
||||
if (preg_match('/^[a-zA-Z]+$/', $keyword)) {
|
||||
$kw = strtolower($keyword);
|
||||
$query->where(function ($q) use ($kw, $keyword) {
|
||||
$q->where('name_pinyin_abbr', 'like', '%' . $kw . '%')
|
||||
->whereOr('name', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
} else {
|
||||
$query->where('name', 'like', '%' . $keyword . '%');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表字段
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$query = Medicine::where($this->searchWhere);
|
||||
$this->appendMedicineNameSearch($query);
|
||||
|
||||
return $query
|
||||
->field([
|
||||
'id', 'name', 'name_pinyin_abbr', 'supplier', 'unit',
|
||||
'settlement_price', 'retail_price', 'stock',
|
||||
'image', 'status', 'remark',
|
||||
'create_time', 'update_time',
|
||||
])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->order(['id' => 'desc'])
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表数量
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$query = Medicine::where($this->searchWhere);
|
||||
$this->appendMedicineNameSearch($query);
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace app\adminapi\lists\doctor;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\model\doctor\Roster;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
|
||||
/**
|
||||
* 医生排班列表
|
||||
* Class RosterLists
|
||||
* @package app\adminapi\lists\doctor
|
||||
*/
|
||||
class RosterLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
* @return array
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['doctor_id', 'period', 'status'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$where = $this->searchWhere;
|
||||
|
||||
// 处理日期范围搜索
|
||||
if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
|
||||
$where[] = ['date', 'between', [$this->params['start_date'], $this->params['end_date']]];
|
||||
}
|
||||
|
||||
$lists = Roster::where($where)
|
||||
->field([
|
||||
'id', 'doctor_id', 'date', 'period', 'start_time', 'end_time', 'shift_type', 'slot_minutes',
|
||||
'status', 'quota', 'max_patients', 'booked_count', 'remark', 'create_time', 'update_time',
|
||||
])
|
||||
->order(['date' => 'asc', 'start_time' => 'asc', 'id' => 'asc'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$where = $this->searchWhere;
|
||||
|
||||
// 处理日期范围搜索
|
||||
if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
|
||||
$where[] = ['date', 'between', [$this->params['start_date'], $this->params['end_date']]];
|
||||
}
|
||||
|
||||
return Roster::where($where)->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
<?php
|
||||
|
||||
namespace app\adminapi\lists\doctor;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\model\doctor\Appointment;
|
||||
use app\common\model\auth\Admin;
|
||||
|
||||
/**
|
||||
* 医生统计列表
|
||||
* Class StatisticsLists
|
||||
* @package app\adminapi\lists\doctor
|
||||
*/
|
||||
class StatisticsLists extends BaseAdminDataLists
|
||||
{
|
||||
protected $type = 'doctor';
|
||||
|
||||
/**
|
||||
* @param string $type 统计类型:doctor(医生统计) 或 dept(部门统计)
|
||||
*/
|
||||
public function __construct($type = 'doctor')
|
||||
{
|
||||
parent::__construct();
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 搜索条件
|
||||
* @return array
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
* @return array
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
if ($this->type === 'dept') {
|
||||
return $this->getDeptStatistics();
|
||||
}
|
||||
|
||||
$doctorId = $this->params['doctor_id'] ?? '';
|
||||
$timeType = $this->params['time_type'] ?? 'today';
|
||||
$startDate = $this->params['start_date'] ?? '';
|
||||
$endDate = $this->params['end_date'] ?? '';
|
||||
|
||||
// 计算时间范围
|
||||
list($startDate, $endDate, $timeRangeText) = $this->getTimeRange($timeType, $startDate, $endDate);
|
||||
|
||||
// 获取医生列表(role_id=1 表示医生角色)
|
||||
$doctorAdminIds = \app\common\model\auth\AdminRole::where('role_id', 1)->column('admin_id');
|
||||
|
||||
if (empty($doctorAdminIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 查询1:获取预约统计数据(快速查询,只查appointment表)
|
||||
$statisticsData = \think\facade\Db::name('doctor_appointment')
|
||||
->field([
|
||||
'doctor_id',
|
||||
'COUNT(*) as total_count',
|
||||
'SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as registered_count',
|
||||
'SUM(CASE WHEN status = 3 THEN 1 ELSE 0 END) as completed_count',
|
||||
'SUM(CASE WHEN status = 4 THEN 1 ELSE 0 END) as missed_count',
|
||||
'SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as cancelled_count'
|
||||
])
|
||||
->whereIn('doctor_id', $doctorAdminIds)
|
||||
->where('appointment_date', '>=', $startDate)
|
||||
->where('appointment_date', '<=', $endDate);
|
||||
|
||||
if ($doctorId) {
|
||||
$statisticsData->where('doctor_id', $doctorId);
|
||||
}
|
||||
|
||||
$statisticsData = $statisticsData->group('doctor_id')->select()->toArray();
|
||||
|
||||
// 将统计数据按 doctor_id 索引
|
||||
$statsMap = [];
|
||||
foreach ($statisticsData as $stat) {
|
||||
$statsMap[$stat['doctor_id']] = $stat;
|
||||
}
|
||||
|
||||
// 诊单数:统计期内该医生挂号对应的 distinct 诊单(patient_id 存的是诊单 id)
|
||||
$diagnosisCountQuery = \think\facade\Db::name('doctor_appointment')
|
||||
->field('doctor_id, COUNT(DISTINCT patient_id) as diagnosis_count')
|
||||
->whereIn('doctor_id', $doctorAdminIds)
|
||||
->where('appointment_date', '>=', $startDate)
|
||||
->where('appointment_date', '<=', $endDate);
|
||||
if ($doctorId) {
|
||||
$diagnosisCountQuery->where('doctor_id', $doctorId);
|
||||
}
|
||||
$diagnosisCountRows = $diagnosisCountQuery->group('doctor_id')->select()->toArray();
|
||||
$diagnosisCountMap = [];
|
||||
foreach ($diagnosisCountRows as $row) {
|
||||
$diagnosisCountMap[(int) $row['doctor_id']] = (int) $row['diagnosis_count'];
|
||||
}
|
||||
|
||||
// 成交单:上述诊单中存在有效处方(未删除、未作废)的数量
|
||||
$dealCountQuery = \think\facade\Db::name('doctor_appointment')->alias('apt')
|
||||
->join('tcm_prescription rx', 'rx.diagnosis_id = apt.patient_id')
|
||||
->field('apt.doctor_id, COUNT(DISTINCT apt.patient_id) as deal_count')
|
||||
->whereIn('apt.doctor_id', $doctorAdminIds)
|
||||
->where('apt.appointment_date', '>=', $startDate)
|
||||
->where('apt.appointment_date', '<=', $endDate)
|
||||
->whereNull('rx.delete_time')
|
||||
->whereRaw('IFNULL(rx.void_status, 0) <> 1');
|
||||
if ($doctorId) {
|
||||
$dealCountQuery->where('apt.doctor_id', $doctorId);
|
||||
}
|
||||
$dealCountRows = $dealCountQuery->group('apt.doctor_id')->select()->toArray();
|
||||
$dealCountMap = [];
|
||||
foreach ($dealCountRows as $row) {
|
||||
$dealCountMap[(int) $row['doctor_id']] = (int) $row['deal_count'];
|
||||
}
|
||||
|
||||
// 获取医生信息
|
||||
$doctorQuery = Admin::field('id,name')->whereIn('id', $doctorAdminIds);
|
||||
if ($doctorId) {
|
||||
$doctorQuery->where('id', $doctorId);
|
||||
}
|
||||
$doctors = $doctorQuery->select()->toArray();
|
||||
|
||||
// 查询2:获取部门统计信息(通过assistant_id直接关联,按部门和状态分组统计数量)
|
||||
$doctorIdsWithAppointments = array_keys($statsMap);
|
||||
$deptStatsMap = [];
|
||||
$deptStatusMap = [];
|
||||
$channelStatsMap = [];
|
||||
$channelStatusMap = [];
|
||||
|
||||
if (!empty($doctorIdsWithAppointments)) {
|
||||
// 通过 assistant_id → admin_dept → dept 获取部门统计(按状态分组)
|
||||
$deptStats = \think\facade\Db::name('doctor_appointment')
|
||||
->alias('apt')
|
||||
->leftJoin('admin_dept ad', 'apt.assistant_id = ad.admin_id')
|
||||
->leftJoin('dept dept', 'ad.dept_id = dept.id')
|
||||
->field([
|
||||
'apt.doctor_id',
|
||||
'dept.id as dept_id',
|
||||
'dept.name as dept_name',
|
||||
'apt.status',
|
||||
'COUNT(*) as count'
|
||||
])
|
||||
->whereIn('apt.doctor_id', $doctorIdsWithAppointments)
|
||||
->where('apt.appointment_date', '>=', $startDate)
|
||||
->where('apt.appointment_date', '<=', $endDate)
|
||||
->whereNotNull('dept.id')
|
||||
->group('apt.doctor_id, dept.id, apt.status')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 组装部门统计数据:医生ID => "部门1(数量1) 部门2(数量2)"
|
||||
// 同时组装部门状态数据:医生ID => 部门ID => 状态 => 数量
|
||||
foreach ($deptStats as $stat) {
|
||||
$doctorId = $stat['doctor_id'];
|
||||
$deptId = $stat['dept_id'];
|
||||
$deptName = $stat['dept_name'];
|
||||
$status = $stat['status'];
|
||||
$count = $stat['count'];
|
||||
|
||||
// 组装部门统计数据
|
||||
if (!isset($deptStatsMap[$doctorId])) {
|
||||
$deptStatsMap[$doctorId] = [];
|
||||
}
|
||||
if (!isset($deptStatsMap[$doctorId][$deptId])) {
|
||||
$deptStatsMap[$doctorId][$deptId] = [
|
||||
'dept_name' => $deptName,
|
||||
'total_count' => 0
|
||||
];
|
||||
}
|
||||
$deptStatsMap[$doctorId][$deptId]['total_count'] += $count;
|
||||
|
||||
// 组装部门状态数据
|
||||
if (!isset($deptStatusMap[$doctorId])) {
|
||||
$deptStatusMap[$doctorId] = [];
|
||||
}
|
||||
if (!isset($deptStatusMap[$doctorId][$deptId])) {
|
||||
$deptStatusMap[$doctorId][$deptId] = [
|
||||
'dept_name' => $deptName,
|
||||
'statuses' => []
|
||||
];
|
||||
}
|
||||
$deptStatusMap[$doctorId][$deptId]['statuses'][$status] = $count;
|
||||
}
|
||||
|
||||
// 格式化部门统计数据
|
||||
foreach ($deptStatsMap as $doctorId => &$depts) {
|
||||
$formattedDepts = [];
|
||||
foreach ($depts as $dept) {
|
||||
$formattedDepts[] = $dept['dept_name'] . '(' . $dept['total_count'] . ')';
|
||||
}
|
||||
$deptStatsMap[$doctorId] = $formattedDepts;
|
||||
}
|
||||
|
||||
// 获取渠道字典(dict_data.value => name),与挂号创建时 channel_source 存字典 value 一致
|
||||
$channelDict = \think\facade\Db::name('dict_data')
|
||||
->where('type_value', 'channels')
|
||||
->column('name', 'value');
|
||||
|
||||
// 渠道:业务保存的是 channel_source(字典 value 字符串,见 AppointmentLogic::create);旧库可能仅有 channels(tinyint)
|
||||
$channelBuckets = [];
|
||||
$channelStatusBuckets = [];
|
||||
|
||||
try {
|
||||
$channelStats = \think\facade\Db::name('doctor_appointment')
|
||||
->field('doctor_id, channel_source, status, COUNT(*) as channel_count')
|
||||
->whereIn('doctor_id', $doctorIdsWithAppointments)
|
||||
->where('appointment_date', '>=', $startDate)
|
||||
->where('appointment_date', '<=', $endDate)
|
||||
->where('channel_source', '<>', '')
|
||||
->group('doctor_id, channel_source, status')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($channelStats as $stat) {
|
||||
$did = (int) $stat['doctor_id'];
|
||||
$src = trim((string) ($stat['channel_source'] ?? ''));
|
||||
$status = $stat['status'];
|
||||
$cnt = (int) ($stat['channel_count'] ?? 0);
|
||||
if ($src === '' || $cnt < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 组装渠道统计数据
|
||||
if (!isset($channelBuckets[$did])) {
|
||||
$channelBuckets[$did] = [];
|
||||
}
|
||||
if (!isset($channelBuckets[$did][$src])) {
|
||||
$channelBuckets[$did][$src] = 0;
|
||||
}
|
||||
$channelBuckets[$did][$src] += $cnt;
|
||||
|
||||
// 组装渠道状态数据
|
||||
if (!isset($channelStatusBuckets[$did])) {
|
||||
$channelStatusBuckets[$did] = [];
|
||||
}
|
||||
if (!isset($channelStatusBuckets[$did][$src])) {
|
||||
$channelStatusBuckets[$did][$src] = [];
|
||||
}
|
||||
$channelStatusBuckets[$did][$src][$status] = $cnt;
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// 无 channel_source 字段等
|
||||
}
|
||||
|
||||
$mergeLegacyChannels = static function (array &$buckets, array &$statusBuckets, array $rows): void {
|
||||
foreach ($rows as $stat) {
|
||||
$did = (int) $stat['doctor_id'];
|
||||
$key = (string) (int) ($stat['channels'] ?? 0);
|
||||
$status = $stat['status'];
|
||||
$cnt = (int) ($stat['channel_count'] ?? 0);
|
||||
if ($key === '0' || $cnt < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 组装渠道统计数据
|
||||
if (!isset($buckets[$did])) {
|
||||
$buckets[$did] = [];
|
||||
}
|
||||
if (!isset($buckets[$did][$key])) {
|
||||
$buckets[$did][$key] = 0;
|
||||
}
|
||||
$buckets[$did][$key] += $cnt;
|
||||
|
||||
// 组装渠道状态数据
|
||||
if (!isset($statusBuckets[$did])) {
|
||||
$statusBuckets[$did] = [];
|
||||
}
|
||||
if (!isset($statusBuckets[$did][$key])) {
|
||||
$statusBuckets[$did][$key] = [];
|
||||
}
|
||||
$statusBuckets[$did][$key][$status] = $cnt;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
$legacyStats = \think\facade\Db::name('doctor_appointment')
|
||||
->field('doctor_id, channels, status, COUNT(*) as channel_count')
|
||||
->whereIn('doctor_id', $doctorIdsWithAppointments)
|
||||
->where('appointment_date', '>=', $startDate)
|
||||
->where('appointment_date', '<=', $endDate)
|
||||
->where('channels', '>', 0)
|
||||
->where(function ($q) {
|
||||
$q->whereNull('channel_source')->whereOr('channel_source', '=', '');
|
||||
})
|
||||
->group('doctor_id, channels, status')
|
||||
->select()
|
||||
->toArray();
|
||||
$mergeLegacyChannels($channelBuckets, $channelStatusBuckets, $legacyStats);
|
||||
} catch (\Throwable) {
|
||||
try {
|
||||
$legacyStats = \think\facade\Db::name('doctor_appointment')
|
||||
->field('doctor_id, channels, status, COUNT(*) as channel_count')
|
||||
->whereIn('doctor_id', $doctorIdsWithAppointments)
|
||||
->where('appointment_date', '>=', $startDate)
|
||||
->where('appointment_date', '<=', $endDate)
|
||||
->where('channels', '>', 0)
|
||||
->group('doctor_id, channels, status')
|
||||
->select()
|
||||
->toArray();
|
||||
$mergeLegacyChannels($channelBuckets, $channelStatusBuckets, $legacyStats);
|
||||
} catch (\Throwable) {
|
||||
// 无 channels 字段
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($channelBuckets as $did => $byKey) {
|
||||
$parts = [];
|
||||
foreach ($byKey as $key => $cnt) {
|
||||
$label = $channelDict[$key] ?? $channelDict[(string) $key] ?? $key;
|
||||
$parts[] = $label . '(' . $cnt . ')';
|
||||
}
|
||||
$channelStatsMap[$did] = $parts;
|
||||
}
|
||||
|
||||
// 组装渠道状态明细数据
|
||||
foreach ($channelStatusBuckets as $did => $byKey) {
|
||||
foreach ($byKey as $key => $statuses) {
|
||||
$label = $channelDict[$key] ?? $channelDict[(string) $key] ?? $key;
|
||||
if (!isset($channelStatusMap[$did])) {
|
||||
$channelStatusMap[$did] = [];
|
||||
}
|
||||
$channelStatusMap[$did][] = [
|
||||
'channel_name' => $label,
|
||||
'statuses' => $statuses
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 组装结果
|
||||
$result = [];
|
||||
foreach ($doctors as $doctor) {
|
||||
$stat = $statsMap[$doctor['id']] ?? [
|
||||
'total_count' => 0,
|
||||
'registered_count' => 0,
|
||||
'completed_count' => 0,
|
||||
'missed_count' => 0,
|
||||
'cancelled_count' => 0
|
||||
];
|
||||
|
||||
$did = (int) $doctor['id'];
|
||||
$diagnosisCount = $diagnosisCountMap[$did] ?? 0;
|
||||
$dealCount = $dealCountMap[$did] ?? 0;
|
||||
$dealRate = $diagnosisCount > 0
|
||||
? round(($dealCount / $diagnosisCount) * 100, 2)
|
||||
: 0;
|
||||
|
||||
// 计算完成率
|
||||
$completionRate = $stat['total_count'] > 0
|
||||
? round(($stat['completed_count'] / $stat['total_count']) * 100, 2)
|
||||
: 0;
|
||||
|
||||
// 格式化部门状态明细
|
||||
$deptStatusDetails = [];
|
||||
if (isset($deptStatusMap[$doctor['id']])) {
|
||||
foreach ($deptStatusMap[$doctor['id']] as $deptId => $deptInfo) {
|
||||
$deptStatusDetails[] = [
|
||||
'dept_id' => $deptId,
|
||||
'dept_name' => $deptInfo['dept_name'],
|
||||
'statuses' => $deptInfo['statuses']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化渠道状态明细
|
||||
$channelStatusDetails = [];
|
||||
if (isset($channelStatusMap[$doctor['id']])) {
|
||||
$channelStatusDetails = $channelStatusMap[$doctor['id']];
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'doctor_id' => $doctor['id'],
|
||||
'doctor_name' => $doctor['name'],
|
||||
'total_count' => (int)$stat['total_count'] ?? 0,
|
||||
'registered_count' => (int)$stat['registered_count'] ?? 0,
|
||||
'completed_count' => (int)$stat['completed_count'] ?? 0,
|
||||
'missed_count' => (int)$stat['missed_count'] ?? 0,
|
||||
'cancelled_count' => (int)$stat['cancelled_count'] ?? 0,
|
||||
'diagnosis_count' => $diagnosisCount ?? 0,
|
||||
'deal_count' => $dealCount ?? 0,
|
||||
'deal_rate' => $dealRate ?? 0,
|
||||
'completion_rate' => $completionRate ?? 0,
|
||||
'dept_status_details' => $deptStatusDetails,
|
||||
'channel_status_details' => $channelStatusDetails,
|
||||
'time_range' => $timeRangeText
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取部门统计数据
|
||||
* @return array
|
||||
*/
|
||||
private function getDeptStatistics()
|
||||
{
|
||||
$deptId = $this->params['dept_id'] ?? '';
|
||||
$timeType = $this->params['time_type'] ?? 'today';
|
||||
$startDate = $this->params['start_date'] ?? '';
|
||||
$endDate = $this->params['end_date'] ?? '';
|
||||
|
||||
// 计算时间范围
|
||||
list($startDate, $endDate, $timeRangeText) = $this->getTimeRange($timeType, $startDate, $endDate);
|
||||
|
||||
// 查询部门统计数据
|
||||
$deptStatsQuery = \think\facade\Db::name('doctor_appointment')
|
||||
->alias('apt')
|
||||
->leftJoin('zyt_admin_dept ad', 'apt.assistant_id = ad.admin_id')
|
||||
->leftJoin('zyt_dept dept', 'ad.dept_id = dept.id')
|
||||
->field([
|
||||
'dept.id as dept_id',
|
||||
'dept.name as dept_name',
|
||||
'COUNT(*) as total_count',
|
||||
'SUM(CASE WHEN apt.status = 1 THEN 1 ELSE 0 END) as registered_count',
|
||||
'SUM(CASE WHEN apt.status = 3 THEN 1 ELSE 0 END) as completed_count',
|
||||
'SUM(CASE WHEN apt.status = 2 THEN 1 ELSE 0 END) as canceled_count',
|
||||
'SUM(CASE WHEN apt.status = 4 THEN 1 ELSE 0 END) as expired_count'
|
||||
])
|
||||
->where('apt.appointment_date', '>=', $startDate)
|
||||
->where('apt.appointment_date', '<=', $endDate)
|
||||
->whereNotNull('dept.id');
|
||||
|
||||
if ($deptId) {
|
||||
$deptStatsQuery->where('dept.id', $deptId);
|
||||
}
|
||||
|
||||
$deptStats = $deptStatsQuery->group('dept.id')->select()->toArray();
|
||||
|
||||
// 组装结果
|
||||
$result = [];
|
||||
foreach ($deptStats as $stat) {
|
||||
// 计算完成率
|
||||
$completionRate = $stat['total_count'] > 0
|
||||
? round(($stat['completed_count'] / $stat['total_count']) * 100, 2)
|
||||
: 0;
|
||||
|
||||
$result[] = [
|
||||
'dept_id' => $stat['dept_id'],
|
||||
'dept_name' => $stat['dept_name'],
|
||||
'total_count' => (int)$stat['total_count'] ?? 0,
|
||||
'registered_count' => (int)$stat['registered_count'] ?? 0,
|
||||
'completed_count' => (int)$stat['completed_count'] ?? 0,
|
||||
'canceled_count' => (int)$stat['canceled_count'] ?? 0,
|
||||
'expired_count' => (int)$stat['expired_count'] ?? 0,
|
||||
'completion_rate' => $completionRate,
|
||||
'time_range' => $timeRangeText
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取时间范围
|
||||
* @param string $timeType
|
||||
* @param string $customStartDate
|
||||
* @param string $customEndDate
|
||||
* @return array
|
||||
*/
|
||||
private function getTimeRange($timeType, $customStartDate, $customEndDate)
|
||||
{
|
||||
$today = date('Y-m-d');
|
||||
|
||||
switch ($timeType) {
|
||||
case 'today':
|
||||
$startDate = $today;
|
||||
$endDate = $today;
|
||||
$timeRangeText = '今天 (' . $today . ')';
|
||||
break;
|
||||
|
||||
case 'week':
|
||||
$startDate = date('Y-m-d', strtotime('-6 days'));
|
||||
$endDate = $today;
|
||||
$timeRangeText = '最近7天 (' . $startDate . ' 至 ' . $endDate . ')';
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
$startDate = date('Y-m-d', strtotime('-29 days'));
|
||||
$endDate = $today;
|
||||
$timeRangeText = '最近30天 (' . $startDate . ' 至 ' . $endDate . ')';
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
if ($customStartDate && $customEndDate) {
|
||||
$startDate = $customStartDate;
|
||||
$endDate = $customEndDate;
|
||||
$timeRangeText = '自定义 (' . $startDate . ' 至 ' . $endDate . ')';
|
||||
} else {
|
||||
$startDate = $today;
|
||||
$endDate = $today;
|
||||
$timeRangeText = '今天 (' . $today . ')';
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$startDate = $today;
|
||||
$endDate = $today;
|
||||
$timeRangeText = '今天 (' . $today . ')';
|
||||
}
|
||||
|
||||
return [$startDate, $endDate, $timeRangeText];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->lists());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\file;
|
||||
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\file\FileCate;
|
||||
|
||||
/**
|
||||
* 文件分类列表
|
||||
* Class FileCateLists
|
||||
* @package app\adminapi\lists\file
|
||||
*/
|
||||
class FileCateLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 文件分类搜素条件
|
||||
* @return \string[][]
|
||||
* @author 段誉
|
||||
* @date 2021/12/29 14:24
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['type']
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取文件分类列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2021/12/29 14:24
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = (new FileCate())->field(['id,pid,type,name'])
|
||||
->where($this->searchWhere)
|
||||
->order('id desc')
|
||||
->select()->toArray();
|
||||
|
||||
return linear_to_tree($lists, 'children');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取文件分类数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2021/12/29 14:24
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return (new FileCate())->where($this->searchWhere)->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\file;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\FileLogic;
|
||||
use app\common\enum\FileEnum;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\file\File;
|
||||
use app\common\model\file\FileCate;
|
||||
use app\common\service\FileService;
|
||||
|
||||
/**
|
||||
* 文件列表
|
||||
* Class FileLists
|
||||
* @package app\adminapi\lists\file
|
||||
*/
|
||||
class FileLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 文件搜索条件
|
||||
* @return \string[][]
|
||||
* @author 段誉
|
||||
* @date 2021/12/29 14:27
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
// 不按 source 搜索:列表固定为「当前管理员后台上传」,避免请求参数覆盖权限条件
|
||||
return [
|
||||
'=' => ['type'],
|
||||
'%like%' => ['name']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 额外查询处理
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2024/2/7 10:26
|
||||
*/
|
||||
public function queryWhere(): array
|
||||
{
|
||||
$where = [];
|
||||
|
||||
if (!empty($this->params['cid'])) {
|
||||
$cateChild = FileLogic::getCateIds($this->params['cid']);
|
||||
array_push($cateChild, $this->params['cid']);
|
||||
$where[] = ['cid', 'in', $cateChild];
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取文件列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2021/12/29 14:27
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = (new File())->field(['id,cid,type,name,uri,create_time'])
|
||||
->order('id', 'desc')
|
||||
->where($this->searchWhere)
|
||||
->where($this->queryWhere())
|
||||
->where('source', FileEnum::SOURCE_ADMIN)
|
||||
->where('source_id', $this->adminId)
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($lists as &$item) {
|
||||
$item['url'] = FileService::getFileUrl($item['uri']);
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取文件数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2021/12/29 14:29
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return (new File())->where($this->searchWhere)
|
||||
->where($this->queryWhere())
|
||||
->where('source', FileEnum::SOURCE_ADMIN)
|
||||
->where('source_id', $this->adminId)
|
||||
->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\finance;
|
||||
|
||||
use app\adminapi\logic\dept\DeptLogic;
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsExtendInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\finance\AccountCost;
|
||||
use app\common\service\qywx\MediaChannelService;
|
||||
|
||||
class AccountCostLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
||||
{
|
||||
public function setSearch(): array
|
||||
{
|
||||
$searchFields = ['remark', 'creator_name', 'updater_name'];
|
||||
if (AccountCost::supportsDeptBinding()) {
|
||||
$searchFields[] = 'dept_name';
|
||||
}
|
||||
|
||||
return [
|
||||
'%like%' => $searchFields,
|
||||
];
|
||||
}
|
||||
|
||||
private function baseQuery()
|
||||
{
|
||||
$query = AccountCost::where($this->searchWhere);
|
||||
|
||||
if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
|
||||
$query->whereBetween('cost_date', [$this->params['start_date'], $this->params['end_date']]);
|
||||
} elseif (!empty($this->params['start_date'])) {
|
||||
$query->where('cost_date', '>=', $this->params['start_date']);
|
||||
} elseif (!empty($this->params['end_date'])) {
|
||||
$query->where('cost_date', '<=', $this->params['end_date']);
|
||||
}
|
||||
|
||||
if (!empty($this->params['media_channel_code'])) {
|
||||
$query->where('media_channel_code', trim((string) $this->params['media_channel_code']));
|
||||
}
|
||||
|
||||
if (AccountCost::supportsDeptBinding() && !empty($this->params['dept_id'])) {
|
||||
$query->where('dept_id', (int) $this->params['dept_id']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
return $this->baseQuery()
|
||||
->order(['cost_date' => 'desc', 'id' => 'desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return (int) $this->baseQuery()->count();
|
||||
}
|
||||
|
||||
public function extend(): array
|
||||
{
|
||||
return [
|
||||
'total_amount' => round((float) $this->baseQuery()->sum('amount'), 2),
|
||||
'days_count' => (int) $this->baseQuery()->distinct(true)->count('cost_date'),
|
||||
'media_channel_options' => MediaChannelService::getOptions(),
|
||||
'media_channel_groups' => MediaChannelService::getOptionGroups(),
|
||||
'dept_options' => DeptLogic::getAllData(),
|
||||
'supports_dept_binding' => AccountCost::supportsDeptBinding(),
|
||||
'default_media_channel_code' => MediaChannelService::getDefaultCode(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\finance;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\enum\user\AccountLogEnum;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\user\UserAccountLog;
|
||||
use app\common\service\FileService;
|
||||
|
||||
|
||||
/**
|
||||
* 账记流水列表
|
||||
* Class AccountLogLists
|
||||
* @package app\adminapi\lists\finance
|
||||
*/
|
||||
class AccountLogLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 搜索条件
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 15:26
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['al.change_type'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 搜索条件
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 15:26
|
||||
*/
|
||||
public function queryWhere()
|
||||
{
|
||||
$where = [];
|
||||
// 用户余额
|
||||
if (isset($this->params['type']) && $this->params['type'] == 'um') {
|
||||
$where[] = ['change_type', 'in', AccountLogEnum::getUserMoneyChangeType()];
|
||||
}
|
||||
|
||||
if (!empty($this->params['user_info'])) {
|
||||
$where[] = ['u.sn|u.nickname|u.mobile|u.account', 'like', '%' . $this->params['user_info'] . '%'];
|
||||
}
|
||||
|
||||
if (!empty($this->params['start_time'])) {
|
||||
$where[] = ['al.create_time', '>=', strtotime($this->params['start_time'])];
|
||||
}
|
||||
|
||||
if (!empty($this->params['end_time'])) {
|
||||
$where[] = ['al.create_time', '<=', strtotime($this->params['end_time'])];
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 15:31
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$field = 'u.nickname,u.account,u.sn,u.avatar,u.mobile,al.action,al.change_amount,al.left_amount,al.change_type,al.source_sn,al.create_time';
|
||||
$lists = UserAccountLog::alias('al')
|
||||
->join('user u', 'u.id = al.user_id')
|
||||
->field($field)
|
||||
->where($this->searchWhere)
|
||||
->where($this->queryWhere())
|
||||
->order('al.id', 'desc')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($lists as &$item) {
|
||||
$item['avatar'] = FileService::getFileUrl($item['avatar']);
|
||||
$item['change_type_desc'] = AccountLogEnum::getChangeTypeDesc($item['change_type']);
|
||||
$symbol = $item['action'] == AccountLogEnum::INC ? '+' : '-';
|
||||
$item['change_amount'] = $symbol . $item['change_amount'];
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 15:36
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return UserAccountLog::alias('al')
|
||||
->join('user u', 'u.id = al.user_id')
|
||||
->where($this->queryWhere())
|
||||
->where($this->searchWhere)
|
||||
->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\finance;
|
||||
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\model\refund\RefundLog;
|
||||
|
||||
|
||||
/**
|
||||
* 退款日志列表
|
||||
* Class RefundLogLists
|
||||
* @package app\adminapi\lists\product
|
||||
*/
|
||||
class RefundLogLists extends BaseAdminDataLists
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 查询条件
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 9:55
|
||||
*/
|
||||
public function queryWhere()
|
||||
{
|
||||
$where[] = ['record_id', '=', $this->params['record_id'] ?? 0];
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 9:56
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = (new RefundLog())
|
||||
->order(['id' => 'desc'])
|
||||
->where($this->queryWhere())
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->hidden(['refund_msg'])
|
||||
->append(['handler', 'refund_status_text'])
|
||||
->select()
|
||||
->toArray();
|
||||
return $lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 9:56
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return (new RefundLog())
|
||||
->where($this->queryWhere())
|
||||
->count();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\finance;
|
||||
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\enum\RefundEnum;
|
||||
use app\common\lists\ListsExtendInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\refund\RefundRecord;
|
||||
use app\common\service\FileService;
|
||||
|
||||
|
||||
/**
|
||||
* 退款记录列表
|
||||
* Class RefundRecordLists
|
||||
* @package app\adminapi\lists\product
|
||||
*/
|
||||
class RefundRecordLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 查询条件
|
||||
* @return \string[][]
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 9:51
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['r.sn', 'r.order_sn', 'r.refund_type'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 查询条件
|
||||
* @param bool $flag
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 9:51
|
||||
*/
|
||||
public function queryWhere($flag = true)
|
||||
{
|
||||
$where = [];
|
||||
if (!empty($this->params['user_info'])) {
|
||||
$where[] = ['u.sn|u.nickname|u.mobile|u.account', 'like', '%' . $this->params['user_info'] . '%'];
|
||||
}
|
||||
if (!empty($this->params['start_time'])) {
|
||||
$where[] = ['r.create_time', '>=', strtotime($this->params['start_time'])];
|
||||
}
|
||||
if (!empty($this->params['end_time'])) {
|
||||
$where[] = ['r.create_time', '<=', strtotime($this->params['end_time'])];
|
||||
}
|
||||
|
||||
if ($flag == true) {
|
||||
if (isset($this->params['refund_status']) && $this->params['refund_status'] != '') {
|
||||
$where[] = ['r.refund_status', '=', $this->params['refund_status']];
|
||||
}
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 9:51
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = (new RefundRecord())->alias('r')
|
||||
->field('r.*,u.nickname,u.avatar')
|
||||
->join('user u', 'u.id = r.user_id')
|
||||
->order(['r.id' => 'desc'])
|
||||
->where($this->searchWhere)
|
||||
->where($this->queryWhere())
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->append(['refund_type_text', 'refund_status_text', 'refund_way_text'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($lists as &$item) {
|
||||
$item['avatar'] = FileService::getFileUrl($item['avatar']);
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 9:51
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return (new RefundRecord())->alias('r')
|
||||
->join('user u', 'u.id = r.user_id')
|
||||
->where($this->searchWhere)
|
||||
->where($this->queryWhere())
|
||||
->count();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 额外参数
|
||||
* @return mixed|null
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 9:51
|
||||
*/
|
||||
public function extend()
|
||||
{
|
||||
$count = (new RefundRecord())->alias('r')
|
||||
->join('user u', 'u.id = r.user_id')
|
||||
->field([
|
||||
'count(r.id) as total',
|
||||
'count(if(r.refund_status='.RefundEnum::REFUND_ING.', true, null)) as ing',
|
||||
'count(if(r.refund_status='.RefundEnum::REFUND_SUCCESS.', true, null)) as success',
|
||||
'count(if(r.refund_status='.RefundEnum::REFUND_ERROR.', true, null)) as error',
|
||||
])
|
||||
->where($this->searchWhere)
|
||||
->where($this->queryWhere(false))
|
||||
->select()->toArray();
|
||||
|
||||
return array_shift($count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\firstvisit;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\firstvisit\MyPatientLogic;
|
||||
use app\common\lists\ListsExtendInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\DiagnosisViewRecord;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\doctor\Appointment;
|
||||
use app\common\model\tcm\Diagnosis;
|
||||
use think\db\Query;
|
||||
use think\facade\Db;
|
||||
|
||||
class MyPatientLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
||||
{
|
||||
private const EFFECTIVE_APPOINTMENT_STATUSES = [1, 3, 4];
|
||||
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$query = $this->buildQuery(true, true);
|
||||
$appointmentTable = (new Appointment())->getTable();
|
||||
$today = date('Y-m-d');
|
||||
$upcomingSql = "SELECT MIN(CONCAT(sort_apt.appointment_date, ' ', IFNULL(NULLIF(TRIM(sort_apt.appointment_time), ''), '00:00:00')))"
|
||||
. " FROM {$appointmentTable} sort_apt"
|
||||
. ' WHERE sort_apt.patient_id = d.id'
|
||||
. ' AND sort_apt.status IN (1,4)'
|
||||
. " AND sort_apt.appointment_date >= '{$today}'";
|
||||
|
||||
$rows = $query
|
||||
->field([
|
||||
'd.id', 'd.patient_id', 'd.patient_name', 'd.phone', 'd.id_card', 'd.gender', 'd.age',
|
||||
'd.diagnosis_date', 'd.diagnosis_type', 'd.syndrome_type', 'd.assistant_id',
|
||||
'd.assign_read_at', 'd.create_time',
|
||||
])
|
||||
->orderRaw("CASE WHEN ({$upcomingSql}) IS NULL THEN 1 ELSE 0 END ASC")
|
||||
->orderRaw("IFNULL(({$upcomingSql}), '9999-12-31 23:59:59') ASC")
|
||||
->order('d.id', 'desc')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $this->appendRelations($rows);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return (int) $this->buildQuery(true, true)->count('d.id');
|
||||
}
|
||||
|
||||
public function extend(): array
|
||||
{
|
||||
$today = date('Y-m-d');
|
||||
$tomorrow = date('Y-m-d', strtotime('+1 day'));
|
||||
$dayAfter = date('Y-m-d', strtotime('+2 days'));
|
||||
|
||||
return [
|
||||
'summary' => [
|
||||
'today' => $this->countByAppointmentDate($today),
|
||||
'tomorrow' => $this->countByAppointmentDate($tomorrow),
|
||||
'day_after' => $this->countByAppointmentDate($dayAfter),
|
||||
],
|
||||
'scope' => MyPatientLogic::scopeMeta($this->adminId, $this->adminInfo),
|
||||
'dates' => [
|
||||
'today' => $today,
|
||||
'tomorrow' => $tomorrow,
|
||||
'day_after' => $dayAfter,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function buildQuery(bool $applyStatusFilter, bool $applyDateFilter): Query
|
||||
{
|
||||
$diagnosisTable = (new Diagnosis())->getTable();
|
||||
$query = Db::table($diagnosisTable)
|
||||
->alias('d')
|
||||
->whereNull('d.delete_time')
|
||||
->where('d.status', 1);
|
||||
|
||||
MyPatientLogic::applyScope($query, $this->adminId, $this->adminInfo);
|
||||
$this->applyKeyword($query);
|
||||
|
||||
$statusFilter = $applyStatusFilter ? trim((string) ($this->params['status_filter'] ?? '')) : '';
|
||||
$appointmentTable = (new Appointment())->getTable();
|
||||
if ($statusFilter === 'unbooked') {
|
||||
$query->whereNotExists(
|
||||
"SELECT 1 FROM {$appointmentTable} unbooked_apt"
|
||||
. ' WHERE unbooked_apt.patient_id = d.id'
|
||||
. ' AND unbooked_apt.status IN (' . implode(',', self::EFFECTIVE_APPOINTMENT_STATUSES) . ')'
|
||||
);
|
||||
}
|
||||
|
||||
$appointmentStatuses = self::EFFECTIVE_APPOINTMENT_STATUSES;
|
||||
if (in_array($statusFilter, ['pending_interview', 'booked'], true)) {
|
||||
$appointmentStatuses = [1];
|
||||
} elseif ($statusFilter === 'completed') {
|
||||
$appointmentStatuses = [3];
|
||||
} elseif ($statusFilter === 'missed') {
|
||||
$appointmentStatuses = [4];
|
||||
}
|
||||
|
||||
$needsAppointmentFilter = in_array(
|
||||
$statusFilter,
|
||||
['pending_interview', 'booked', 'completed', 'missed'],
|
||||
true
|
||||
);
|
||||
[$startDate, $endDate] = $applyDateFilter ? $this->dateRange() : ['', ''];
|
||||
if ($startDate !== '' || $endDate !== '') {
|
||||
$needsAppointmentFilter = true;
|
||||
}
|
||||
|
||||
if ($needsAppointmentFilter) {
|
||||
$conditions = [
|
||||
'filter_apt.patient_id = d.id',
|
||||
'filter_apt.status IN (' . implode(',', $appointmentStatuses) . ')',
|
||||
];
|
||||
if ($startDate !== '') {
|
||||
$conditions[] = "filter_apt.appointment_date >= '{$startDate}'";
|
||||
}
|
||||
if ($endDate !== '') {
|
||||
$conditions[] = "filter_apt.appointment_date <= '{$endDate}'";
|
||||
}
|
||||
$query->whereExists(
|
||||
"SELECT 1 FROM {$appointmentTable} filter_apt WHERE " . implode(' AND ', $conditions)
|
||||
);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function applyKeyword(Query $query): void
|
||||
{
|
||||
$keyword = trim((string) ($this->params['keyword'] ?? ''));
|
||||
if ($keyword === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$needle = addslashes($keyword);
|
||||
$adminTable = (new Admin())->getTable();
|
||||
$appointmentTable = (new Appointment())->getTable();
|
||||
$query->whereRaw(
|
||||
"(d.patient_name LIKE '%{$needle}%'"
|
||||
. " OR d.phone LIKE '%{$needle}%'"
|
||||
. " OR EXISTS (SELECT 1 FROM {$adminTable} assistant_admin"
|
||||
. ' WHERE assistant_admin.id = CAST(d.assistant_id AS UNSIGNED)'
|
||||
. ' AND assistant_admin.delete_time IS NULL'
|
||||
. " AND assistant_admin.name LIKE '%{$needle}%')"
|
||||
. " OR EXISTS (SELECT 1 FROM {$appointmentTable} keyword_apt"
|
||||
. " INNER JOIN {$adminTable} doctor_admin ON doctor_admin.id = keyword_apt.doctor_id"
|
||||
. ' AND doctor_admin.delete_time IS NULL'
|
||||
. ' WHERE keyword_apt.patient_id = d.id'
|
||||
. ' AND keyword_apt.status IN (1,3,4)'
|
||||
. " AND doctor_admin.name LIKE '%{$needle}%'))"
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array{0:string,1:string} */
|
||||
private function dateRange(): array
|
||||
{
|
||||
$startDate = $this->normalizeDate($this->params['start_date'] ?? '');
|
||||
$endDate = $this->normalizeDate($this->params['end_date'] ?? '');
|
||||
if ($startDate === '' && $endDate !== '') {
|
||||
$startDate = $endDate;
|
||||
}
|
||||
if ($endDate === '' && $startDate !== '') {
|
||||
$endDate = $startDate;
|
||||
}
|
||||
if ($startDate !== '' && $endDate !== '' && $startDate > $endDate) {
|
||||
[$startDate, $endDate] = [$endDate, $startDate];
|
||||
}
|
||||
|
||||
return [$startDate, $endDate];
|
||||
}
|
||||
|
||||
private function normalizeDate($value): string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
|
||||
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) ? $value : '';
|
||||
}
|
||||
|
||||
private function countByAppointmentDate(string $date): int
|
||||
{
|
||||
$query = $this->buildQuery(false, false);
|
||||
$appointmentTable = (new Appointment())->getTable();
|
||||
$query->whereExists(
|
||||
"SELECT 1 FROM {$appointmentTable} summary_apt"
|
||||
. ' WHERE summary_apt.patient_id = d.id'
|
||||
. " AND summary_apt.appointment_date = '{$date}'"
|
||||
. ' AND summary_apt.status IN (1,3,4)'
|
||||
);
|
||||
|
||||
return (int) $query->count('d.id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $rows
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function appendRelations(array $rows): array
|
||||
{
|
||||
if ($rows === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$diagnosisIds = array_values(array_unique(array_map('intval', array_column($rows, 'id'))));
|
||||
$assistantIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'assistant_id')))));
|
||||
$appointmentTable = (new Appointment())->getTable();
|
||||
$appointments = Db::table($appointmentTable)
|
||||
->whereIn('patient_id', $diagnosisIds)
|
||||
->whereIn('status', self::EFFECTIVE_APPOINTMENT_STATUSES)
|
||||
->field(['id', 'patient_id', 'doctor_id', 'appointment_date', 'appointment_time', 'status'])
|
||||
->order('appointment_date', 'asc')
|
||||
->order('appointment_time', 'asc')
|
||||
->order('id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$doctorIds = array_values(array_unique(array_filter(array_map('intval', array_column($appointments, 'doctor_id')))));
|
||||
$adminIds = array_values(array_unique(array_merge($assistantIds, $doctorIds)));
|
||||
$adminNames = $adminIds === [] ? [] : Admin::whereIn('id', $adminIds)->whereNull('delete_time')->column('name', 'id');
|
||||
|
||||
$appointmentMap = [];
|
||||
foreach ($appointments as $appointment) {
|
||||
$diagnosisId = (int) ($appointment['patient_id'] ?? 0);
|
||||
if ($diagnosisId > 0) {
|
||||
$appointmentMap[$diagnosisId][] = $appointment;
|
||||
}
|
||||
}
|
||||
|
||||
$viewTable = (new DiagnosisViewRecord())->getTable();
|
||||
$confirmedIds = Db::table($viewTable)
|
||||
->whereIn('diagnosis_id', $diagnosisIds)
|
||||
->where('is_confirmed', 1)
|
||||
->whereNull('delete_time')
|
||||
->column('diagnosis_id');
|
||||
$confirmedSet = array_fill_keys(array_map('intval', $confirmedIds), true);
|
||||
[$rangeStart, $rangeEnd] = $this->dateRange();
|
||||
$today = date('Y-m-d');
|
||||
$statusFilter = trim((string) ($this->params['status_filter'] ?? ''));
|
||||
$preferredStatuses = [
|
||||
'pending_interview' => [1],
|
||||
'booked' => [1],
|
||||
'completed' => [3],
|
||||
'missed' => [4],
|
||||
][$statusFilter] ?? [];
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
$diagnosisId = (int) $row['id'];
|
||||
$rowAppointments = $appointmentMap[$diagnosisId] ?? [];
|
||||
$primary = $this->pickPrimaryAppointment(
|
||||
$rowAppointments,
|
||||
$rangeStart,
|
||||
$rangeEnd,
|
||||
$today,
|
||||
$preferredStatuses
|
||||
);
|
||||
$completedCount = count(array_filter($rowAppointments, static function (array $appointment): bool {
|
||||
return (int) ($appointment['status'] ?? 0) === 3;
|
||||
}));
|
||||
$assistantId = (int) ($row['assistant_id'] ?? 0);
|
||||
|
||||
$row['diagnosis_id'] = $diagnosisId;
|
||||
$row['source_patient_id'] = (int) ($row['patient_id'] ?? 0);
|
||||
$row['phone_masked'] = $this->maskPhone((string) ($row['phone'] ?? ''));
|
||||
unset($row['phone']);
|
||||
$row['has_id_card'] = trim((string) ($row['id_card'] ?? '')) !== '' ? 1 : 0;
|
||||
unset($row['id_card']);
|
||||
$row['gender_desc'] = (int) ($row['gender'] ?? 0) === 1 ? '男' : '女';
|
||||
$row['diagnosis_date_text'] = $this->formatDiagnosisDate($row['diagnosis_date'] ?? '');
|
||||
$row['assistant_name'] = (string) ($adminNames[$assistantId] ?? '未分配');
|
||||
$row['confirmed'] = isset($confirmedSet[$diagnosisId]) ? 1 : 0;
|
||||
$row['confirmation_text'] = $row['confirmed'] ? '已确认' : '待确认';
|
||||
$row['visit_count'] = $completedCount;
|
||||
$row['revisit_count'] = max(0, $completedCount - 1);
|
||||
$row['appointment_id'] = $primary ? (int) $primary['id'] : 0;
|
||||
$row['appointment_status'] = $primary ? (int) $primary['status'] : 0;
|
||||
$row['appointment_status_text'] = $this->appointmentStatusText((int) ($primary['status'] ?? 0));
|
||||
$row['appointment_doctor_id'] = $primary ? (int) $primary['doctor_id'] : 0;
|
||||
$row['appointment_doctor_name'] = $primary
|
||||
? (string) ($adminNames[(int) $primary['doctor_id']] ?? '未知医生')
|
||||
: '未预约';
|
||||
$row['appointment_time_text'] = $primary ? $this->appointmentTimeText($primary) : '';
|
||||
$row['has_appointment'] = $primary !== null ? 1 : 0;
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $appointments
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
private function pickPrimaryAppointment(
|
||||
array $appointments,
|
||||
string $rangeStart,
|
||||
string $rangeEnd,
|
||||
string $today,
|
||||
array $preferredStatuses = []
|
||||
): ?array
|
||||
{
|
||||
if ($appointments === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$candidates = $appointments;
|
||||
if ($preferredStatuses !== []) {
|
||||
$candidates = array_values(array_filter($candidates, static function (array $appointment) use ($preferredStatuses): bool {
|
||||
return in_array((int) ($appointment['status'] ?? 0), $preferredStatuses, true);
|
||||
}));
|
||||
}
|
||||
if ($rangeStart !== '' || $rangeEnd !== '') {
|
||||
$candidates = array_values(array_filter($candidates, static function (array $appointment) use ($rangeStart, $rangeEnd): bool {
|
||||
$date = (string) ($appointment['appointment_date'] ?? '');
|
||||
|
||||
return ($rangeStart === '' || $date >= $rangeStart) && ($rangeEnd === '' || $date <= $rangeEnd);
|
||||
}));
|
||||
}
|
||||
if ($candidates === []) {
|
||||
$candidates = $appointments;
|
||||
}
|
||||
|
||||
foreach ($candidates as $appointment) {
|
||||
$status = (int) ($appointment['status'] ?? 0);
|
||||
$date = (string) ($appointment['appointment_date'] ?? '');
|
||||
if (in_array($status, [1, 4], true) && $date >= $today) {
|
||||
return $appointment;
|
||||
}
|
||||
}
|
||||
|
||||
return $candidates[count($candidates) - 1] ?? null;
|
||||
}
|
||||
|
||||
private function maskPhone(string $phone): string
|
||||
{
|
||||
return preg_replace('/^(\d{3})\d{4}(\d{4})$/', '$1****$2', $phone) ?: $phone;
|
||||
}
|
||||
|
||||
private function formatDiagnosisDate($value): string
|
||||
{
|
||||
if (is_numeric($value)) {
|
||||
return (int) $value > 0 ? date('Y-m-d', (int) $value) : '';
|
||||
}
|
||||
$text = trim((string) $value);
|
||||
|
||||
return $text === '' ? '' : substr($text, 0, 10);
|
||||
}
|
||||
|
||||
private function appointmentTimeText(array $appointment): string
|
||||
{
|
||||
$time = trim((string) ($appointment['appointment_time'] ?? ''));
|
||||
if (strlen($time) > 5) {
|
||||
$time = substr($time, 0, 5);
|
||||
}
|
||||
|
||||
return trim((string) ($appointment['appointment_date'] ?? '') . ' ' . $time);
|
||||
}
|
||||
|
||||
private function appointmentStatusText(int $status): string
|
||||
{
|
||||
return [1 => '待面诊', 3 => '已完成', 4 => '已过号'][$status] ?? '未预约';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\firstvisit;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\firstvisit\MyPatientLogic;
|
||||
use app\adminapi\logic\stats\YejiStatsLogic;
|
||||
use app\adminapi\logic\tcm\PrescriptionOrderLogic;
|
||||
use app\common\lists\ListsExtendInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\Order;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\tcm\Prescription;
|
||||
use app\common\model\tcm\PrescriptionOrder;
|
||||
use app\common\model\tcm\PrescriptionOrderPayOrder;
|
||||
use think\db\Query;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* “我的患者”内嵌订单列表。
|
||||
*
|
||||
* 订单可见性始终锚定 diagnosis 别名 d,并复用 MyPatientLogic;订单创建人仅用于展示,
|
||||
* 不能作为患者归属或数据范围条件。
|
||||
*/
|
||||
class MyPatientOrderLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
||||
{
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$rows = $this->buildQuery()
|
||||
->field([
|
||||
'po.id', 'po.order_no', 'po.prescription_id', 'po.diagnosis_id', 'po.creator_id',
|
||||
'po.recipient_name', 'po.recipient_phone', 'po.fee_type', 'po.amount',
|
||||
'po.prescription_audit_status', 'po.payment_slip_audit_status',
|
||||
'po.fulfillment_status', 'po.express_company', 'po.tracking_number', 'po.ship_mode',
|
||||
'po.gancao_reciperl_order_no', 'po.ej_pharmacy_order_no',
|
||||
'po.gancao_submit_time', 'po.ej_pharmacy_submit_time',
|
||||
'po.ej_pharmacy_status', 'po.ej_pharmacy_review_status', 'po.refund_amount',
|
||||
'po.create_time',
|
||||
'd.patient_name', 'd.phone AS patient_phone', 'd.assistant_id',
|
||||
])
|
||||
->order('po.id', 'desc')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $this->appendRelations($rows);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return (int) $this->buildQuery()->count('po.id');
|
||||
}
|
||||
|
||||
public function extend(): array
|
||||
{
|
||||
$query = $this->buildQuery();
|
||||
$pendingQuery = clone $query;
|
||||
$effectiveAmountQuery = clone $query;
|
||||
YejiStatsLogic::applyPrescriptionOrderEffectiveAmountQuery($effectiveAmountQuery, 'po');
|
||||
|
||||
// 拒收指标保留关键词、审核和日期条件,但不受当前履约状态按钮影响,
|
||||
// 避免点击“拒收订单”后分母被收窄为拒收状态而固定显示 100%。
|
||||
$rejectionScopeQuery = $this->buildQuery(true);
|
||||
$rejectionScopeOrderCount = (int) (clone $rejectionScopeQuery)->count('po.id');
|
||||
$rejectedCount = (int) (clone $rejectionScopeQuery)
|
||||
->where('po.fulfillment_status', 9)
|
||||
->count('po.id');
|
||||
$orderCount = (int) (clone $query)->count('po.id');
|
||||
|
||||
return [
|
||||
'summary' => [
|
||||
'orders' => $orderCount,
|
||||
'amount' => round((float) $effectiveAmountQuery->sum('po.amount'), 2),
|
||||
'pending' => (int) $pendingQuery
|
||||
->where(function ($q) {
|
||||
$q->where('po.prescription_audit_status', 0)
|
||||
->whereOr('po.payment_slip_audit_status', 0);
|
||||
})
|
||||
->count('po.id'),
|
||||
'completed' => (int) (clone $query)->whereIn('po.fulfillment_status', [3, 6])->count('po.id'),
|
||||
'rejected' => $rejectedCount,
|
||||
'rejection_rate' => $rejectionScopeOrderCount > 0
|
||||
? round($rejectedCount / $rejectionScopeOrderCount * 100, 2)
|
||||
: 0.0,
|
||||
],
|
||||
'scope' => MyPatientLogic::scopeMeta($this->adminId, $this->adminInfo),
|
||||
];
|
||||
}
|
||||
|
||||
private function buildQuery(bool $ignoreFulfillmentStatus = false): Query
|
||||
{
|
||||
$query = PrescriptionOrder::alias('po')
|
||||
->join('tcm_diagnosis d', 'po.diagnosis_id = d.id')
|
||||
->whereNull('po.delete_time')
|
||||
->whereNull('d.delete_time')
|
||||
->where('d.status', 1);
|
||||
|
||||
MyPatientLogic::applyScope($query, $this->adminId, $this->adminInfo);
|
||||
$this->applyKeyword($query);
|
||||
$this->applyStatusFilters($query, $ignoreFulfillmentStatus);
|
||||
$this->applyDateFilter($query);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function applyKeyword(Query $query): void
|
||||
{
|
||||
$keyword = trim((string) ($this->params['keyword'] ?? ''));
|
||||
if ($keyword === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$like = '%' . $keyword . '%';
|
||||
$q->whereLike('po.order_no', $like)
|
||||
->whereOr('d.patient_name', 'like', $like)
|
||||
->whereOr('d.phone', 'like', $like)
|
||||
->whereOr('po.recipient_name', 'like', $like)
|
||||
->whereOr('po.recipient_phone', 'like', $like);
|
||||
if (preg_match('/^\d+$/', $keyword)) {
|
||||
$id = (int) $keyword;
|
||||
if ($id > 0) {
|
||||
$q->whereOr('po.id', $id)
|
||||
->whereOr('po.prescription_id', $id)
|
||||
->whereOr('po.diagnosis_id', $id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function applyStatusFilters(Query $query, bool $ignoreFulfillmentStatus = false): void
|
||||
{
|
||||
foreach (['prescription_audit_status', 'payment_slip_audit_status', 'fulfillment_status'] as $field) {
|
||||
if ($ignoreFulfillmentStatus && $field === 'fulfillment_status') {
|
||||
continue;
|
||||
}
|
||||
$raw = $this->params[$field] ?? '';
|
||||
if ($raw === '' || $raw === null) {
|
||||
continue;
|
||||
}
|
||||
$query->where('po.' . $field, (int) $raw);
|
||||
}
|
||||
}
|
||||
|
||||
private function applyDateFilter(Query $query): void
|
||||
{
|
||||
[$startDate, $endDate] = $this->dateRange();
|
||||
if ($startDate !== '') {
|
||||
$query->where('po.create_time', '>=', strtotime($startDate . ' 00:00:00'));
|
||||
}
|
||||
if ($endDate !== '') {
|
||||
$query->where('po.create_time', '<=', strtotime($endDate . ' 23:59:59'));
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array{0:string,1:string} */
|
||||
private function dateRange(): array
|
||||
{
|
||||
$startDate = $this->normalizeDate($this->params['start_date'] ?? '');
|
||||
$endDate = $this->normalizeDate($this->params['end_date'] ?? '');
|
||||
if ($startDate === '' && $endDate !== '') {
|
||||
$startDate = $endDate;
|
||||
}
|
||||
if ($endDate === '' && $startDate !== '') {
|
||||
$endDate = $startDate;
|
||||
}
|
||||
if ($startDate !== '' && $endDate !== '' && $startDate > $endDate) {
|
||||
[$startDate, $endDate] = [$endDate, $startDate];
|
||||
}
|
||||
|
||||
return [$startDate, $endDate];
|
||||
}
|
||||
|
||||
private function normalizeDate($value): string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
|
||||
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) ? $value : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $rows
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function appendRelations(array $rows): array
|
||||
{
|
||||
if ($rows === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$orderIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'id')))));
|
||||
$prescriptionIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'prescription_id')))));
|
||||
$creatorIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'creator_id')))));
|
||||
$assistantIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'assistant_id')))));
|
||||
|
||||
$prescriptionMap = [];
|
||||
$doctorIds = [];
|
||||
if ($prescriptionIds !== []) {
|
||||
$prescriptions = Prescription::whereIn('id', $prescriptionIds)
|
||||
->whereNull('delete_time')
|
||||
->field(['id', 'creator_id', 'doctor_name'])
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($prescriptions as $prescription) {
|
||||
$prescriptionId = (int) ($prescription['id'] ?? 0);
|
||||
$doctorId = (int) ($prescription['creator_id'] ?? 0);
|
||||
if ($prescriptionId > 0) {
|
||||
$prescriptionMap[$prescriptionId] = $prescription;
|
||||
}
|
||||
if ($doctorId > 0) {
|
||||
$doctorIds[] = $doctorId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$adminIds = array_values(array_unique(array_merge($creatorIds, $assistantIds, $doctorIds)));
|
||||
$adminNames = $adminIds === []
|
||||
? []
|
||||
: Admin::whereIn('id', $adminIds)->whereNull('delete_time')->column('name', 'id');
|
||||
|
||||
$linkCounts = [];
|
||||
$paidTotals = [];
|
||||
if ($orderIds !== []) {
|
||||
$linkRows = PrescriptionOrderPayOrder::whereIn('prescription_order_id', $orderIds)
|
||||
->field(['prescription_order_id', 'pay_order_id'])
|
||||
->select()
|
||||
->toArray();
|
||||
$payOrderIds = array_values(array_unique(array_filter(array_map('intval', array_column($linkRows, 'pay_order_id')))));
|
||||
$payOrders = $payOrderIds === []
|
||||
? []
|
||||
: Order::whereIn('id', $payOrderIds)
|
||||
->whereNull('delete_time')
|
||||
->field(['id', 'amount', 'status'])
|
||||
->select()
|
||||
->toArray();
|
||||
$payOrderMap = [];
|
||||
foreach ($payOrders as $payOrder) {
|
||||
$payOrderMap[(int) ($payOrder['id'] ?? 0)] = $payOrder;
|
||||
}
|
||||
foreach ($linkRows as $linkRow) {
|
||||
$orderId = (int) ($linkRow['prescription_order_id'] ?? 0);
|
||||
if ($orderId > 0) {
|
||||
$linkCounts[$orderId] = ($linkCounts[$orderId] ?? 0) + 1;
|
||||
}
|
||||
$payOrder = $payOrderMap[(int) ($linkRow['pay_order_id'] ?? 0)] ?? [];
|
||||
if ($orderId > 0 && in_array((int) ($payOrder['status'] ?? 0), [2, 5], true)) {
|
||||
$paidTotals[$orderId] = round(
|
||||
(float) ($paidTotals[$orderId] ?? 0) + (float) ($payOrder['amount'] ?? 0),
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$assistantByDiagnosis = [];
|
||||
foreach ($rows as $row) {
|
||||
$diagnosisId = (int) ($row['diagnosis_id'] ?? 0);
|
||||
if ($diagnosisId > 0) {
|
||||
$assistantByDiagnosis[$diagnosisId] = (int) ($row['assistant_id'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
$claimByOrder = [];
|
||||
if ($orderIds !== []) {
|
||||
$claimRows = Db::name('pharmacy_submission_claim')
|
||||
->whereIn('prescription_order_id', $orderIds)
|
||||
->field(['prescription_order_id', 'target', 'status', 'lease_expires_at'])
|
||||
->order('source_revision', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($claimRows as $claimRow) {
|
||||
$orderId = (int) ($claimRow['prescription_order_id'] ?? 0);
|
||||
if ($orderId > 0 && !isset($claimByOrder[$orderId])) {
|
||||
$claimByOrder[$orderId] = $claimRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
$prescription = $prescriptionMap[(int) ($row['prescription_id'] ?? 0)] ?? [];
|
||||
$doctorId = (int) ($prescription['creator_id'] ?? 0);
|
||||
$doctorName = trim((string) ($prescription['doctor_name'] ?? ''));
|
||||
$creatorId = (int) ($row['creator_id'] ?? 0);
|
||||
$assistantId = (int) ($row['assistant_id'] ?? 0);
|
||||
|
||||
$row['patient_phone_masked'] = $this->maskPhone((string) ($row['patient_phone'] ?? ''));
|
||||
$row['recipient_phone_masked'] = $this->maskPhone((string) ($row['recipient_phone'] ?? ''));
|
||||
unset($row['patient_phone'], $row['recipient_phone']);
|
||||
$row['creator_name'] = (string) ($adminNames[$creatorId] ?? '—');
|
||||
$row['assistant_name'] = (string) ($adminNames[$assistantId] ?? '未分配');
|
||||
$row['doctor_name'] = $doctorName !== '' ? $doctorName : (string) ($adminNames[$doctorId] ?? '—');
|
||||
$row['linked_pay_order_count'] = (int) ($linkCounts[(int) $row['id']] ?? 0);
|
||||
$row['linked_pay_paid_total'] = (float) ($paidTotals[(int) $row['id']] ?? 0);
|
||||
$claim = $claimByOrder[(int) $row['id']] ?? [];
|
||||
$row['pharmacy_claim_target'] = (string) ($claim['target'] ?? '');
|
||||
$row['pharmacy_claim_status'] = (string) ($claim['status'] ?? '');
|
||||
$row['pharmacy_claim_lease_expires_at'] = (int) ($claim['lease_expires_at'] ?? 0);
|
||||
$row['can_upload_pharmacy'] = PrescriptionOrderLogic::canUploadToPharmacy(
|
||||
$row,
|
||||
$this->adminId,
|
||||
$this->adminInfo,
|
||||
$assistantByDiagnosis
|
||||
);
|
||||
$row['create_time_text'] = $this->formatTimestamp($row['create_time'] ?? 0);
|
||||
$row['fee_type_text'] = $this->feeTypeText((int) ($row['fee_type'] ?? 0));
|
||||
$row['prescription_audit_text'] = $this->auditStatusText((int) ($row['prescription_audit_status'] ?? 0));
|
||||
$row['payment_slip_audit_text'] = $this->auditStatusText((int) ($row['payment_slip_audit_status'] ?? 0));
|
||||
$row['fulfillment_text'] = $this->fulfillmentStatusText((int) ($row['fulfillment_status'] ?? 0));
|
||||
$fulfillmentStatus = (int) ($row['fulfillment_status'] ?? 0);
|
||||
$refundAmount = round((float) ($row['refund_amount'] ?? 0), 2);
|
||||
$amountIncluded = !in_array(
|
||||
$fulfillmentStatus,
|
||||
YejiStatsLogic::PRESCRIPTION_ORDER_FULFILLMENT_EXCLUDED_FROM_PERFORMANCE,
|
||||
true
|
||||
) && $refundAmount <= 0;
|
||||
$row['amount_included'] = $amountIncluded;
|
||||
$row['effective_amount'] = $amountIncluded ? round((float) ($row['amount'] ?? 0), 2) : 0.0;
|
||||
$row['amount_exclusion_text'] = $amountIncluded
|
||||
? ''
|
||||
: ($refundAmount > 0 || $fulfillmentStatus === 10 ? '退款不计入' : $row['fulfillment_text'] . '不计入');
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
private function maskPhone(string $phone): string
|
||||
{
|
||||
return preg_replace('/^(\d{3})\d{4}(\d{4})$/', '$1****$2', $phone) ?: $phone;
|
||||
}
|
||||
|
||||
private function formatTimestamp($value): string
|
||||
{
|
||||
return is_numeric($value) && (int) $value > 0 ? date('Y-m-d H:i', (int) $value) : '';
|
||||
}
|
||||
|
||||
private function auditStatusText(int $status): string
|
||||
{
|
||||
return [0 => '待审核', 1 => '已通过', 2 => '已驳回'][$status] ?? '未知';
|
||||
}
|
||||
|
||||
private function feeTypeText(int $type): string
|
||||
{
|
||||
return [1 => '挂号', 2 => '问诊', 3 => '药品', 4 => '首付', 5 => '尾款', 6 => '其他', 7 => '全部'][$type] ?? '其他';
|
||||
}
|
||||
|
||||
private function fulfillmentStatusText(int $status): string
|
||||
{
|
||||
return [
|
||||
1 => '待双审通过', 2 => '待发货', 3 => '已完成', 4 => '已取消',
|
||||
5 => '已发货', 6 => '已签收', 7 => '进行中', 8 => '暂不制药',
|
||||
9 => '拒收', 10 => '退款', 11 => '保留药方', 12 => '制药缓发',
|
||||
][$status] ?? '未知';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,702 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\firstvisit;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\firstvisit\MyPatientLogic;
|
||||
use app\common\lists\ListsExtendInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\DiagnosisViewRecord;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\auth\AdminRole;
|
||||
use app\common\model\doctor\Appointment;
|
||||
use app\common\model\doctor\Roster;
|
||||
use app\common\model\tcm\Prescription;
|
||||
use app\common\service\doctor\RosterSegmentService;
|
||||
use think\db\Query;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* “我的患者”内嵌面诊进度。
|
||||
*
|
||||
* 一条挂号一行;只返回脱敏患者信息,并严格复用 MyPatientLogic 的患者级范围。
|
||||
*/
|
||||
class MyPatientProgressLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
||||
{
|
||||
private const EFFECTIVE_STATUSES = [1, 3, 4];
|
||||
private const AVG_MINUTES_PER_VISIT = 15;
|
||||
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$rows = $this->buildQuery(true)
|
||||
->field([
|
||||
'a.id', 'a.patient_id AS diagnosis_id', 'a.doctor_id', 'a.appointment_date',
|
||||
'a.appointment_time', 'a.appointment_type', 'a.status', 'a.create_time',
|
||||
'd.patient_id AS source_patient_id', 'd.patient_name', 'd.phone', 'd.gender', 'd.age',
|
||||
'd.assistant_id', 'doctor_admin.name AS doctor_name', 'assistant_admin.name AS assistant_name',
|
||||
])
|
||||
->order('a.appointment_date', 'asc')
|
||||
->order('a.appointment_time', 'asc')
|
||||
->order('a.id', 'asc')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $this->appendProgress($rows);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return (int) $this->buildQuery(true)->count('a.id');
|
||||
}
|
||||
|
||||
public function extend(): array
|
||||
{
|
||||
$query = $this->buildQuery(false);
|
||||
[$startDate, $endDate] = $this->dateRange();
|
||||
$summary = [
|
||||
'total' => (int) (clone $query)->count('a.id'),
|
||||
'booked' => (int) (clone $query)->where('a.status', 1)->count('a.id'),
|
||||
'completed' => (int) (clone $query)->where('a.status', 3)->count('a.id'),
|
||||
'missed' => (int) (clone $query)->where('a.status', 4)->count('a.id'),
|
||||
];
|
||||
$scheduleMode = $this->usesOwnershipSchedule() ? 'ownership' : 'roster';
|
||||
$weekSchedule = $scheduleMode === 'ownership' ? $this->ownershipWeekSchedule() : $this->weekSchedule();
|
||||
$todaySchedule = $weekSchedule[0] ?? $this->emptyScheduleDay(date('Y-m-d'));
|
||||
$todayOverview = $scheduleMode === 'ownership'
|
||||
? [
|
||||
'total_visits' => (int) ($todaySchedule['total_appointments'] ?? 0),
|
||||
'booked' => (int) ($todaySchedule['waiting_appointments'] ?? 0),
|
||||
'completed' => (int) ($todaySchedule['completed_appointments'] ?? 0),
|
||||
'missed' => (int) ($todaySchedule['missed_appointments'] ?? 0),
|
||||
'empty_slots' => 0,
|
||||
'passed_slots' => 0,
|
||||
'remaining_slots' => 0,
|
||||
'doctor_count' => (int) ($todaySchedule['doctor_count'] ?? 0),
|
||||
]
|
||||
: [
|
||||
'total_visits' => (int) ($todaySchedule['total_slots'] ?? 0),
|
||||
'booked' => (int) ($todaySchedule['booked_slots'] ?? 0),
|
||||
'completed' => 0,
|
||||
'missed' => 0,
|
||||
// 空号口径:剩余可预约号源(未过时刻且未被有效挂号占用),过号单独给出
|
||||
'empty_slots' => (int) ($todaySchedule['remaining_slots'] ?? 0),
|
||||
'passed_slots' => (int) ($todaySchedule['passed_slots'] ?? 0),
|
||||
'remaining_slots' => (int) ($todaySchedule['remaining_slots'] ?? 0),
|
||||
'doctor_count' => (int) ($todaySchedule['doctor_count'] ?? 0),
|
||||
];
|
||||
|
||||
return [
|
||||
'summary' => $summary,
|
||||
'schedule_mode' => $scheduleMode,
|
||||
'today_overview' => $todayOverview,
|
||||
'week_schedule' => $weekSchedule,
|
||||
'scope' => MyPatientLogic::scopeMeta($this->adminId, $this->adminInfo),
|
||||
'dates' => ['start' => $startDate, 'end' => $endDate],
|
||||
];
|
||||
}
|
||||
|
||||
private function buildQuery(bool $applyStatus): Query
|
||||
{
|
||||
$query = Appointment::alias('a')
|
||||
->join('tcm_diagnosis d', 'a.patient_id = d.id')
|
||||
->leftJoin('admin doctor_admin', 'a.doctor_id = doctor_admin.id')
|
||||
->leftJoin('admin assistant_admin', 'CAST(d.assistant_id AS UNSIGNED) = assistant_admin.id')
|
||||
->whereNull('d.delete_time')
|
||||
->where('d.status', 1);
|
||||
|
||||
MyPatientLogic::applyScope($query, $this->adminId, $this->adminInfo);
|
||||
$this->applyKeyword($query);
|
||||
$this->applyDateFilter($query);
|
||||
|
||||
if ($applyStatus) {
|
||||
$status = $this->params['status'] ?? '';
|
||||
if ($status !== '' && $status !== null && in_array((int) $status, self::EFFECTIVE_STATUSES, true)) {
|
||||
$query->where('a.status', (int) $status);
|
||||
} else {
|
||||
$query->whereIn('a.status', self::EFFECTIVE_STATUSES);
|
||||
}
|
||||
} else {
|
||||
$query->whereIn('a.status', self::EFFECTIVE_STATUSES);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function applyKeyword(Query $query): void
|
||||
{
|
||||
$keyword = trim((string) ($this->params['keyword'] ?? ''));
|
||||
if ($keyword === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$like = '%' . $keyword . '%';
|
||||
$q->whereLike('d.patient_name', $like)
|
||||
->whereOr('d.phone', 'like', $like)
|
||||
->whereOr('doctor_admin.name', 'like', $like)
|
||||
->whereOr('assistant_admin.name', 'like', $like);
|
||||
if (preg_match('/^\d+$/', $keyword)) {
|
||||
$id = (int) $keyword;
|
||||
if ($id > 0) {
|
||||
$q->whereOr('a.id', $id)->whereOr('d.id', $id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function applyDateFilter(Query $query): void
|
||||
{
|
||||
[$startDate, $endDate] = $this->dateRange();
|
||||
$query->whereBetween('a.appointment_date', [$startDate, $endDate]);
|
||||
}
|
||||
|
||||
/** @return array{0:string,1:string} */
|
||||
private function dateRange(): array
|
||||
{
|
||||
$today = date('Y-m-d');
|
||||
$startDate = $this->normalizeDate($this->params['start_date'] ?? '') ?: $today;
|
||||
$endDate = $this->normalizeDate($this->params['end_date'] ?? '') ?: $startDate;
|
||||
if ($startDate > $endDate) {
|
||||
[$startDate, $endDate] = [$endDate, $startDate];
|
||||
}
|
||||
|
||||
$startTs = strtotime($startDate);
|
||||
$endTs = strtotime($endDate);
|
||||
if ($startTs !== false && $endTs !== false && $endTs - $startTs > 31 * 86400) {
|
||||
$endDate = date('Y-m-d', $startTs + 31 * 86400);
|
||||
}
|
||||
|
||||
return [$startDate, $endDate];
|
||||
}
|
||||
|
||||
private function normalizeDate($value): string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
|
||||
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) ? $value : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $rows
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function appendProgress(array $rows): array
|
||||
{
|
||||
if ($rows === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$diagnosisIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'diagnosis_id')))));
|
||||
$appointmentIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'id')))));
|
||||
$queuePositionMap = $this->queuePositionMap($rows);
|
||||
|
||||
$confirmedSet = [];
|
||||
if ($diagnosisIds !== []) {
|
||||
$viewTable = (new DiagnosisViewRecord())->getTable();
|
||||
$confirmedIds = Db::table($viewTable)
|
||||
->whereIn('diagnosis_id', $diagnosisIds)
|
||||
->where('is_confirmed', 1)
|
||||
->whereNull('delete_time')
|
||||
->column('diagnosis_id');
|
||||
$confirmedSet = array_fill_keys(array_map('intval', $confirmedIds), true);
|
||||
}
|
||||
|
||||
$prescriptionMap = [];
|
||||
if ($appointmentIds !== []) {
|
||||
$prescriptions = Prescription::whereIn('appointment_id', $appointmentIds)
|
||||
->whereNull('delete_time')
|
||||
->where('void_status', 0)
|
||||
->field(['id', 'appointment_id', 'audit_status', 'is_system_auto'])
|
||||
->order('id', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($prescriptions as $prescription) {
|
||||
$appointmentId = (int) ($prescription['appointment_id'] ?? 0);
|
||||
if ($appointmentId > 0 && !isset($prescriptionMap[$appointmentId])) {
|
||||
$prescriptionMap[$appointmentId] = $prescription;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
$appointmentId = (int) ($row['id'] ?? 0);
|
||||
$diagnosisId = (int) ($row['diagnosis_id'] ?? 0);
|
||||
$status = (int) ($row['status'] ?? 0);
|
||||
$prescription = $prescriptionMap[$appointmentId] ?? [];
|
||||
$confirmed = isset($confirmedSet[$diagnosisId]);
|
||||
$prescribed = $prescription !== [];
|
||||
$aheadCount = $status === 1 ? (int) ($queuePositionMap[$appointmentId] ?? 0) : 0;
|
||||
|
||||
$row['phone_masked'] = $this->maskPhone((string) ($row['phone'] ?? ''));
|
||||
unset($row['phone']);
|
||||
$row['gender_desc'] = (int) ($row['gender'] ?? 0) === 1 ? '男' : '女';
|
||||
$row['assistant_name'] = trim((string) ($row['assistant_name'] ?? '')) ?: '未分配';
|
||||
$row['doctor_name'] = trim((string) ($row['doctor_name'] ?? '')) ?: '未知医生';
|
||||
$row['appointment_time_text'] = $this->appointmentTimeText($row);
|
||||
$row['status_text'] = $this->appointmentStatusText($status);
|
||||
$row['appointment_type_text'] = $this->appointmentTypeText((string) ($row['appointment_type'] ?? ''));
|
||||
$row['registered'] = 1;
|
||||
$row['diagnosis_confirmed'] = $confirmed ? 1 : 0;
|
||||
$row['visit_completed'] = $status === 3 ? 1 : 0;
|
||||
$row['has_prescription'] = $prescribed ? 1 : 0;
|
||||
$row['prescription_id'] = (int) ($prescription['id'] ?? 0);
|
||||
$row['prescription_audit_status'] = $prescribed ? (int) ($prescription['audit_status'] ?? 0) : -1;
|
||||
$row['progress_text'] = $this->progressText($confirmed, $status === 3, $prescribed, $status);
|
||||
$row['queue_no'] = $status === 1 ? $aheadCount + 1 : 0;
|
||||
$row['ahead_count'] = $aheadCount;
|
||||
$row['estimated_wait_minutes'] = $aheadCount * self::AVG_MINUTES_PER_VISIT;
|
||||
$row['queue_status'] = $this->queueStatus($status, $confirmed, $aheadCount);
|
||||
$row['queue_status_text'] = $this->queueStatusText((string) $row['queue_status']);
|
||||
$row['is_self_patient'] = (
|
||||
(int) ($row['assistant_id'] ?? 0) === $this->adminId
|
||||
|| (int) ($row['doctor_id'] ?? 0) === $this->adminId
|
||||
) ? 1 : 0;
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 候诊位次按 progress.vue 的真实规则计算:同医生、同日、待就诊,按预约时刻和挂号 ID 升序。
|
||||
* 队列计算读取完整医生队列,只向当前范围列表返回人数,不暴露范围外患者身份。
|
||||
*
|
||||
* @param array<int,array<string,mixed>> $rows
|
||||
* @return array<int,int>
|
||||
*/
|
||||
private function queuePositionMap(array $rows): array
|
||||
{
|
||||
$doctorIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'doctor_id')))));
|
||||
$dates = array_values(array_unique(array_filter(array_map('strval', array_column($rows, 'appointment_date')))));
|
||||
if ($doctorIds === [] || $dates === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$queueRows = Appointment::whereIn('doctor_id', $doctorIds)
|
||||
->whereIn('appointment_date', $dates)
|
||||
->where('status', 1)
|
||||
->field(['id', 'doctor_id', 'appointment_date', 'appointment_time'])
|
||||
->order('doctor_id', 'asc')
|
||||
->order('appointment_date', 'asc')
|
||||
->order('appointment_time', 'asc')
|
||||
->order('id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$groupCounts = [];
|
||||
$positions = [];
|
||||
foreach ($queueRows as $queueRow) {
|
||||
$group = (int) ($queueRow['doctor_id'] ?? 0) . '|' . (string) ($queueRow['appointment_date'] ?? '');
|
||||
$positions[(int) ($queueRow['id'] ?? 0)] = (int) ($groupCounts[$group] ?? 0);
|
||||
$groupCounts[$group] = (int) ($groupCounts[$group] ?? 0) + 1;
|
||||
}
|
||||
|
||||
return $positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 未来七天号源:完全复用 paiban/availableSlots 的生成口径,按医生+日期+时刻去重。
|
||||
*
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
private function weekSchedule(): array
|
||||
{
|
||||
$startDate = date('Y-m-d');
|
||||
$endDate = date('Y-m-d', strtotime($startDate . ' +6 days'));
|
||||
$days = [];
|
||||
for ($offset = 0; $offset < 7; $offset++) {
|
||||
$date = date('Y-m-d', strtotime($startDate . " +{$offset} days"));
|
||||
$days[$date] = $this->emptyScheduleDay($date);
|
||||
}
|
||||
|
||||
$doctorIds = $this->visibleDoctorIds($startDate, $endDate);
|
||||
if ($doctorIds === []) {
|
||||
return array_values($days);
|
||||
}
|
||||
|
||||
$rosters = Roster::whereIn('doctor_id', $doctorIds)
|
||||
->whereBetween('date', [$startDate, $endDate])
|
||||
->where('status', 1)
|
||||
->whereNull('delete_time')
|
||||
->field(['doctor_id', 'date', 'period', 'start_time', 'end_time', 'slot_minutes', 'quota'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$doctorNames = Admin::whereIn('id', $doctorIds)->column('name', 'id');
|
||||
|
||||
$doctorSlotSets = [];
|
||||
$doctorWindowSets = [];
|
||||
foreach ($rosters as $roster) {
|
||||
$date = (string) ($roster['date'] ?? '');
|
||||
$doctorId = (int) ($roster['doctor_id'] ?? 0);
|
||||
$window = RosterSegmentService::resolveWindow($roster);
|
||||
if (!isset($days[$date]) || $doctorId <= 0 || $window === null) {
|
||||
continue;
|
||||
}
|
||||
[$startTime, $endTime] = $window;
|
||||
$times = RosterSegmentService::generateSlotTimes(
|
||||
$startTime,
|
||||
$endTime,
|
||||
RosterSegmentService::normalizeSlotMinutes($roster['slot_minutes'] ?? 15)
|
||||
);
|
||||
$times = RosterSegmentService::applyQuotaCap($times, (int) ($roster['quota'] ?? 0));
|
||||
foreach ($times as $time) {
|
||||
$doctorSlotSets[$date][$doctorId][$time] = true;
|
||||
}
|
||||
$doctorWindowSets[$date][$doctorId][$startTime . '-' . $endTime] = true;
|
||||
}
|
||||
|
||||
$appointments = Appointment::whereIn('doctor_id', $doctorIds)
|
||||
->whereBetween('appointment_date', [$startDate, $endDate])
|
||||
->where('status', 1)
|
||||
->field(['doctor_id', 'appointment_date', 'appointment_time'])
|
||||
->select()
|
||||
->toArray();
|
||||
$doctorBookedSets = [];
|
||||
foreach ($appointments as $appointment) {
|
||||
$date = (string) ($appointment['appointment_date'] ?? '');
|
||||
$doctorId = (int) ($appointment['doctor_id'] ?? 0);
|
||||
$time = substr((string) ($appointment['appointment_time'] ?? ''), 0, 5);
|
||||
if (isset($doctorSlotSets[$date][$doctorId][$time])) {
|
||||
$doctorBookedSets[$date][$doctorId][$time] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$nowHm = date('H:i');
|
||||
|
||||
foreach ($days as $date => &$day) {
|
||||
$doctorDetails = [];
|
||||
$total = 0;
|
||||
$booked = 0;
|
||||
$passed = 0;
|
||||
$remaining = 0;
|
||||
foreach ($doctorSlotSets[$date] ?? [] as $doctorId => $slotSet) {
|
||||
$doctorTotal = count($slotSet);
|
||||
$bookedSet = $doctorBookedSets[$date][$doctorId] ?? [];
|
||||
$doctorBooked = count($bookedSet);
|
||||
$doctorPassed = 0;
|
||||
$doctorRemaining = 0;
|
||||
foreach ($slotSet as $time => $_) {
|
||||
if (isset($bookedSet[$time])) {
|
||||
continue;
|
||||
}
|
||||
// 与挂号选号一致:今日已过(含当前分钟)的未约号源计为过号,其余为剩余可约
|
||||
$isPassed = $date < $today || ($date === $today && strcmp((string) $time, $nowHm) <= 0);
|
||||
if ($isPassed) {
|
||||
$doctorPassed++;
|
||||
} else {
|
||||
$doctorRemaining++;
|
||||
}
|
||||
}
|
||||
$total += $doctorTotal;
|
||||
$booked += $doctorBooked;
|
||||
$passed += $doctorPassed;
|
||||
$remaining += $doctorRemaining;
|
||||
$scheduleWindows = array_values(array_keys($doctorWindowSets[$date][$doctorId] ?? []));
|
||||
sort($scheduleWindows, SORT_STRING);
|
||||
$doctorDetails[] = [
|
||||
'doctor_id' => (int) $doctorId,
|
||||
'doctor_name' => trim((string) ($doctorNames[$doctorId] ?? '')) ?: '未知医生',
|
||||
'schedule_windows' => $scheduleWindows,
|
||||
'total_slots' => $doctorTotal,
|
||||
'booked_slots' => $doctorBooked,
|
||||
'passed_slots' => $doctorPassed,
|
||||
'remaining_slots' => $doctorRemaining,
|
||||
// 空号对外展示剩余可约;过号/未挂号细分见 passed/remaining
|
||||
'empty_slots' => $doctorRemaining,
|
||||
];
|
||||
}
|
||||
usort($doctorDetails, static function (array $left, array $right): int {
|
||||
return $right['remaining_slots'] <=> $left['remaining_slots']
|
||||
?: $right['booked_slots'] <=> $left['booked_slots']
|
||||
?: $right['total_slots'] <=> $left['total_slots']
|
||||
?: strcmp((string) $left['doctor_name'], (string) $right['doctor_name']);
|
||||
});
|
||||
$day['total_slots'] = $total;
|
||||
$day['booked_slots'] = $booked;
|
||||
$day['passed_slots'] = $passed;
|
||||
$day['remaining_slots'] = $remaining;
|
||||
$day['empty_slots'] = $remaining;
|
||||
$day['doctor_count'] = count($doctorDetails);
|
||||
$day['doctors'] = $doctorDetails;
|
||||
}
|
||||
unset($day);
|
||||
|
||||
return array_values($days);
|
||||
}
|
||||
|
||||
/**
|
||||
* 医助“本人归属”只统计其患者的真实挂号,不再把历史接诊医生的整周号源算到本人名下。
|
||||
*
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
private function ownershipWeekSchedule(): array
|
||||
{
|
||||
$startDate = date('Y-m-d');
|
||||
$endDate = date('Y-m-d', strtotime($startDate . ' +6 days'));
|
||||
$days = [];
|
||||
for ($offset = 0; $offset < 7; $offset++) {
|
||||
$date = date('Y-m-d', strtotime($startDate . " +{$offset} days"));
|
||||
$days[$date] = $this->emptyScheduleDay($date);
|
||||
}
|
||||
|
||||
$query = Appointment::alias('ownership_a')
|
||||
->join('tcm_diagnosis d', 'ownership_a.patient_id = d.id')
|
||||
->leftJoin('admin ownership_doctor', 'ownership_a.doctor_id = ownership_doctor.id')
|
||||
->whereNull('d.delete_time')
|
||||
->where('d.status', 1)
|
||||
->whereBetween('ownership_a.appointment_date', [$startDate, $endDate])
|
||||
->whereIn('ownership_a.status', self::EFFECTIVE_STATUSES);
|
||||
MyPatientLogic::applyScope($query, $this->adminId, $this->adminInfo);
|
||||
|
||||
$appointments = $query
|
||||
->field([
|
||||
'ownership_a.id', 'ownership_a.doctor_id', 'ownership_a.appointment_date',
|
||||
'ownership_a.appointment_time', 'ownership_a.status',
|
||||
'ownership_doctor.name AS doctor_name',
|
||||
])
|
||||
->order('ownership_a.appointment_date', 'asc')
|
||||
->order('ownership_a.appointment_time', 'asc')
|
||||
->order('ownership_a.id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$doctorDetails = [];
|
||||
foreach ($appointments as $appointment) {
|
||||
$date = (string) ($appointment['appointment_date'] ?? '');
|
||||
$doctorId = (int) ($appointment['doctor_id'] ?? 0);
|
||||
if (!isset($days[$date]) || $doctorId <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($doctorDetails[$date][$doctorId])) {
|
||||
$doctorDetails[$date][$doctorId] = [
|
||||
'doctor_id' => $doctorId,
|
||||
'doctor_name' => trim((string) ($appointment['doctor_name'] ?? '')) ?: '未知医生',
|
||||
'appointment_time_set' => [],
|
||||
'total_appointments' => 0,
|
||||
'waiting_appointments' => 0,
|
||||
'completed_appointments' => 0,
|
||||
'missed_appointments' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$time = substr((string) ($appointment['appointment_time'] ?? ''), 0, 5);
|
||||
if ($time !== '') {
|
||||
$doctorDetails[$date][$doctorId]['appointment_time_set'][$time] = true;
|
||||
}
|
||||
$status = (int) ($appointment['status'] ?? 0);
|
||||
$doctorDetails[$date][$doctorId]['total_appointments']++;
|
||||
if ($status === 1) {
|
||||
$doctorDetails[$date][$doctorId]['waiting_appointments']++;
|
||||
} elseif ($status === 3) {
|
||||
$doctorDetails[$date][$doctorId]['completed_appointments']++;
|
||||
} elseif ($status === 4) {
|
||||
$doctorDetails[$date][$doctorId]['missed_appointments']++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($days as $date => &$day) {
|
||||
$rows = [];
|
||||
foreach ($doctorDetails[$date] ?? [] as $doctor) {
|
||||
$times = array_values(array_keys($doctor['appointment_time_set'] ?? []));
|
||||
sort($times, SORT_STRING);
|
||||
unset($doctor['appointment_time_set']);
|
||||
$doctor['appointment_times'] = $times;
|
||||
$rows[] = $doctor;
|
||||
}
|
||||
usort($rows, static function (array $left, array $right): int {
|
||||
return $right['waiting_appointments'] <=> $left['waiting_appointments']
|
||||
?: $right['total_appointments'] <=> $left['total_appointments']
|
||||
?: strcmp((string) $left['doctor_name'], (string) $right['doctor_name']);
|
||||
});
|
||||
|
||||
$day['total_appointments'] = array_sum(array_column($rows, 'total_appointments'));
|
||||
$day['waiting_appointments'] = array_sum(array_column($rows, 'waiting_appointments'));
|
||||
$day['completed_appointments'] = array_sum(array_column($rows, 'completed_appointments'));
|
||||
$day['missed_appointments'] = array_sum(array_column($rows, 'missed_appointments'));
|
||||
$day['doctor_count'] = count($rows);
|
||||
$day['doctors'] = $rows;
|
||||
}
|
||||
unset($day);
|
||||
|
||||
return array_values($days);
|
||||
}
|
||||
|
||||
private function usesOwnershipSchedule(): bool
|
||||
{
|
||||
if ((int) ($this->adminInfo['root'] ?? 0) === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$roleIds = $this->currentRoleIds();
|
||||
|
||||
return in_array(2, $roleIds, true) && array_intersect($roleIds, [3, 7, 8]) === [];
|
||||
}
|
||||
|
||||
/** @return int[] */
|
||||
private function visibleDoctorIds(string $startDate, string $endDate): array
|
||||
{
|
||||
if ((int) ($this->adminInfo['root'] ?? 0) === 1) {
|
||||
$doctorIds = array_values(array_unique(array_map('intval', Roster::whereBetween('date', [$startDate, $endDate])
|
||||
->where('status', 1)
|
||||
->whereNull('delete_time')
|
||||
->column('doctor_id'))));
|
||||
|
||||
return $this->activeDoctorIds($doctorIds);
|
||||
}
|
||||
|
||||
$roleIds = $this->currentRoleIds();
|
||||
$isTeamRole = array_intersect($roleIds, [3, 7, 8]) !== [];
|
||||
$isDoctor = in_array(1, $roleIds, true);
|
||||
$isAssistant = in_array(2, $roleIds, true);
|
||||
|
||||
// 纯医生账号的概览只统计本人排班,避免同一患者曾由其他医生接诊时放大到其他医生。
|
||||
if (!$isTeamRole && $isDoctor && !$isAssistant) {
|
||||
return $this->activeDoctorIds([$this->adminId]);
|
||||
}
|
||||
|
||||
$query = Appointment::alias('scope_a')
|
||||
->join('tcm_diagnosis d', 'scope_a.patient_id = d.id')
|
||||
->whereNull('d.delete_time')
|
||||
->where('d.status', 1)
|
||||
->whereIn('scope_a.status', self::EFFECTIVE_STATUSES)
|
||||
->where('scope_a.doctor_id', '>', 0);
|
||||
MyPatientLogic::applyScope($query, $this->adminId, $this->adminInfo);
|
||||
|
||||
$doctorIds = array_values(array_unique(array_filter(array_map('intval', $query->distinct(true)->column('scope_a.doctor_id')))));
|
||||
if (!$isTeamRole && $isDoctor) {
|
||||
$doctorIds[] = $this->adminId;
|
||||
}
|
||||
|
||||
return $this->activeDoctorIds(array_values(array_unique($doctorIds)));
|
||||
}
|
||||
|
||||
/** @return int[] */
|
||||
private function currentRoleIds(): array
|
||||
{
|
||||
return array_values(array_unique(array_filter(array_map('intval', AdminRole::where('admin_id', $this->adminId)->column('role_id')))));
|
||||
}
|
||||
|
||||
/** @param int[] $doctorIds @return int[] */
|
||||
private function activeDoctorIds(array $doctorIds): array
|
||||
{
|
||||
$doctorIds = array_values(array_unique(array_filter(array_map('intval', $doctorIds))));
|
||||
if ($doctorIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$roleDoctorIds = array_values(array_unique(array_map('intval', AdminRole::whereIn('admin_id', $doctorIds)
|
||||
->where('role_id', 1)
|
||||
->column('admin_id'))));
|
||||
if ($roleDoctorIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$activeSet = array_fill_keys(array_map('intval', Admin::whereIn('id', $roleDoctorIds)
|
||||
->where('disable', 0)
|
||||
->column('id')), true);
|
||||
|
||||
return array_values(array_filter($doctorIds, static function (int $doctorId) use ($activeSet): bool {
|
||||
return isset($activeSet[$doctorId]);
|
||||
}));
|
||||
}
|
||||
|
||||
/** @return array<string,mixed> */
|
||||
private function emptyScheduleDay(string $date): array
|
||||
{
|
||||
$weekdayLabels = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
$timestamp = strtotime($date) ?: time();
|
||||
|
||||
return [
|
||||
'date' => $date,
|
||||
'date_text' => date('m-d', $timestamp),
|
||||
'weekday' => $weekdayLabels[(int) date('w', $timestamp)],
|
||||
'total_slots' => 0,
|
||||
'booked_slots' => 0,
|
||||
'passed_slots' => 0,
|
||||
'remaining_slots' => 0,
|
||||
'empty_slots' => 0,
|
||||
'doctor_count' => 0,
|
||||
'doctors' => [],
|
||||
'total_appointments' => 0,
|
||||
'waiting_appointments' => 0,
|
||||
'completed_appointments' => 0,
|
||||
'missed_appointments' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
private function queueStatus(int $status, bool $confirmed, int $aheadCount): string
|
||||
{
|
||||
if ($status === 3) {
|
||||
return 'completed';
|
||||
}
|
||||
if ($status === 4) {
|
||||
return 'missed';
|
||||
}
|
||||
if ($confirmed) {
|
||||
return 'consulting';
|
||||
}
|
||||
|
||||
return $aheadCount === 0 ? 'next' : 'waiting';
|
||||
}
|
||||
|
||||
private function queueStatusText(string $status): string
|
||||
{
|
||||
return [
|
||||
'completed' => '已完成',
|
||||
'missed' => '已过号',
|
||||
'consulting' => '就诊中',
|
||||
'next' => '待确认',
|
||||
'waiting' => '等待中',
|
||||
][$status] ?? '等待中';
|
||||
}
|
||||
|
||||
private function maskPhone(string $phone): string
|
||||
{
|
||||
return preg_replace('/^(\d{3})\d{4}(\d{4})$/', '$1****$2', $phone) ?: $phone;
|
||||
}
|
||||
|
||||
private function appointmentTimeText(array $row): string
|
||||
{
|
||||
$time = trim((string) ($row['appointment_time'] ?? ''));
|
||||
if (strlen($time) > 5) {
|
||||
$time = substr($time, 0, 5);
|
||||
}
|
||||
|
||||
return trim((string) ($row['appointment_date'] ?? '') . ' ' . $time);
|
||||
}
|
||||
|
||||
private function appointmentStatusText(int $status): string
|
||||
{
|
||||
return [1 => '已挂号', 3 => '已完成', 4 => '已过号'][$status] ?? '未知';
|
||||
}
|
||||
|
||||
private function appointmentTypeText(string $type): string
|
||||
{
|
||||
return ['video' => '视频问诊', 'text' => '图文问诊', 'phone' => '电话问诊'][$type] ?? '面诊';
|
||||
}
|
||||
|
||||
private function progressText(bool $confirmed, bool $completed, bool $prescribed, int $status): string
|
||||
{
|
||||
if ($status === 4) {
|
||||
return '已过号';
|
||||
}
|
||||
if (!$confirmed) {
|
||||
return '待确认诊单';
|
||||
}
|
||||
if (!$completed) {
|
||||
return '待完诊';
|
||||
}
|
||||
|
||||
return $prescribed ? '已开方' : '待开方';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\notice;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\notice\NoticeSetting;
|
||||
|
||||
/**
|
||||
* 通知设置
|
||||
* Class NoticeSettingLists
|
||||
* @package app\adminapi\lists\notice
|
||||
*/
|
||||
class NoticeSettingLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
/**
|
||||
* @notes 搜索条件
|
||||
* @return \string[][]
|
||||
* @author ljj
|
||||
* @date 2022/2/17 2:21 下午
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['recipient', 'type']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 通知设置列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author ljj
|
||||
* @date 2022/2/16 3:18 下午
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = (new NoticeSetting())->field('id,scene_name,sms_notice,type')
|
||||
->append(['sms_status_desc','type_desc'])
|
||||
->where($this->searchWhere)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 通知设置数量
|
||||
* @return int
|
||||
* @author ljj
|
||||
* @date 2022/2/16 3:18 下午
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return (new NoticeSetting())->where($this->searchWhere)->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\order;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
use app\common\model\Order;
|
||||
use app\common\model\auth\AdminRole;
|
||||
|
||||
/**
|
||||
* 订单列表
|
||||
* Class OrderLists
|
||||
* @package app\adminapi\lists\order
|
||||
*/
|
||||
class OrderLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
/**
|
||||
* 将列表「创建时间」参数规范为可解析的日期时间(datetimerange 已是 Y-m-d H:i:s,不能再去拼接 00:00:00,否则会变成非法字符串)
|
||||
*/
|
||||
private function normalizeListDateTimeString(string $raw, bool $isEnd): string
|
||||
{
|
||||
$raw = trim($raw);
|
||||
if ($raw === '') {
|
||||
return '';
|
||||
}
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $raw) === 1) {
|
||||
return $isEnd ? ($raw . ' 23:59:59') : ($raw . ' 00:00:00');
|
||||
}
|
||||
if (strlen($raw) === 19 && preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $raw) === 1) {
|
||||
return $raw;
|
||||
}
|
||||
$ts = strtotime($raw);
|
||||
if ($ts !== false) {
|
||||
return date('Y-m-d H:i:s', $ts);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 患者关键词:在诊单表按姓名、手机号或诊单ID匹配(子查询里显式 whereOr,避免 | 在闭包/子查下兼容问题)
|
||||
*
|
||||
* @return \Closure|array{} 无匹配关键词时返回空数组表示不加条件
|
||||
*/
|
||||
private function patientKeywordSubWhere()
|
||||
{
|
||||
$kw = trim((string) ($this->params['patient_keyword'] ?? ''));
|
||||
if ($kw === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return function ($query) use ($kw) {
|
||||
$query->table('zyt_tcm_diagnosis')
|
||||
->whereNull('delete_time')
|
||||
->where(function ($q) use ($kw) {
|
||||
$q->where('patient_name', 'like', '%' . $kw . '%')
|
||||
->whereOr('phone', 'like', '%' . $kw . '%');
|
||||
if (preg_match('/^\d+$/', $kw) && (int) $kw > 0) {
|
||||
$q->whereOr('id', '=', (int) $kw);
|
||||
}
|
||||
})
|
||||
->field('id');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建时间:支持 int 时间戳 与 字符串 datetime 两种存法(用 OR 包裹,至少命中一类)
|
||||
*/
|
||||
private function appendCreateTimeWhere(array &$where): void
|
||||
{
|
||||
$s = !empty($this->params['create_time_start'])
|
||||
? $this->normalizeListDateTimeString((string) $this->params['create_time_start'], false)
|
||||
: '';
|
||||
$e = !empty($this->params['create_time_end'])
|
||||
? $this->normalizeListDateTimeString((string) $this->params['create_time_end'], true)
|
||||
: '';
|
||||
if ($s === '' && $e === '') {
|
||||
return;
|
||||
}
|
||||
$i0 = $s !== '' ? (int) strtotime($s) : 0;
|
||||
$i1 = $e !== '' ? (int) strtotime($e) : 0;
|
||||
if ($i0 > 0 && $i1 > 0 && $i1 < $i0) {
|
||||
$t = $i0;
|
||||
$i0 = $i1;
|
||||
$i1 = $t;
|
||||
$ts = $s;
|
||||
$s = $e;
|
||||
$e = $ts;
|
||||
}
|
||||
$intOk = $i0 > 0 || $i1 > 0;
|
||||
$strOk = $s !== '' || $e !== '';
|
||||
if (!$intOk && !$strOk) {
|
||||
return;
|
||||
}
|
||||
|
||||
$where[] = function ($query) use ($s, $e, $i0, $i1, $intOk, $strOk) {
|
||||
$query->where(function ($q) use ($s, $e, $i0, $i1, $intOk, $strOk) {
|
||||
if ($intOk) {
|
||||
$q->where(function ($qi) use ($i0, $i1) {
|
||||
if ($i0 > 0) {
|
||||
$qi->where('create_time', '>=', $i0);
|
||||
}
|
||||
if ($i1 > 0) {
|
||||
$qi->where('create_time', '<=', $i1);
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($strOk) {
|
||||
if ($intOk) {
|
||||
$q->whereOr(function ($qs) use ($s, $e) {
|
||||
if ($s !== '') {
|
||||
$qs->where('create_time', '>=', $s);
|
||||
}
|
||||
if ($e !== '') {
|
||||
$qs->where('create_time', '<=', $e);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$q->where(function ($qs) use ($s, $e) {
|
||||
if ($s !== '') {
|
||||
$qs->where('create_time', '>=', $s);
|
||||
}
|
||||
if ($e !== '') {
|
||||
$qs->where('create_time', '<=', $e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
* @return array
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['order_type', 'status'],
|
||||
// ListsSearchTrait 只识别 %like% / like% 等,「like」不会命中任何 case,订单号因此搜不到
|
||||
'%like%' => ['order_no'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 按医助筛选:与「创建人」一致(指派后创建人为该医助)
|
||||
*/
|
||||
private function appendAssistantFilter(array &$where): void
|
||||
{
|
||||
$assistantId = (int) ($this->params['assistant_id'] ?? 0);
|
||||
if ($assistantId <= 0) {
|
||||
return;
|
||||
}
|
||||
$where[] = ['creator_id', '=', $assistantId];
|
||||
}
|
||||
|
||||
private function filterByPermission(array &$where): void
|
||||
{
|
||||
// 检查是否是超管(root字段为1)
|
||||
if (!empty($this->adminInfo['root']) && $this->adminInfo['root'] == 1) {
|
||||
// 超管不过滤,可以看所有订单
|
||||
return;
|
||||
}
|
||||
|
||||
// 可查看全部订单:project.order_list_view_all_roles;未配置时回退 order_edit_all_roles
|
||||
$supervisorRoles = config('project.order_list_view_all_roles', null);
|
||||
if (!\is_array($supervisorRoles) || $supervisorRoles === []) {
|
||||
$supervisorRoles = config('project.order_edit_all_roles', [0, 3, 4, 9, 6]);
|
||||
}
|
||||
|
||||
// 检查当前用户是否是主管
|
||||
$roleIds = \app\common\model\auth\AdminRole::where('admin_id', $this->adminId)->column('role_id');
|
||||
|
||||
// 如果不是主管,只能看自己创建的订单
|
||||
$isSupervisor = count(array_intersect($roleIds, $supervisorRoles)) > 0;
|
||||
|
||||
if (!$isSupervisor) {
|
||||
$where[] = ['creator_id', '=', $this->adminId];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$where = $this->searchWhere;
|
||||
|
||||
// 权限控制:普通员工只能看自己创建的订单
|
||||
$this->filterByPermission($where);
|
||||
|
||||
// 处理患者关联状态:pending=待关联, associated=已关联
|
||||
$patientAssociation = $this->params['patient_association'] ?? '';
|
||||
if ($patientAssociation === 'pending') {
|
||||
$where[] = ['patient_id', 'null', ''];
|
||||
} elseif ($patientAssociation === 'associated') {
|
||||
$where[] = ['patient_id', 'not null', ''];
|
||||
}
|
||||
|
||||
// 处理患者关键词搜索(从诊单表搜索)
|
||||
$pkw = $this->patientKeywordSubWhere();
|
||||
if ($pkw !== []) {
|
||||
$where[] = ['patient_id', 'in', $pkw];
|
||||
}
|
||||
|
||||
$this->appendAssistantFilter($where);
|
||||
|
||||
$this->appendCreateTimeWhere($where);
|
||||
|
||||
$query = Order::where($where);
|
||||
$this->applyDataScopeByOwner($query, 'creator_id');
|
||||
return $query
|
||||
->with(['patient', 'creator'])
|
||||
->order(['create_time' => 'desc'])
|
||||
->limit($this->limitOffset, $this->pageSize)
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$where = $this->searchWhere;
|
||||
|
||||
// 权限控制:普通员工只能看自己创建的订单
|
||||
$this->filterByPermission($where);
|
||||
|
||||
// 处理患者关联状态
|
||||
$patientAssociation = $this->params['patient_association'] ?? '';
|
||||
if ($patientAssociation === 'pending') {
|
||||
$where[] = ['patient_id', 'null', ''];
|
||||
} elseif ($patientAssociation === 'associated') {
|
||||
$where[] = ['patient_id', 'not null', ''];
|
||||
}
|
||||
|
||||
// 处理患者关键词搜索(从诊单表搜索)
|
||||
$pkw = $this->patientKeywordSubWhere();
|
||||
if ($pkw !== []) {
|
||||
$where[] = ['patient_id', 'in', $pkw];
|
||||
}
|
||||
|
||||
$this->appendAssistantFilter($where);
|
||||
|
||||
$this->appendCreateTimeWhere($where);
|
||||
|
||||
$query = Order::where($where);
|
||||
$this->applyDataScopeByOwner($query, 'creator_id');
|
||||
return $query->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\pharmacy;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use think\facade\Db;
|
||||
|
||||
class MedicineMappingLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$rows = $this->query()
|
||||
->field($this->fields())
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->order('l.id', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($rows as &$row) {
|
||||
foreach ([
|
||||
'local_medicine_id', 'local_status', 'mapping_id', 'mapping_status',
|
||||
'operator_id', 'mapping_update_time', 'catalog_version', 'remote_status', 'remote_deleted',
|
||||
] as $field) {
|
||||
if ($row[$field] !== null) {
|
||||
$row[$field] = (int) $row[$field];
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return (int) $this->query()->count('l.id');
|
||||
}
|
||||
|
||||
private function query()
|
||||
{
|
||||
$query = Db::name('doctor_medicine')->alias('l')
|
||||
->leftJoin(
|
||||
'ej_medicine_mapping m',
|
||||
'm.local_medicine_id = l.id AND m.status = 1 AND m.delete_time IS NULL'
|
||||
)
|
||||
->leftJoin('ej_medicine_catalog c', 'c.medicine_code = m.medicine_code')
|
||||
->whereNull('l.delete_time');
|
||||
|
||||
$localName = trim((string) ($this->params['local_name'] ?? ''));
|
||||
if ($localName !== '') {
|
||||
$query->where('l.name', 'like', '%' . $localName . '%');
|
||||
}
|
||||
$remoteKeyword = trim((string) ($this->params['remote_keyword'] ?? ''));
|
||||
if ($remoteKeyword !== '') {
|
||||
$query->where(function ($nested) use ($remoteKeyword): void {
|
||||
$nested->where('c.name', 'like', '%' . $remoteKeyword . '%')
|
||||
->whereOr('c.medicine_code', 'like', '%' . $remoteKeyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$mappingStatus = trim((string) ($this->params['mapping_status'] ?? ''));
|
||||
if ($mappingStatus === 'mapped') {
|
||||
$query->whereNotNull('m.id')->where('c.status', 1)->where('c.remote_deleted', 0);
|
||||
} elseif ($mappingStatus === 'unmapped') {
|
||||
$query->whereNull('m.id');
|
||||
} elseif ($mappingStatus === 'invalid') {
|
||||
$query->whereNotNull('m.id')
|
||||
->where(function ($nested): void {
|
||||
$nested->whereNull('c.id')
|
||||
->whereOr('c.status', '<>', 1)
|
||||
->whereOr('c.remote_deleted', 1);
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function fields(): string
|
||||
{
|
||||
return implode(',', [
|
||||
'l.id AS local_medicine_id',
|
||||
'l.name AS local_name',
|
||||
'l.unit AS local_unit',
|
||||
'l.status AS local_status',
|
||||
'm.id AS mapping_id',
|
||||
'm.medicine_code',
|
||||
'm.operator_id',
|
||||
'm.operator_name',
|
||||
'm.update_time AS mapping_update_time',
|
||||
'c.name AS remote_name',
|
||||
'c.brand AS remote_brand',
|
||||
'c.unit AS remote_unit',
|
||||
'c.settlement_price',
|
||||
'c.retail_price',
|
||||
'c.catalog_version',
|
||||
'c.status AS remote_status',
|
||||
'c.remote_deleted',
|
||||
"CASE WHEN m.id IS NULL THEN 0 "
|
||||
. "WHEN c.id IS NULL OR c.status <> 1 OR c.remote_deleted = 1 THEN 2 ELSE 1 END AS mapping_status",
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\qywx;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\qywx\CustomerLogic;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\QywxExternalContact;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 企业微信客户列表
|
||||
*/
|
||||
class CustomerLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
/**
|
||||
* @notes 搜索条件
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
// 表无 follow_user 列,跟进人在 follow_users(JSON);跟进人筛选在 baseQuery() 中处理
|
||||
return [
|
||||
'%like%' => ['name'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[] 入参 tag_ids 规范化后的非空字符串数组(实际为 string[])
|
||||
*/
|
||||
private function normalizeTagIds(): array
|
||||
{
|
||||
$raw = $this->params['tag_ids'] ?? null;
|
||||
if ($raw === null || $raw === '') {
|
||||
return [];
|
||||
}
|
||||
if (is_string($raw)) {
|
||||
$raw = explode(',', $raw);
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$ids = [];
|
||||
foreach ($raw as $v) {
|
||||
$s = trim((string) $v);
|
||||
if ($s !== '') {
|
||||
$ids[] = $s;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 去重筛选:
|
||||
* - first(默认):按客户首次添加时间筛选(客户去重,系统原口径)
|
||||
* - any:按添加事件流水筛选(含老客被其他员工重加,对齐企微「当天有添加动作」)
|
||||
*/
|
||||
private function dedupeMode(): string
|
||||
{
|
||||
$mode = strtolower(trim((string) ($this->params['dedupe_mode'] ?? 'first')));
|
||||
|
||||
return $mode === 'any' ? 'any' : 'first';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] 跟进人关键词对应的企微 userid(含已软删 admin)
|
||||
*/
|
||||
private function resolveFollowStaffWxUserids(): array
|
||||
{
|
||||
$kw = trim((string) ($this->params['follow_user'] ?? ''));
|
||||
if ($kw === '') {
|
||||
return [];
|
||||
}
|
||||
$escaped = addcslashes($kw, '%_\\');
|
||||
$rows = Db::name('admin')
|
||||
->whereLike('name', '%' . $escaped . '%')
|
||||
->where('work_wechat_userid', '<>', '')
|
||||
->whereNotNull('work_wechat_userid')
|
||||
->column('work_wechat_userid');
|
||||
|
||||
$wxUserids = [];
|
||||
foreach ($rows as $uid) {
|
||||
$uid = trim((string) $uid);
|
||||
if ($uid !== '') {
|
||||
$wxUserids[$uid] = true;
|
||||
}
|
||||
}
|
||||
// 关键词本身也可能是企微 userid(通常无中文);中文名不能当 userid 去筛事件流水
|
||||
if ($kw !== '' && !preg_match('/\p{Han}/u', $kw)) {
|
||||
$wxUserids[$kw] = true;
|
||||
}
|
||||
|
||||
return array_keys($wxUserids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟进人筛选:列表展示优先用 admin.name(见 lists() 补 admin_name),
|
||||
* 但库内 follow_users JSON 通常只有企微 userid,直接 LIKE 中文名会搜不到。
|
||||
* 策略:admin.name 模糊匹配 → work_wechat_userid / admin.id,再按 userid / follow_admin_ids 过滤;
|
||||
* 同时保留对 follow_users 原文的 LIKE(兼容直接搜 userid、备注等)。
|
||||
*/
|
||||
private function applyFollowUserFilter($query)
|
||||
{
|
||||
$kw = trim((string) ($this->params['follow_user'] ?? ''));
|
||||
if ($kw === '') {
|
||||
return;
|
||||
}
|
||||
$escaped = addcslashes($kw, '%_\\');
|
||||
|
||||
// 含已软删账号,避免离职后跟进人姓名映射丢失导致搜不到历史客户
|
||||
$adminRows = Db::name('admin')
|
||||
->whereLike('name', '%' . $escaped . '%')
|
||||
->where('work_wechat_userid', '<>', '')
|
||||
->whereNotNull('work_wechat_userid')
|
||||
->field(['id', 'work_wechat_userid'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$wxUserids = [];
|
||||
$adminIds = [];
|
||||
foreach ($adminRows as $row) {
|
||||
$uid = trim((string) ($row['work_wechat_userid'] ?? ''));
|
||||
if ($uid !== '') {
|
||||
$wxUserids[$uid] = true;
|
||||
}
|
||||
$aid = (int) ($row['id'] ?? 0);
|
||||
if ($aid > 0) {
|
||||
$adminIds[$aid] = true;
|
||||
}
|
||||
}
|
||||
$wxUserids = array_keys($wxUserids);
|
||||
$adminIds = array_keys($adminIds);
|
||||
|
||||
$query->where(function ($q) use ($escaped, $wxUserids, $adminIds) {
|
||||
$q->whereLike('follow_users', '%' . $escaped . '%');
|
||||
foreach ($wxUserids as $uid) {
|
||||
$uidEsc = addcslashes($uid, '%_\\');
|
||||
$q->whereOr('follow_users', 'like', '%"userid":"' . $uidEsc . '"%');
|
||||
}
|
||||
foreach ($adminIds as $aid) {
|
||||
// JSON 数组精确包含,避免 id=12 误命中 123
|
||||
$q->whereOrRaw(
|
||||
'JSON_CONTAINS(IFNULL(follow_admin_ids, \'[]\'), ?)',
|
||||
[json_encode($aid)]
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0:int,1:int} [startTs, endTs],未设置则为 0
|
||||
*/
|
||||
private function parseAddTimeBounds(): array
|
||||
{
|
||||
$addStart = trim((string) ($this->params['add_time_start'] ?? ''));
|
||||
$addEnd = trim((string) ($this->params['add_time_end'] ?? ''));
|
||||
$startTs = 0;
|
||||
$endTs = 0;
|
||||
if ($addStart !== '') {
|
||||
$t = strtotime($addStart . ' 00:00:00');
|
||||
if ($t !== false) {
|
||||
$startTs = (int) $t;
|
||||
}
|
||||
}
|
||||
if ($addEnd !== '') {
|
||||
$t = strtotime($addEnd . ' 23:59:59');
|
||||
if ($t !== false) {
|
||||
$endTs = (int) $t;
|
||||
}
|
||||
}
|
||||
|
||||
return [$startTs, $endTs];
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加时间筛选。
|
||||
* first:external_first_add_time(客户去重)
|
||||
* any:事件流水 add_external_contact(含重加;可叠加跟进人 userid)
|
||||
*
|
||||
* @return bool true=已按事件+跟进人收窄,调用方勿再 applyFollowUserFilter
|
||||
*/
|
||||
private function applyAddTimeFilter($query): bool
|
||||
{
|
||||
[$startTs, $endTs] = $this->parseAddTimeBounds();
|
||||
if ($startTs <= 0 && $endTs <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->dedupeMode() === 'any') {
|
||||
$eventQuery = Db::name('qywx_external_contact_event')
|
||||
->where('change_type', 'add_external_contact')
|
||||
->where('external_userid', '<>', '');
|
||||
if ($startTs > 0) {
|
||||
$eventQuery->where('event_time', '>=', $startTs);
|
||||
}
|
||||
if ($endTs > 0) {
|
||||
$eventQuery->where('event_time', '<=', $endTs);
|
||||
}
|
||||
|
||||
$followKw = trim((string) ($this->params['follow_user'] ?? ''));
|
||||
$staffScoped = false;
|
||||
if ($followKw !== '') {
|
||||
$staffIds = $this->resolveFollowStaffWxUserids();
|
||||
// 去掉「关键词本身」这一项后若仍有 admin 映射,或关键词像 userid,则按事件员工收窄
|
||||
$staffIds = array_values(array_filter($staffIds, static fn (string $id): bool => $id !== ''));
|
||||
if ($staffIds !== []) {
|
||||
$eventQuery->whereIn('user_id', $staffIds);
|
||||
$staffScoped = true;
|
||||
}
|
||||
}
|
||||
|
||||
$extIds = $eventQuery->group('external_userid')->column('external_userid');
|
||||
if ($extIds === []) {
|
||||
$query->whereRaw('1=0');
|
||||
} else {
|
||||
$query->whereIn('external_userid', $extIds);
|
||||
}
|
||||
|
||||
return $staffScoped;
|
||||
}
|
||||
|
||||
$effExpr = 'COALESCE(NULLIF(external_first_add_time, 0), create_time)';
|
||||
if ($startTs > 0) {
|
||||
$query->whereRaw($effExpr . ' > 0 AND ' . $effExpr . ' >= ?', [$startTs]);
|
||||
}
|
||||
if ($endTs > 0) {
|
||||
$query->whereRaw($effExpr . ' > 0 AND ' . $effExpr . ' <= ?', [$endTs]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function baseQuery()
|
||||
{
|
||||
$query = QywxExternalContact::where($this->searchWhere);
|
||||
|
||||
// 添加时间可能已按「跟进人+事件流水」收窄;此时不必再 LIKE follow_users
|
||||
$followAlreadyScoped = $this->applyAddTimeFilter($query);
|
||||
if (!$followAlreadyScoped) {
|
||||
$this->applyFollowUserFilter($query);
|
||||
}
|
||||
|
||||
// 标签筛选:JOIN 关系表按 tag_id 过滤;多个标签为 OR(命中任一即返回)。
|
||||
// 走 zyt_qywx_external_contact_tag.idx_tag 索引,比 LIKE follow_users 快得多
|
||||
$tagIds = $this->normalizeTagIds();
|
||||
if ($tagIds !== []) {
|
||||
$matchedExtIds = Db::name('qywx_external_contact_tag')
|
||||
->whereIn('tag_id', $tagIds)
|
||||
->group('external_userid')
|
||||
->column('external_userid');
|
||||
if ($matchedExtIds === []) {
|
||||
// 没人命中:直接给一个不可能成立的条件,避免下面命中所有客户
|
||||
$query->whereRaw('1=0');
|
||||
} else {
|
||||
$query->whereIn('external_userid', $matchedExtIds);
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
// 按首次添加时间倒序:库字段 external_first_add_time;未回填(0)时回退 create_time(与列表「添加时间」展示一致)
|
||||
$lists = $this->baseQuery()
|
||||
->orderRaw(
|
||||
'(COALESCE(NULLIF(external_first_add_time, 0), create_time) = 0) ASC, '
|
||||
. 'COALESCE(NULLIF(external_first_add_time, 0), create_time) DESC, id DESC'
|
||||
)
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$wxUserids = [];
|
||||
foreach ($lists as $item) {
|
||||
$raw = json_decode($item['follow_users'] ?? '[]', true);
|
||||
if (!is_array($raw)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($raw as $fu) {
|
||||
if (!is_array($fu)) {
|
||||
continue;
|
||||
}
|
||||
$wx = trim((string) ($fu['userid'] ?? ''));
|
||||
if ($wx !== '') {
|
||||
$wxUserids[$wx] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$wxUserids = array_keys($wxUserids);
|
||||
$adminNameByWx = [];
|
||||
if ($wxUserids !== []) {
|
||||
$adminNameByWx = Admin::whereIn('work_wechat_userid', $wxUserids)->column('name', 'work_wechat_userid');
|
||||
}
|
||||
|
||||
foreach ($lists as &$item) {
|
||||
$followUsers = json_decode($item['follow_users'] ?? '[]', true);
|
||||
$followUsers = is_array($followUsers) ? $followUsers : [];
|
||||
foreach ($followUsers as &$fu) {
|
||||
if (!is_array($fu)) {
|
||||
continue;
|
||||
}
|
||||
$wx = trim((string) ($fu['userid'] ?? ''));
|
||||
if ($wx !== '' && isset($adminNameByWx[$wx]) && $adminNameByWx[$wx] !== '') {
|
||||
$fu['admin_name'] = $adminNameByWx[$wx];
|
||||
}
|
||||
}
|
||||
unset($fu);
|
||||
$item['follow_users'] = $followUsers;
|
||||
$followAdminIds = json_decode($item['follow_admin_ids'] ?? '[]', true);
|
||||
$item['follow_admin_ids'] = is_array($followAdminIds) ? $followAdminIds : [];
|
||||
|
||||
// 解析标签 JSON 数组(值由 CustomerLogic::extractFollowUserTags 写入;按 tag_id 去重)
|
||||
$tags = json_decode((string) ($item['tags'] ?? '[]'), true);
|
||||
$item['tags'] = is_array($tags) ? $tags : [];
|
||||
|
||||
$fromDb = (int) ($item['external_first_add_time'] ?? 0);
|
||||
$fromJson = CustomerLogic::minFollowCreatetime($followUsers);
|
||||
$item['external_first_add_time'] = $fromDb > 0 ? $fromDb : $fromJson;
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return $this->baseQuery()->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\qywx;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\QywxExternalContact;
|
||||
use app\common\model\QywxMsgArchive;
|
||||
use app\common\model\QywxMsgArchiveMedia;
|
||||
|
||||
/**
|
||||
* 单会话消息历史
|
||||
*
|
||||
* 两种查询模式,二选一:
|
||||
* 1) session_id 传 QywxMsgSession.id(推荐)
|
||||
* 2) staff_userid + external_userid(单聊)/ roomid(群聊)
|
||||
*
|
||||
* 消息默认按 send_time 升序,便于前端从下往上追加。
|
||||
* 支持 before_time / after_time 游标翻页。
|
||||
*/
|
||||
class MsgArchiveLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['roomid', 'msgtype'],
|
||||
];
|
||||
}
|
||||
|
||||
private function resolveScope(): array
|
||||
{
|
||||
$sessionId = (int) ($this->params['session_id'] ?? 0);
|
||||
if ($sessionId > 0) {
|
||||
$session = \app\common\model\QywxMsgSession::find($sessionId);
|
||||
if ($session) {
|
||||
return [
|
||||
'staff_userid' => (string) $session['staff_userid'],
|
||||
'external_userid' => (string) $session['external_userid'],
|
||||
'roomid' => (string) $session['roomid'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'staff_userid' => trim((string) ($this->params['staff_userid'] ?? '')),
|
||||
'external_userid' => trim((string) ($this->params['external_userid'] ?? '')),
|
||||
'roomid' => trim((string) ($this->params['roomid'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
private function baseQuery()
|
||||
{
|
||||
$scope = $this->resolveScope();
|
||||
$query = QywxMsgArchive::where($this->searchWhere);
|
||||
|
||||
if ($scope['roomid'] !== '') {
|
||||
$query->where('roomid', $scope['roomid']);
|
||||
} else {
|
||||
// 单聊:两端顺序不定,from_user/tolist 都可能为员工或客户
|
||||
$staff = $scope['staff_userid'];
|
||||
$external = $scope['external_userid'];
|
||||
if ($staff === '' && $external === '') {
|
||||
$query->where('id', 0);
|
||||
} else {
|
||||
$query->where('roomid', '');
|
||||
if ($staff !== '' && $external !== '') {
|
||||
$query->where(function ($q) use ($staff, $external) {
|
||||
$q->where(function ($sub) use ($staff, $external) {
|
||||
$sub->where('from_user', $staff)->whereLike('to_list', '%"' . $external . '"%');
|
||||
})->whereOr(function ($sub) use ($staff, $external) {
|
||||
$sub->where('from_user', $external)->whereLike('to_list', '%"' . $staff . '"%');
|
||||
});
|
||||
});
|
||||
} elseif ($staff !== '') {
|
||||
$query->where(function ($q) use ($staff) {
|
||||
$q->where('from_user', $staff)->whereOr('to_list', 'like', '%"' . $staff . '"%');
|
||||
});
|
||||
} else {
|
||||
$query->where(function ($q) use ($external) {
|
||||
$q->where('from_user', $external)->whereOr('to_list', 'like', '%"' . $external . '"%');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->params['before_time'])) {
|
||||
$query->where('send_time', '<', (int) $this->params['before_time']);
|
||||
}
|
||||
if (!empty($this->params['after_time'])) {
|
||||
$query->where('send_time', '>', (int) $this->params['after_time']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$rows = $this->baseQuery()
|
||||
->order('send_time', 'desc')
|
||||
->order('id', 'desc')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = array_reverse($rows); // 前端按时间升序渲染
|
||||
|
||||
$users = [];
|
||||
foreach ($rows as $r) {
|
||||
$users[$r['from_user']] = true;
|
||||
$toList = $r['to_list'];
|
||||
if (is_array($toList)) {
|
||||
foreach ($toList as $to) {
|
||||
$users[$to] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$users = array_keys(array_filter($users, fn ($_, $k) => $k !== '', ARRAY_FILTER_USE_BOTH));
|
||||
|
||||
$adminMap = [];
|
||||
$extMap = [];
|
||||
if ($users) {
|
||||
$adminMap = Admin::whereIn('work_wechat_userid', $users)
|
||||
->column('id,name,avatar,work_wechat_userid', 'work_wechat_userid');
|
||||
$extMap = QywxExternalContact::whereIn('external_userid', $users)
|
||||
->column('external_userid,name,avatar,type,unionid', 'external_userid');
|
||||
}
|
||||
|
||||
$msgIds = array_column($rows, 'msgid');
|
||||
$mediaMap = [];
|
||||
if ($msgIds) {
|
||||
$mediaList = QywxMsgArchiveMedia::whereIn('msgid', $msgIds)->select()->toArray();
|
||||
foreach ($mediaList as $m) {
|
||||
$mediaMap[$m['msgid']][] = $m;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($rows as &$r) {
|
||||
$fromId = (string) $r['from_user'];
|
||||
$r['from_is_staff'] = isset($adminMap[$fromId]);
|
||||
$r['from_profile'] = $adminMap[$fromId] ?? ($extMap[$fromId] ?? null);
|
||||
$r['media'] = $mediaMap[$r['msgid']] ?? [];
|
||||
}
|
||||
unset($r);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return $this->baseQuery()->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\qywx;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\QywxMsgSendTask;
|
||||
|
||||
class MsgSendTaskLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['sender_userid', 'status', 'admin_id'],
|
||||
];
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
return QywxMsgSendTask::where($this->searchWhere)
|
||||
->order('id', 'desc')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return QywxMsgSendTask::where($this->searchWhere)->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\qywx;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\QywxExternalContact;
|
||||
use app\common\model\QywxMsgSession;
|
||||
|
||||
/**
|
||||
* 企微消息会话列表
|
||||
*
|
||||
* 查询参数:
|
||||
* staff_userid - 过滤某员工的会话(支持传 admin_id 反查)
|
||||
* external_userid- 过滤某客户
|
||||
* keyword - 按客户名 / 摘要 模糊搜索
|
||||
* only_unread - 仅未读
|
||||
*/
|
||||
class MsgSessionLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['staff_userid', 'external_userid', 'roomid', 'session_type'],
|
||||
];
|
||||
}
|
||||
|
||||
private function baseQuery()
|
||||
{
|
||||
$query = QywxMsgSession::where($this->searchWhere);
|
||||
|
||||
$adminId = (int) ($this->params['admin_id'] ?? 0);
|
||||
if ($adminId > 0) {
|
||||
$wxId = Admin::where('id', $adminId)->value('work_wechat_userid');
|
||||
if ($wxId) {
|
||||
$query->where('staff_userid', $wxId);
|
||||
} else {
|
||||
// 找不到对应企微 userid 时不返回任何会话
|
||||
$query->where('id', 0);
|
||||
}
|
||||
}
|
||||
|
||||
$keyword = trim((string) ($this->params['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$kw = addcslashes($keyword, '%_\\');
|
||||
$extIds = QywxExternalContact::whereLike('name', '%' . $kw . '%')->column('external_userid');
|
||||
$query->where(function ($q) use ($kw, $extIds) {
|
||||
$q->whereLike('last_msg_summary', '%' . $kw . '%');
|
||||
if ($extIds) {
|
||||
$q->whereOr('external_userid', 'in', $extIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!empty($this->params['only_unread'])) {
|
||||
$query->where('unread_staff', '>', 0);
|
||||
}
|
||||
|
||||
return $query->order('last_msg_time', 'desc');
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$rows = $this->baseQuery()
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$extIds = array_values(array_unique(array_filter(array_map(
|
||||
fn ($r) => (string) ($r['external_userid'] ?? ''),
|
||||
$rows
|
||||
))));
|
||||
$extMap = [];
|
||||
if ($extIds) {
|
||||
$extMap = QywxExternalContact::whereIn('external_userid', $extIds)
|
||||
->column('name,avatar,type,gender,corp_name,unionid', 'external_userid');
|
||||
}
|
||||
|
||||
$staffIds = array_values(array_unique(array_filter(array_map(
|
||||
fn ($r) => (string) ($r['staff_userid'] ?? ''),
|
||||
$rows
|
||||
))));
|
||||
$staffMap = [];
|
||||
if ($staffIds) {
|
||||
$staffMap = Admin::whereIn('work_wechat_userid', $staffIds)
|
||||
->column('id,name,avatar', 'work_wechat_userid');
|
||||
}
|
||||
|
||||
foreach ($rows as &$r) {
|
||||
$r['customer'] = $extMap[$r['external_userid']] ?? null;
|
||||
$r['staff'] = $staffMap[$r['staff_userid']] ?? null;
|
||||
}
|
||||
unset($r);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return $this->baseQuery()->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\recharge;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\enum\PayEnum;
|
||||
use app\common\lists\ListsExcelInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\recharge\RechargeOrder;
|
||||
use app\common\service\FileService;
|
||||
|
||||
/**
|
||||
* 充值记录列表
|
||||
* Class RecharLists
|
||||
* @package app\adminapi\lists
|
||||
*/
|
||||
class RechargeLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExcelInterface
|
||||
{
|
||||
/**
|
||||
* @notes 导出字段
|
||||
* @return string[]
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 16:07
|
||||
*/
|
||||
public function setExcelFields(): array
|
||||
{
|
||||
return [
|
||||
'sn' => '充值单号',
|
||||
'nickname' => '用户昵称',
|
||||
'order_amount' => '充值金额',
|
||||
'pay_way_text' => '支付方式',
|
||||
'pay_status_text' => '支付状态',
|
||||
'pay_time' => '支付时间',
|
||||
'create_time' => '下单时间',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 导出表名
|
||||
* @return string
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 16:07
|
||||
*/
|
||||
public function setFileName(): string
|
||||
{
|
||||
return '充值记录';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 搜索条件
|
||||
* @return \string[][]
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 16:08
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['ro.sn', 'ro.pay_way', 'ro.pay_status'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 搜索条件
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 16:08
|
||||
*/
|
||||
public function queryWhere()
|
||||
{
|
||||
$where = [];
|
||||
// 用户编号
|
||||
if (!empty($this->params['user_info'])) {
|
||||
$where[] = ['u.sn|u.nickname|u.mobile|u.account', 'like', '%' . $this->params['user_info'] . '%'];
|
||||
}
|
||||
|
||||
// 下单时间
|
||||
if (!empty($this->params['start_time']) && !empty($this->params['end_time'])) {
|
||||
$time = [strtotime($this->params['start_time']), strtotime($this->params['end_time'])];
|
||||
$where[] = ['ro.create_time', 'between', $time];
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 16:13
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$field = 'ro.id,ro.sn,ro.order_amount,ro.pay_way,ro.pay_time,ro.pay_status,ro.create_time,ro.refund_status';
|
||||
$field .= ',u.avatar,u.nickname,u.account';
|
||||
$lists = RechargeOrder::alias('ro')
|
||||
->join('user u', 'u.id = ro.user_id')
|
||||
->field($field)
|
||||
->where($this->queryWhere())
|
||||
->where($this->searchWhere)
|
||||
->order('ro.id', 'desc')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->append(['pay_status_text', 'pay_way_text'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($lists as &$item) {
|
||||
$item['avatar'] = FileService::getFileUrl($item['avatar']);
|
||||
$item['pay_time'] = empty($item['pay_time']) ? '' : date('Y-m-d H:i:s', $item['pay_time']);
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 16:13
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return RechargeOrder::alias('ro')
|
||||
->join('user u', 'u.id = ro.user_id')
|
||||
->where($this->queryWhere())
|
||||
->where($this->searchWhere)
|
||||
->count();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\setting\dict;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\dict\DictData;
|
||||
|
||||
|
||||
/**
|
||||
* 字典数据列表
|
||||
* Class DictDataLists
|
||||
* @package app\adminapi\lists\dict
|
||||
*/
|
||||
class DictDataLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
* @return \string[][]
|
||||
* @author 段誉
|
||||
* @date 2022/6/20 16:29
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['name', 'type_value'],
|
||||
'=' => ['status', 'type_id']
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/6/20 16:35
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
return DictData::where($this->searchWhere)
|
||||
->append(['status_desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->order(['sort' => 'desc', 'id' => 'desc'])
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2022/6/20 16:35
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return DictData::where($this->searchWhere)->count();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\setting\dict;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\dict\DictType;
|
||||
|
||||
|
||||
/**
|
||||
* 字典类型列表
|
||||
* Class DictTypeLists
|
||||
* @package app\adminapi\lists\dictionary
|
||||
*/
|
||||
class DictTypeLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
* @return \string[][]
|
||||
* @author 段誉
|
||||
* @date 2022/6/20 15:53
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['name', 'type'],
|
||||
'=' => ['status']
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/6/20 15:54
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
return DictType::where($this->searchWhere)
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->append(['status_desc'])
|
||||
->order(['id' => 'desc'])
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2022/6/20 15:54
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return DictType::where($this->searchWhere)->count();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\setting\pay;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\model\pay\PayConfig;
|
||||
|
||||
/**
|
||||
* 支付配置列表
|
||||
* Class PayConfigLists
|
||||
* @package app\adminapi\lists\setting\pay
|
||||
*/
|
||||
class PayConfigLists extends BaseAdminDataLists
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2023/2/23 16:15
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = PayConfig::field('id,name,pay_way,icon,sort')
|
||||
->append(['pay_way_name'])
|
||||
->order('sort','desc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2023/2/23 16:15
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return PayConfig::count();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\setting\system;
|
||||
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsExcelInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\OperationLog;
|
||||
|
||||
/**
|
||||
* 日志列表
|
||||
* Class LogLists
|
||||
* @package app\adminapi\lists\setting\system
|
||||
*/
|
||||
class LogLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExcelInterface
|
||||
{
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
* @return \string[][]
|
||||
* @author ljj
|
||||
* @date 2021/8/3 4:21 下午
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['admin_name','url','ip','type'],
|
||||
'between_time' => 'create_time',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 查看系统日志列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author ljj
|
||||
* @date 2021/8/3 4:21 下午
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = OperationLog::field('id,action,admin_name,admin_id,url,type,params,ip,create_time')
|
||||
->where($this->searchWhere)
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->order('id','desc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 查看系统日志总数
|
||||
* @return int
|
||||
* @author ljj
|
||||
* @date 2021/8/3 4:23 下午
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return OperationLog::where($this->searchWhere)->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 设置导出字段
|
||||
* @return string[]
|
||||
* @author ljj
|
||||
* @date 2021/8/3 4:48 下午
|
||||
*/
|
||||
public function setExcelFields(): array
|
||||
{
|
||||
return [
|
||||
// '数据库字段名(支持别名) => 'Excel表字段名'
|
||||
'id' => '记录ID',
|
||||
'action' => '操作',
|
||||
'admin_name' => '管理员',
|
||||
'admin_id' => '管理员ID',
|
||||
'url' => '访问链接',
|
||||
'type' => '访问方式',
|
||||
'params' => '访问参数',
|
||||
'ip' => '来源IP',
|
||||
'create_time' => '日志时间',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 设置默认表名
|
||||
* @return string
|
||||
* @author ljj
|
||||
* @date 2021/8/3 4:48 下午
|
||||
*/
|
||||
public function setFileName(): string
|
||||
{
|
||||
return '系统日志';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\stats;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 待分配诊单自动指派日志列表
|
||||
*
|
||||
* 数据来源:tcm_diagnosis_auto_assign_log(定时命令 tcm:auto-assign-pending 写入)
|
||||
* 筛选:run_date / create_time 区间 / action / keyword / rollback 状态
|
||||
*/
|
||||
class AutoAssignLogLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
private const TIER_LABELS = [
|
||||
'gt70' => '>70%',
|
||||
'60_70' => '60%~70%',
|
||||
'50_60' => '50%~60%',
|
||||
];
|
||||
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['run_date', 'action', 'assistant_id', 'batch_no', 'stat_month'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$rows = $this->buildQuery()
|
||||
->order(['id' => 'desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
$ct = (int) ($row['create_time'] ?? 0);
|
||||
$row['create_time_text'] = $ct > 0 ? date('Y-m-d H:i:s', $ct) : '';
|
||||
$row['action_text'] = (int) ($row['action'] ?? 0) === 1 ? '已分配' : '未分配';
|
||||
$row['tier_text'] = self::TIER_LABELS[(string) ($row['tier'] ?? '')] ?? '';
|
||||
$row['visit2_rate'] = $row['visit2_rate'] !== null ? (float) $row['visit2_rate'] : null;
|
||||
|
||||
$rbTs = (int) ($row['rollback_time'] ?? 0);
|
||||
$row['rollback_time_text'] = $rbTs > 0 ? date('Y-m-d H:i:s', $rbTs) : '';
|
||||
$row['is_rollback'] = $rbTs > 0 ? 1 : 0;
|
||||
$row['can_rollback'] = ((int) ($row['action'] ?? 0) === 1 && $rbTs === 0) ? 1 : 0;
|
||||
if ($rbTs > 0) {
|
||||
$row['rollback_status_text'] = '已回退'
|
||||
. (($row['rollback_admin_name'] ?? '') !== '' ? (' · ' . $row['rollback_admin_name']) : '');
|
||||
} else {
|
||||
$row['rollback_status_text'] = ((int) ($row['action'] ?? 0) === 1) ? '可回退' : '—';
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return $this->buildQuery()->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \think\db\Query
|
||||
*/
|
||||
private function buildQuery()
|
||||
{
|
||||
$query = Db::name('tcm_diagnosis_auto_assign_log')->where($this->searchWhere);
|
||||
|
||||
$keyword = trim((string) ($this->params['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$q->whereLike('patient_name', '%' . $keyword . '%')
|
||||
->whereOr('patient_phone', 'like', '%' . $keyword . '%')
|
||||
->whereOr('assistant_name', 'like', '%' . $keyword . '%');
|
||||
if (preg_match('/^\d+$/', $keyword) === 1 && (int) $keyword > 0) {
|
||||
$q->whereOr('diagnosis_id', '=', (int) $keyword);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$startDate = trim((string) ($this->params['start_date'] ?? ''));
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $startDate) === 1) {
|
||||
$query->where('run_date', '>=', $startDate);
|
||||
}
|
||||
$endDate = trim((string) ($this->params['end_date'] ?? ''));
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $endDate) === 1) {
|
||||
$query->where('run_date', '<=', $endDate);
|
||||
}
|
||||
|
||||
// 记录时间(create_time)区间:支持 Y-m-d 或 Y-m-d H:i:s
|
||||
$startTs = $this->parseTimeParam((string) ($this->params['start_time'] ?? ''), false);
|
||||
if ($startTs > 0) {
|
||||
$query->where('create_time', '>=', $startTs);
|
||||
}
|
||||
$endTs = $this->parseTimeParam((string) ($this->params['end_time'] ?? ''), true);
|
||||
if ($endTs > 0) {
|
||||
$query->where('create_time', '<=', $endTs);
|
||||
}
|
||||
|
||||
$rollback = trim((string) ($this->params['is_rollback'] ?? ''));
|
||||
if ($rollback === '1') {
|
||||
$query->where('rollback_time', '>', 0);
|
||||
} elseif ($rollback === '0') {
|
||||
$query->where('rollback_time', '=', 0);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $endOfDay 仅日期时是否取当天结束
|
||||
*/
|
||||
private function parseTimeParam(string $raw, bool $endOfDay): int
|
||||
{
|
||||
$raw = trim($raw);
|
||||
if ($raw === '') {
|
||||
return 0;
|
||||
}
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $raw) === 1) {
|
||||
return (int) strtotime($raw . ($endOfDay ? ' 23:59:59' : ' 00:00:00'));
|
||||
}
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2})?$/', $raw) === 1) {
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', $raw) === 1) {
|
||||
$raw .= $endOfDay ? ':59' : ':00';
|
||||
}
|
||||
|
||||
return (int) strtotime($raw);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\stats;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\stats\PersonalStatsScopeTrait;
|
||||
use app\common\lists\ListsExtendInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
use app\common\model\stats\PersonalAccountCost;
|
||||
|
||||
class PersonalAccountCostLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
use PersonalStatsScopeTrait;
|
||||
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['media_source', 'creator_name', 'remark'],
|
||||
];
|
||||
}
|
||||
|
||||
private function baseQuery()
|
||||
{
|
||||
$query = PersonalAccountCost::where($this->searchWhere);
|
||||
$visibleIds = $this->getDataScopeVisibleAdminIds();
|
||||
$deptId = (int) ($this->params['dept_id'] ?? 0);
|
||||
|
||||
$finalIds = self::intersectVisibleByDept($visibleIds, $deptId);
|
||||
if ($finalIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
} elseif ($finalIds !== null) {
|
||||
$query->whereIn('creator_id', $finalIds);
|
||||
}
|
||||
|
||||
if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
|
||||
$query->whereBetween('cost_date', [$this->params['start_date'], $this->params['end_date']]);
|
||||
} elseif (!empty($this->params['start_date'])) {
|
||||
$query->where('cost_date', '>=', $this->params['start_date']);
|
||||
} elseif (!empty($this->params['end_date'])) {
|
||||
$query->where('cost_date', '<=', $this->params['end_date']);
|
||||
}
|
||||
|
||||
if (!empty($this->params['media_source'])) {
|
||||
$query->where('media_source', trim((string) $this->params['media_source']));
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$rows = $this->baseQuery()
|
||||
->order(['cost_date' => 'desc', 'id' => 'desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return self::attachDeptInfoToRows($rows);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return (int) $this->baseQuery()->count();
|
||||
}
|
||||
|
||||
public function extend(): array
|
||||
{
|
||||
return [
|
||||
'total_amount' => round((float) $this->baseQuery()->sum('amount'), 2),
|
||||
'days_count' => (int) $this->baseQuery()->distinct(true)->count('cost_date'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\stats;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\stats\PersonalStatsScopeTrait;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
use app\common\model\stats\PersonalYeji;
|
||||
|
||||
class PersonalYejiLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
use PersonalStatsScopeTrait;
|
||||
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['media_source', 'creator_name', 'remark'],
|
||||
];
|
||||
}
|
||||
|
||||
private function baseQuery()
|
||||
{
|
||||
$query = PersonalYeji::where($this->searchWhere);
|
||||
$visibleIds = $this->getDataScopeVisibleAdminIds();
|
||||
$deptId = (int) ($this->params['dept_id'] ?? 0);
|
||||
|
||||
$finalIds = self::intersectVisibleByDept($visibleIds, $deptId);
|
||||
if ($finalIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
} elseif ($finalIds !== null) {
|
||||
$query->whereIn('creator_id', $finalIds);
|
||||
}
|
||||
|
||||
if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
|
||||
$query->whereBetween('yeji_date', [$this->params['start_date'], $this->params['end_date']]);
|
||||
} elseif (!empty($this->params['start_date'])) {
|
||||
$query->where('yeji_date', '>=', $this->params['start_date']);
|
||||
} elseif (!empty($this->params['end_date'])) {
|
||||
$query->where('yeji_date', '<=', $this->params['end_date']);
|
||||
}
|
||||
|
||||
if (!empty($this->params['media_source'])) {
|
||||
$query->where('media_source', trim((string) $this->params['media_source']));
|
||||
}
|
||||
|
||||
if (!empty($this->params['creator_id'])) {
|
||||
$query->where('creator_id', (int) $this->params['creator_id']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$rows = $this->baseQuery()
|
||||
->order(['yeji_date' => 'desc', 'id' => 'desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return self::attachDeptInfoToRows($rows);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return (int) $this->baseQuery()->count();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\tcm;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\tcm\DiagnosisTodoLogic;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\tcm\DiagnosisTodo;
|
||||
|
||||
/**
|
||||
* 诊单待办事项列表
|
||||
*
|
||||
* 默认按 remind_time desc 排序;按 diagnosis_id 过滤。
|
||||
*
|
||||
* @package app\adminapi\lists\tcm
|
||||
*/
|
||||
class DiagnosisTodoLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['diagnosis_id', 'status', 'creator_id'],
|
||||
];
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = DiagnosisTodo::where($this->searchWhere)
|
||||
->field([
|
||||
'id', 'diagnosis_id', 'patient_id', 'content', 'remind_time',
|
||||
'status', 'creator_id', 'creator_name', 'notified_at', 'error',
|
||||
'cancelled_at', 'cancelled_by', 'create_time', 'update_time',
|
||||
])
|
||||
->append([
|
||||
'status_text',
|
||||
'remind_time_text',
|
||||
'notified_at_text',
|
||||
'cancelled_at_text',
|
||||
])
|
||||
->order('remind_time', 'desc')
|
||||
->order('id', 'desc')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 业务态:是否能被「我」取消(前端按钮显隐用)
|
||||
$adminId = (int) $this->adminId;
|
||||
foreach ($lists as &$item) {
|
||||
$item['can_cancel'] = DiagnosisTodoLogic::canCancel($item, $adminId);
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return DiagnosisTodo::where($this->searchWhere)->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\tcm;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\tcm\PrescriptionLibraryLogic;
|
||||
use app\common\model\tcm\PrescriptionLibrary;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
|
||||
/**
|
||||
* 处方库列表
|
||||
*/
|
||||
class PrescriptionLibraryLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['prescription_name'],
|
||||
'=' => ['is_public', 'creator_id', 'formula_type']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 列表数据范围:超管/管理员角色看全部;普通账号仅看自己创建 + 公开处方
|
||||
*/
|
||||
private function applyDataScope($query)
|
||||
{
|
||||
if (PrescriptionLibraryLogic::canManageAllPrescriptions($this->adminId, $this->adminInfo)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
// 开方页导入专用参数(勿与搜索项 creator_id 混用,否则无法 OR 出他人公开模板)
|
||||
$prescribingCreatorId = (int) ($this->params['prescribing_creator_id'] ?? 0);
|
||||
if ($prescribingCreatorId > 0
|
||||
&& PrescriptionLibraryLogic::canListLibraryForCreator($this->adminId, $this->adminInfo, $prescribingCreatorId)) {
|
||||
return $query->where(function ($q) use ($prescribingCreatorId) {
|
||||
$q->where('creator_id', $prescribingCreatorId)
|
||||
->whereOr('is_public', 1);
|
||||
});
|
||||
}
|
||||
|
||||
return $query->where(function ($q) {
|
||||
$q->where('creator_id', $this->adminId)
|
||||
->whereOr('is_public', 1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$field = [
|
||||
'id', 'prescription_name', 'formula_type', 'herbs', 'is_public', 'disable_edit',
|
||||
'creator_id', 'creator_name', 'create_time', 'update_time'
|
||||
];
|
||||
|
||||
$query = PrescriptionLibrary::where($this->searchWhere);
|
||||
$this->applyDataScope($query);
|
||||
|
||||
$lists = $query
|
||||
->field($field)
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->order('id', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 解析药材JSON
|
||||
foreach ($lists as &$item) {
|
||||
if (!empty($item['herbs'])) {
|
||||
$item['herbs'] = json_decode($item['herbs'], true);
|
||||
} else {
|
||||
$item['herbs'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$query = PrescriptionLibrary::where($this->searchWhere);
|
||||
$this->applyDataScope($query);
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\tcm;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
use app\adminapi\logic\tcm\PrescriptionLogic;
|
||||
use app\common\model\tcm\Prescription;
|
||||
use app\common\model\tcm\PrescriptionOrder;
|
||||
|
||||
/**
|
||||
* 处方列表
|
||||
*/
|
||||
class PrescriptionLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
|
||||
/**
|
||||
* 识别「有业务订单且处方药材为空白/重复」的处方 ID(用于全局置顶排序)
|
||||
*
|
||||
* @param array<int,int|string> $candidateIds
|
||||
* @return array<int,int>
|
||||
*/
|
||||
private function collectRiskPrescriptionIds(array $candidateIds): array
|
||||
{
|
||||
$candidateIds = array_values(array_unique(array_filter(array_map('intval', $candidateIds), static function (int $id): bool {
|
||||
return $id > 0;
|
||||
})));
|
||||
if ($candidateIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 仅在「存在有效业务订单」的处方里做风险判定
|
||||
$orderRxIds = PrescriptionOrder::whereIn('prescription_id', $candidateIds)
|
||||
->whereNull('delete_time')
|
||||
->where('fulfillment_status', '<>', 4)
|
||||
->column('prescription_id');
|
||||
$orderRxIds = array_values(array_unique(array_filter(array_map('intval', $orderRxIds), static function (int $id): bool {
|
||||
return $id > 0;
|
||||
})));
|
||||
if ($orderRxIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = Prescription::whereIn('id', $orderRxIds)
|
||||
->whereNull('delete_time')
|
||||
->field(['id', 'herbs'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$riskIds = [];
|
||||
foreach ($rows as $row) {
|
||||
$rid = (int) ($row['id'] ?? 0);
|
||||
if ($rid <= 0) {
|
||||
continue;
|
||||
}
|
||||
if ($this->isRiskHerbs($row['herbs'] ?? null)) {
|
||||
$riskIds[] = $rid;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($riskIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 药材风险判定:空白 or 重复(按名称,忽略空格与大小写)
|
||||
*/
|
||||
private function isRiskHerbs($herbsRaw): bool
|
||||
{
|
||||
$herbs = [];
|
||||
if (is_array($herbsRaw)) {
|
||||
$herbs = $herbsRaw;
|
||||
} elseif (is_string($herbsRaw) && $herbsRaw !== '') {
|
||||
$decoded = json_decode($herbsRaw, true);
|
||||
if (is_array($decoded)) {
|
||||
$herbs = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
$names = [];
|
||||
foreach ($herbs as $h) {
|
||||
if (!is_array($h)) {
|
||||
continue;
|
||||
}
|
||||
$name = trim((string) ($h['name'] ?? ''));
|
||||
if ($name !== '') {
|
||||
$names[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
// 空白药材
|
||||
if ($names === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 重复药材
|
||||
$seen = [];
|
||||
foreach ($names as $name) {
|
||||
$key = strtolower(preg_replace('/\s+/', '', $name) ?? '');
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
if (isset($seen[$key])) {
|
||||
return true;
|
||||
}
|
||||
$seen[$key] = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @notes 搜索条件
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['patient_name', 'sn'],
|
||||
'between_time' => 'create_time',
|
||||
];
|
||||
}
|
||||
|
||||
/** 创建人(医师账号)多选,参数 creator_ids:数组或逗号分隔 ID */
|
||||
private function applyCreatorIdsFilter($query): void
|
||||
{
|
||||
$raw = $this->params['creator_ids'] ?? null;
|
||||
if ($raw === null || $raw === '') {
|
||||
return;
|
||||
}
|
||||
$ids = \is_array($raw) ? $raw : explode(',', (string) $raw);
|
||||
$ids = array_values(array_filter(array_map('intval', $ids), static function (int $id): bool {
|
||||
return $id > 0;
|
||||
}));
|
||||
if ($ids === []) {
|
||||
return;
|
||||
}
|
||||
$query->whereIn('creator_id', $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_filter:passed=已通过,not_passed=未通过(待审+驳回),pending,rejected
|
||||
*/
|
||||
private function applyAuditFilter($query): void
|
||||
{
|
||||
$af = (string) ($this->params['audit_filter'] ?? '');
|
||||
if ($af === '' || $af === 'all') {
|
||||
return;
|
||||
}
|
||||
switch ($af) {
|
||||
case 'passed':
|
||||
$query->where('audit_status', '=', 1);
|
||||
break;
|
||||
case 'not_passed':
|
||||
$query->whereIn('audit_status', [0, 2]);
|
||||
break;
|
||||
case 'pending':
|
||||
$query->where('audit_status', '=', 0);
|
||||
break;
|
||||
case 'rejected':
|
||||
$query->where('audit_status', '=', 2);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 来源:system=系统代开(is_system_auto=1),manual=手工(0 或空)
|
||||
*/
|
||||
private function applySourceFilter($query): void
|
||||
{
|
||||
$sf = (string) ($this->params['source_filter'] ?? '');
|
||||
if ($sf === '' || $sf === 'all') {
|
||||
return;
|
||||
}
|
||||
if ($sf === 'system') {
|
||||
$query->where('is_system_auto', 1);
|
||||
|
||||
return;
|
||||
}
|
||||
if ($sf === 'manual') {
|
||||
$query->where('is_system_auto', 0);
|
||||
}
|
||||
}
|
||||
|
||||
private function applyVisibilityScope($query): void
|
||||
{
|
||||
$query->where(function ($query) {
|
||||
// 超级管理员可查看全部
|
||||
if (!empty($this->adminInfo['root']) && (int) $this->adminInfo['root'] === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否属于可查看全部处方的角色
|
||||
$manageAllRoles = config('project.prescription_library_manage_all_roles', [0, 3]);
|
||||
$roleIds = array_values(array_unique(array_map('intval', $this->adminInfo['role_id'] ?? [])));
|
||||
$canSeeAll = false;
|
||||
foreach ($roleIds as $rid) {
|
||||
if (in_array($rid, $manageAllRoles, true)) {
|
||||
$canSeeAll = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果属于可查看全部的角色,不添加任何限制
|
||||
if ($canSeeAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 其他用户只能查看:共享的、自己创建的、自己是医助的、或指定给自己角色的
|
||||
$adminId = $this->adminId;
|
||||
$query->where(function ($q) use ($adminId, $roleIds) {
|
||||
$q->whereOr('is_shared', '=', 1);
|
||||
$q->whereOr('creator_id', '=', $adminId);
|
||||
$q->whereOr('assistant_id', '=', $adminId);
|
||||
foreach ($roleIds as $rid) {
|
||||
if ($rid > 0) {
|
||||
$q->whereOrRaw('FIND_IN_SET(?, `visible_role_ids`)', [(string) $rid]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$query = Prescription::where($this->searchWhere);
|
||||
$this->applyAuditFilter($query);
|
||||
$this->applySourceFilter($query);
|
||||
$this->applyVisibilityScope($query);
|
||||
$this->applyCreatorIdsFilter($query);
|
||||
$this->applyDataScopeByOwnerColumns($query, ['creator_id', 'assistant_id']);
|
||||
|
||||
// 全局置顶:有业务订单且处方药材为空白/重复
|
||||
$candidateIds = (clone $query)->whereNull('delete_time')->column('id');
|
||||
$riskIds = $this->collectRiskPrescriptionIds(is_array($candidateIds) ? $candidateIds : []);
|
||||
|
||||
$listQuery = $query->whereNull('delete_time');
|
||||
if ($riskIds !== []) {
|
||||
$riskIdStr = implode(',', array_map('intval', $riskIds));
|
||||
$listQuery->orderRaw("CASE WHEN id IN ({$riskIdStr}) THEN 0 ELSE 1 END ASC");
|
||||
}
|
||||
$lists = $listQuery
|
||||
->order('id', 'desc')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$rxIds = array_column($lists, 'id');
|
||||
$rxIds = array_map('intval', $rxIds);
|
||||
$rejectedRx = [];
|
||||
$bizRejectRemark = [];
|
||||
$hasBizOrderRx = [];
|
||||
if ($rxIds !== []) {
|
||||
$bizRejectRows = PrescriptionOrder::whereIn('prescription_id', $rxIds)
|
||||
->where('prescription_audit_status', 2)
|
||||
->whereNull('delete_time')
|
||||
->where('fulfillment_status', '<>', 4)
|
||||
->field(['prescription_id', 'prescription_audit_remark', 'id'])
|
||||
->order('id', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($bizRejectRows as $br) {
|
||||
$pid = (int) ($br['prescription_id'] ?? 0);
|
||||
if ($pid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$rejectedRx[$pid] = true;
|
||||
if (!isset($bizRejectRemark[$pid])) {
|
||||
$bizRejectRemark[$pid] = (string) ($br['prescription_audit_remark'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
$orderRxIds = array_unique(array_map(
|
||||
'intval',
|
||||
PrescriptionOrder::whereIn('prescription_id', $rxIds)
|
||||
->whereNull('delete_time')
|
||||
->where('fulfillment_status', '<>', 4)
|
||||
->column('prescription_id')
|
||||
));
|
||||
$hasBizOrderRx = array_fill_keys($orderRxIds, true);
|
||||
}
|
||||
|
||||
// 获取医助姓名
|
||||
$assistantIds = array_unique(array_filter(array_map('intval', array_column($lists, 'assistant_id'))));
|
||||
$assistantNames = [];
|
||||
if (!empty($assistantIds)) {
|
||||
$assistantNames = \app\common\model\auth\Admin::whereIn('id', $assistantIds)
|
||||
->column('name', 'id');
|
||||
}
|
||||
|
||||
// 处理字段格式
|
||||
foreach ($lists as &$item) {
|
||||
// 设置默认值
|
||||
$item['usage_time'] = $item['usage_time'] ?? '饭前';
|
||||
$item['usage_way'] = $item['usage_way'] ?? '温水送服';
|
||||
$item['usage_notes'] = $item['usage_notes'] ?? '';
|
||||
$item['usage_days'] = $item['usage_days'] ?? 7;
|
||||
$item['is_shared'] = $item['is_shared'] ?? 0;
|
||||
$item['is_system_auto'] = (int) ($item['is_system_auto'] ?? 0);
|
||||
$item['audit_status'] = (int) ($item['audit_status'] ?? 1);
|
||||
$item['void_status'] = (int) ($item['void_status'] ?? 0);
|
||||
$item['visible_role_ids'] = PrescriptionLogic::visibleRoleIdsToArray((string) ($item['visible_role_ids'] ?? ''));
|
||||
$rid = (int) ($item['id'] ?? 0);
|
||||
$item['business_prescription_audit_rejected'] = !empty($rejectedRx[$rid]) ? 1 : 0;
|
||||
$item['business_prescription_audit_remark'] = (string) ($bizRejectRemark[$rid] ?? '');
|
||||
$item['has_prescription_order'] = !empty($hasBizOrderRx[$rid]) ? 1 : 0;
|
||||
|
||||
// 添加医助姓名
|
||||
$assistantId = (int) ($item['assistant_id'] ?? 0);
|
||||
$item['assistant_name'] = $assistantId > 0 ? ($assistantNames[$assistantId] ?? '') : '';
|
||||
|
||||
// 将 dietary_taboo 从逗号分隔的字符串转换为数组(前端需要数组格式)
|
||||
if (!empty($item['dietary_taboo']) && is_string($item['dietary_taboo'])) {
|
||||
$item['dietary_taboo'] = array_filter(explode(',', $item['dietary_taboo']));
|
||||
} else {
|
||||
$item['dietary_taboo'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$query = Prescription::where($this->searchWhere);
|
||||
$this->applyAuditFilter($query);
|
||||
$this->applySourceFilter($query);
|
||||
$this->applyVisibilityScope($query);
|
||||
$this->applyCreatorIdsFilter($query);
|
||||
$this->applyDataScopeByOwnerColumns($query, ['creator_id', 'assistant_id']);
|
||||
|
||||
return $query->whereNull('delete_time')->count();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\tools;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use think\facade\Db;
|
||||
|
||||
|
||||
/**
|
||||
* 数据表列表
|
||||
* Class GeneratorLists
|
||||
* @package app\adminapi\lists\tools
|
||||
*/
|
||||
class DataTableLists extends BaseAdminDataLists
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 查询结果
|
||||
* @return mixed
|
||||
* @author 段誉
|
||||
* @date 2022/6/13 18:54
|
||||
*/
|
||||
public function queryResult()
|
||||
{
|
||||
$sql = 'SHOW TABLE STATUS WHERE 1=1 ';
|
||||
if (!empty($this->params['name'])) {
|
||||
$sql .= "AND name LIKE '%" . $this->params['name'] . "%'";
|
||||
}
|
||||
if (!empty($this->params['comment'])) {
|
||||
$sql .= "AND comment LIKE '%" . $this->params['comment'] . "%'";
|
||||
}
|
||||
return Db::query($sql);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 处理列表
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2022/6/13 18:54
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = array_map("array_change_key_case", $this->queryResult());
|
||||
$offset = max(0, ($this->pageNo - 1) * $this->pageSize);
|
||||
$lists = array_slice($lists, $offset, $this->pageSize, true);
|
||||
return array_values($lists);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2022/6/13 18:54
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->queryResult());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\adminapi\lists\tools;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\tools\GenerateTable;
|
||||
|
||||
|
||||
/**
|
||||
* 代码生成所选数据表列表
|
||||
* Class GenerateTableLists
|
||||
* @package app\adminapi\lists\tools
|
||||
*/
|
||||
class GenerateTableLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 设置搜索条件
|
||||
* @return \string[][]
|
||||
* @author 段誉
|
||||
* @date 2022/6/14 10:55
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['table_name', 'table_comment']
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 查询列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/6/14 10:55
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
return GenerateTable::where($this->searchWhere)
|
||||
->order(['id' => 'desc'])
|
||||
->append(['template_type_desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2022/6/14 10:55
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return GenerateTable::count();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
namespace app\adminapi\lists\user;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\enum\user\UserTerminalEnum;
|
||||
use app\common\lists\ListsExcelInterface;
|
||||
use app\common\model\user\User;
|
||||
|
||||
|
||||
/**
|
||||
* 用户列表
|
||||
* Class UserLists
|
||||
* @package app\adminapi\lists\user
|
||||
*/
|
||||
class UserLists extends BaseAdminDataLists implements ListsExcelInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 搜索条件
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2022/9/22 15:50
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
$allowSearch = ['keyword', 'channel', 'create_time_start', 'create_time_end'];
|
||||
return array_intersect(array_keys($this->params), $allowSearch);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取用户列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/22 15:50
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$field = "id,sn,nickname,sex,avatar,account,mobile,channel,create_time";
|
||||
$lists = User::withSearch($this->setSearch(), $this->params)
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->field($field)
|
||||
->order('id desc')
|
||||
->select()->toArray();
|
||||
|
||||
foreach ($lists as &$item) {
|
||||
$item['channel'] = UserTerminalEnum::getTermInalDesc($item['channel']);
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2022/9/22 15:51
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return User::withSearch($this->setSearch(), $this->params)->count();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 导出文件名
|
||||
* @return string
|
||||
* @author 段誉
|
||||
* @date 2022/11/24 16:17
|
||||
*/
|
||||
public function setFileName(): string
|
||||
{
|
||||
return '用户列表';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 导出字段
|
||||
* @return string[]
|
||||
* @author 段誉
|
||||
* @date 2022/11/24 16:17
|
||||
*/
|
||||
public function setExcelFields(): array
|
||||
{
|
||||
return [
|
||||
'sn' => '用户编号',
|
||||
'nickname' => '用户昵称',
|
||||
'account' => '账号',
|
||||
'mobile' => '手机号码',
|
||||
'channel' => '注册来源',
|
||||
'create_time' => '注册时间',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user