更新
This commit is contained in:
@@ -491,7 +491,11 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
<pagination
|
||||
v-model="pager"
|
||||
:page-sizes="[15, 20, 30, 40, 100, 150, 200]"
|
||||
@change="getLists"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
|
||||
@@ -463,8 +463,8 @@
|
||||
<el-tooltip placement="top" effect="dark" :show-after="200">
|
||||
<template #content>
|
||||
<div style="max-width: 300px; line-height: 1.7; font-size: 12px">
|
||||
按订单<b>创建时间</b>统计,与「处方订单列表」业绩合计口径一致(<b>诊单医助</b>归属展示部门)。<br />
|
||||
<b>不受渠道筛选影响</b>,展示部门下全部订单。
|
||||
按订单<b>创建时间</b>统计(剔除履约已取消),金额与<b>接诊诊单</b>列一致:诊单<b>医助</b>或订单<b>创建人</b>的人事部门落在本展示行(含下级)即计入,多行命中时取最深的展示部门。<br />
|
||||
<b>不受渠道筛选影响</b>。与侧栏按部门打开的业务订单列表可对齐。
|
||||
</div>
|
||||
</template>
|
||||
<el-icon class="col-info"><InfoFilled /></el-icon>
|
||||
@@ -500,7 +500,7 @@
|
||||
<el-tooltip placement="top" effect="dark" :show-after="200">
|
||||
<template #content>
|
||||
<div style="max-width: 320px; line-height: 1.7; font-size: 12px">
|
||||
仅<b>已完成</b>(status=3),与 admin「医生预约 / 已完成」列表计数条件一致(appointment_date、诊单未软删);归属规则同「接诊诊单」列数据源说明。
|
||||
仅<b>已完成</b>(status=3),与 admin「医生预约 / 已完成」列表计数条件一致(appointment_date、诊单未软删);部门归属医助优先,否则接诊医生。
|
||||
</div>
|
||||
</template>
|
||||
<el-icon class="col-info"><InfoFilled /></el-icon>
|
||||
@@ -511,8 +511,8 @@
|
||||
<el-tooltip placement="top" effect="dark" :show-after="200">
|
||||
<template #content>
|
||||
<div style="max-width: 320px; line-height: 1.7; font-size: 12px">
|
||||
与「处方订单列表」在 <b>assistant_dept_id</b> 下条数同源:订单 <b>create_time</b> 落入区间、履约 <b>fulfillment_status ≠ 4</b>;<br />
|
||||
按诊单 <b>医助 assistant_id</b>(经 admin_dept 映射到展示「中心」),非订单 creator_id。选定渠道时本列仍为全量。
|
||||
<b>业务订单条数</b>(非取消):订单 <b>create_time</b> 落入区间、<b>fulfillment_status ≠ 4</b>;与列表筛选
|
||||
<b>assistant_dept_id</b> 时一致——诊单 <b>医助</b>或订单 <b>创建人</b>的人事部门落在该部门子树即计入(表格多行命中时取最深的展示部门)。与侧栏勾选「与表格业绩对齐」时的集合可能略有差异。选定渠道时本列仍为全量。
|
||||
</div>
|
||||
</template>
|
||||
<el-icon class="col-info"><InfoFilled /></el-icon>
|
||||
@@ -994,13 +994,36 @@
|
||||
<span class="yeji-order-appt-k">类型</span>
|
||||
{{ row.linked_appointment.appointment_type_desc || '—' }}
|
||||
</div>
|
||||
<div class="yeji-order-appt-line">
|
||||
<div class="yeji-order-appt-line yeji-order-appt-line--channel">
|
||||
<span class="yeji-order-appt-k">渠道</span>
|
||||
{{
|
||||
row.linked_appointment.channel_source_desc ||
|
||||
row.linked_appointment.channel_source ||
|
||||
'—'
|
||||
}}
|
||||
<span class="yeji-order-appt-channel-text">
|
||||
{{
|
||||
row.linked_appointment.channel_source_desc ||
|
||||
row.linked_appointment.channel_source ||
|
||||
'—'
|
||||
}}
|
||||
<span
|
||||
v-if="
|
||||
orderDrawerLinkedApptChannelDictValue(row.linked_appointment) !==
|
||||
''
|
||||
"
|
||||
class="yeji-order-appt-sub"
|
||||
>
|
||||
(字典 value
|
||||
{{ orderDrawerLinkedApptChannelDictValue(row.linked_appointment) }}
|
||||
)
|
||||
</span>
|
||||
</span>
|
||||
<el-tooltip placement="top" effect="dark" :show-after="200">
|
||||
<template #content>
|
||||
<div style="max-width: 280px; line-height: 1.65; font-size: 12px">
|
||||
「字典 value」= 系统字典
|
||||
<b>channels</b> 的 value,与业绩表里按渠道收窄时所用<b>挂号 channels</b>一致;便于和进线列所选渠道对照,<b>不是</b>企微
|
||||
add_external_contact 单上的独立编号。
|
||||
</div>
|
||||
</template>
|
||||
<el-icon class="yeji-order-appt-info" aria-label="说明"><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="yeji-order-appt-empty">—</span>
|
||||
@@ -2526,6 +2549,23 @@ function orderDrawerApptSchedule(a: Record<string, any>): string {
|
||||
return date || tail || '—'
|
||||
}
|
||||
|
||||
/** 挂号 channels 字典 value(后端 channel_dict_value,与业绩/进线所用字典同源);无数值则空串 */
|
||||
function orderDrawerLinkedApptChannelDictValue(a: Record<string, any> | null | undefined): string {
|
||||
if (!a) return ''
|
||||
const v = a.channel_dict_value
|
||||
if (v !== null && v !== undefined && v !== '') {
|
||||
const n = Number(v)
|
||||
if (Number.isFinite(n)) {
|
||||
return String(Math.trunc(n))
|
||||
}
|
||||
}
|
||||
const raw = a.channel_source
|
||||
if (raw === null || raw === undefined) return ''
|
||||
const s = String(raw).trim()
|
||||
if (s === '' || !/^\d+$/.test(s)) return ''
|
||||
return String(parseInt(s, 10))
|
||||
}
|
||||
|
||||
function formatOrderDrawerTime(t: unknown): string {
|
||||
if (t == null || t === '') return '—'
|
||||
const n = Number(t)
|
||||
@@ -2562,6 +2602,10 @@ async function fetchOrderDrawerPage() {
|
||||
}
|
||||
if (f.mode === 'dept') {
|
||||
params.assistant_dept_id = f.deptId
|
||||
/** 与看板「接诊诊单」列一致:处方列表默认 assistant_dept_id(诊单医助或创建人∪子树),不用业绩摊行医助集合 */
|
||||
if (selectedDeptIds.value.length > 0) {
|
||||
params.dept_ids = selectedDeptIds.value.join(',')
|
||||
}
|
||||
} else {
|
||||
params.assistant_id = f.assistantId
|
||||
if (f.erCenterRevisitSlot !== undefined) {
|
||||
@@ -2616,7 +2660,7 @@ function openOrderDrawerByDept(tb: YejiTable, row: YejiRow) {
|
||||
end: tb.end_date,
|
||||
}
|
||||
orderDrawerTitle.value = `业务订单 · ${row.dept_name}`
|
||||
orderDrawerHint.value = `创建时间:${tb.start_date} ~ ${tb.end_date} · 诊单医助所属部门 · 不含履约已取消`
|
||||
orderDrawerHint.value = `创建时间:${tb.start_date} ~ ${tb.end_date} · 与「接诊诊单」同口径:诊单医助或订单创建人落在本部门(含下级)· 不含履约已取消`
|
||||
orderDrawerPage.value = 1
|
||||
orderDrawerVisible.value = true
|
||||
void fetchOrderDrawerPage()
|
||||
@@ -4493,6 +4537,23 @@ onMounted(async () => {
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.yeji-order-appt-line--channel {
|
||||
align-items: flex-start;
|
||||
|
||||
.yeji-order-appt-channel-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.yeji-order-appt-info {
|
||||
font-size: 14px;
|
||||
color: var(--yj-muted, #94a3b8);
|
||||
cursor: help;
|
||||
flex-shrink: 0;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.yeji-order-appt-k {
|
||||
|
||||
@@ -883,6 +883,21 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
}
|
||||
|
||||
if (isset($this->params['assistant_dept_id']) && $this->params['assistant_dept_id'] !== '' && (int) $this->params['assistant_dept_id'] > 0) {
|
||||
if ((int) ($this->params['yeji_drawer_match_table_performance'] ?? 0) === 1) {
|
||||
$rowDeptId = (int) $this->params['assistant_dept_id'];
|
||||
$adminIds = YejiStatsLogic::collectAdminIdsAttributedToYejiTableRow(
|
||||
$rowDeptId,
|
||||
$this->params['dept_ids'] ?? null
|
||||
);
|
||||
if ($adminIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
$this->applyPerformanceAttributionAdminInFilter($query, $adminIds);
|
||||
|
||||
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 {
|
||||
@@ -909,6 +924,35 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 业绩看板侧栏:只保留「业绩归属医助」落在给定 admin_id 集合的订单(与 YejiStatsLogic::sqlPerformanceAttributionAdminExpr 一致)。
|
||||
*
|
||||
* @param int[] $adminIds
|
||||
*/
|
||||
private function applyPerformanceAttributionAdminInFilter($query, array $adminIds): void
|
||||
{
|
||||
$adminIds = array_values(array_unique(array_filter(array_map('intval', $adminIds), static function (int $x): bool {
|
||||
return $x > 0;
|
||||
})));
|
||||
if ($adminIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
$poTbl = (new PrescriptionOrder())->getTable();
|
||||
$diagTbl = (new Diagnosis())->getTable();
|
||||
$rxTbl = (new Prescription())->getTable();
|
||||
$inList = implode(',', $adminIds);
|
||||
$query->whereRaw(
|
||||
"EXISTS (SELECT 1 FROM `{$diagTbl}` dg "
|
||||
. "LEFT JOIN `{$rxTbl}` rx ON rx.`id` = `{$poTbl}`.`prescription_id` AND rx.`delete_time` IS NULL "
|
||||
. "WHERE dg.`id` = `{$poTbl}`.`diagnosis_id` AND dg.`delete_time` IS NULL AND `{$poTbl}`.`diagnosis_id` > 0 AND "
|
||||
. '(CASE WHEN dg.`assistant_id` > 0 THEN dg.`assistant_id` '
|
||||
. "WHEN `{$poTbl}`.`creator_id` > 0 AND (rx.`id` IS NULL OR NOT (`{$poTbl}`.`creator_id` <=> rx.`creator_id`)) THEN `{$poTbl}`.`creator_id` "
|
||||
. 'ELSE 0 END) IN (' . $inList . '))'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按诊单患者姓名 / 手机号模糊检索(关联 diagnosis_id)
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1000,6 +1000,11 @@ class PrescriptionOrderLogic
|
||||
}
|
||||
}
|
||||
|
||||
$channelDictValue = null;
|
||||
if ($chSrc !== '' && is_numeric($chSrc)) {
|
||||
$channelDictValue = (int) $chSrc;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $apptId,
|
||||
'appointment_date' => (string) ($row['appointment_date'] ?? ''),
|
||||
@@ -1012,6 +1017,8 @@ class PrescriptionOrderLogic
|
||||
'doctor_name' => $doctorName,
|
||||
'channel_source' => $chSrc,
|
||||
'channel_source_desc' => $chDesc,
|
||||
/** 字典 type_value=channels 的 value(整型);与业绩看板挂号 channels 收窄、进线统计所用字典为同套 */
|
||||
'channel_dict_value' => $channelDictValue,
|
||||
'remark' => (string) ($row['remark'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ use think\console\Output;
|
||||
*
|
||||
* 配置 crontab(每 10 分钟):拉快递 100 + 按履约「已发货/已签收」核对释放诊单医助(与是否甘草单无关)
|
||||
* 0,10,20,30,40,50 * * * * cd /path/to/server && php think express:auto-update >> /dev/null 2>&1
|
||||
*
|
||||
* 已对非二中心医助执行发货/签收自动释放的诊单会写入 tcm_diagnosis.shipped_non_er_assistant_cleared_at,
|
||||
* 后续履约核对不再重复扫描(需先执行 sql/1.9.20260507/add_diagnosis_shipped_non_er_assistant_cleared_at.sql)。
|
||||
*/
|
||||
class ExpressAutoUpdate extends Command
|
||||
{
|
||||
@@ -34,7 +37,7 @@ class ExpressAutoUpdate extends Command
|
||||
|
||||
try {
|
||||
$result = ExpressTrackingService::autoUpdateBatch(500);
|
||||
$recon = ExpressTrackingService::reconcileAssistantReleaseForShippedPrescriptionOrders(200);
|
||||
$recon = ExpressTrackingService::reconcileAssistantReleaseForShippedPrescriptionOrders(500);
|
||||
|
||||
$duration = round(microtime(true) - $startTime, 2);
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ use think\console\Output;
|
||||
*
|
||||
* 建议 crontab(每 30 分钟执行一次):
|
||||
* 0,30 * * * * cd /path/to/server && php think gancao:sync-logistics --limit=200 >> runtime/log/gancao_sync.log 2>&1
|
||||
*
|
||||
* 已对非二中心医助执行发货/签收自动释放的诊单会写入 tcm_diagnosis.shipped_non_er_assistant_cleared_at,
|
||||
* 后续履约核对不再重复扫描(需先执行 sql/1.9.20260507/add_diagnosis_shipped_non_er_assistant_cleared_at.sql)。
|
||||
*/
|
||||
class GancaoSyncLogisticsRoute extends Command
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ use app\common\model\ExpressStateLog;
|
||||
use app\common\model\ExpressQueryLog;
|
||||
use app\common\model\tcm\Diagnosis;
|
||||
use app\common\model\tcm\DiagnosisAssignLog;
|
||||
use app\common\model\tcm\Prescription;
|
||||
use app\common\model\tcm\PrescriptionOrder;
|
||||
use app\common\model\tcm\PrescriptionOrderLog;
|
||||
use think\facade\Db;
|
||||
@@ -250,6 +251,7 @@ class ExpressTrackingService
|
||||
* 快递 100 定时拉轨迹、甘草路由、甘草回调与履约核对共用同一规则。
|
||||
* 例外:指派日志中若存在「从某医助改派到另一医助」(原、新医助 ID 均大于 0)的记录,视为人工指派链,自动释放跳过。
|
||||
* 例外:患者当前医助在「二中心」及其全部子部门(与后台部门树一致)下任职时,不自动清空医助(按 admin_dept 与 dept 树判定)。
|
||||
* 非二中心且已自动释放:诊单写入 shipped_non_er_assistant_cleared_at,assistant_id 已为 0 时后续履约核对不再重复扫描(重新指派医助后可再次处理)。
|
||||
*
|
||||
* @param array{tracking_id?:int,tracking_number?:string,source?:string} $meta
|
||||
* @return array{
|
||||
@@ -288,7 +290,26 @@ class ExpressTrackingService
|
||||
if (!$diag) {
|
||||
return null;
|
||||
}
|
||||
$shippedClearedAt = (int) ($diag->getAttr('shipped_non_er_assistant_cleared_at') ?? 0);
|
||||
$diagAssistantIdNow = (int) ($diag->getAttr('assistant_id') ?? 0);
|
||||
if ($shippedClearedAt > 0 && $diagAssistantIdNow === 0) {
|
||||
return null;
|
||||
}
|
||||
// 诊单 assistant_id 与列表「医助」筛选一致:医助常代建单但诊单未写 assistant_id,此时用创建人作为待释放身份(创建人即开方医生时不采用,避免误记指派日志)
|
||||
$fromAssistantId = (int) ($diag->getAttr('assistant_id') ?? 0);
|
||||
if ($fromAssistantId <= 0) {
|
||||
$creatorId = (int) ($order->getAttr('creator_id') ?? 0);
|
||||
if ($creatorId > 0) {
|
||||
$rxId = (int) ($order->getAttr('prescription_id') ?? 0);
|
||||
$rxCreator = 0;
|
||||
if ($rxId > 0) {
|
||||
$rxCreator = (int) Prescription::where('id', $rxId)->whereNull('delete_time')->value('creator_id');
|
||||
}
|
||||
if ($rxId <= 0 || $creatorId !== $rxCreator) {
|
||||
$fromAssistantId = $creatorId;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($fromAssistantId <= 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -328,7 +349,10 @@ class ExpressTrackingService
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
Diagnosis::where('id', $diagnosisId)->whereNull('delete_time')->update(['assistant_id' => 0]);
|
||||
Diagnosis::where('id', $diagnosisId)->whereNull('delete_time')->update([
|
||||
'assistant_id' => 0,
|
||||
'shipped_non_er_assistant_cleared_at' => time(),
|
||||
]);
|
||||
|
||||
DiagnosisAssignLog::create([
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
@@ -624,19 +648,29 @@ class ExpressTrackingService
|
||||
/**
|
||||
* 按处方订单履约核对:已发货(5)/已签收(6) 时诊单仍挂在医助的,一律释放并写指派日志。
|
||||
* 与快递 100 / 甘草来自哪个物流渠道无关,保障「已发货」后患者侧医助被清掉。
|
||||
* 已对非二中心医助做过发货/签收释放且 assistant_id=0 的诊单(shipped_non_er_assistant_cleared_at>0)不再纳入本扫描,避免重复命中 creator 分支。
|
||||
*
|
||||
* @return array{scanned: int, cleared: int, lines: list<string>}
|
||||
*/
|
||||
public static function reconcileAssistantReleaseForShippedPrescriptionOrders(int $limit = 200): array
|
||||
{
|
||||
$dTable = (new Diagnosis())->getTable();
|
||||
$rxTable = (new Prescription())->getTable();
|
||||
$orders = PrescriptionOrder::alias('po')
|
||||
->join($dTable . ' d', 'd.id = po.diagnosis_id')
|
||||
->leftJoin($rxTable . ' rx', 'rx.id = po.prescription_id AND rx.delete_time IS NULL')
|
||||
->whereNull('po.delete_time')
|
||||
->whereNull('d.delete_time')
|
||||
->whereRaw('(IFNULL(d.shipped_non_er_assistant_cleared_at, 0) = 0 OR d.assistant_id > 0)')
|
||||
->whereIn('po.fulfillment_status', [5, 6])
|
||||
->where('po.diagnosis_id', '>', 0)
|
||||
->where('d.assistant_id', '>', 0)
|
||||
->where(function ($w) {
|
||||
$w->where('d.assistant_id', '>', 0)
|
||||
->whereOr(function ($w2) {
|
||||
$w2->where('po.creator_id', '>', 0)
|
||||
->whereRaw('(rx.id IS NULL OR po.creator_id <> rx.creator_id)');
|
||||
});
|
||||
})
|
||||
->order('po.id', 'asc')
|
||||
->limit($limit)
|
||||
->field('po.*')
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-- 非二中心医助因业务订单已发货/已签收被自动释放后打标,履约核对等任务不再重复处理同一诊单(人工重新指派医助后可再次参与)
|
||||
|
||||
ALTER TABLE `zyt_tcm_diagnosis`
|
||||
ADD COLUMN `shipped_non_er_assistant_cleared_at` int(10) unsigned NOT NULL DEFAULT 0
|
||||
COMMENT '非二中心医助·发货/签收自动释放诊单医助的时间戳;>0 且 assistant_id=0 时跳过重复核对'
|
||||
AFTER `assistant_id`;
|
||||
Reference in New Issue
Block a user