「{{ tb.channel_name }}」渠道业绩(与卡片标题渠道一致)。与「处方订单列表」业绩同口径:按订单
- create_time 归日,剔除履约已取消(4)。
+ create_time 归日,剔除履约状态 4(已取消)/9(拒收)/10(退款)。
渠道判定用 EXISTS:该患者有任一挂号 channels 命中字典 + status=3(不限挂号时点)。
点击金额可查看该部门本渠道订单明细(侧栏「渠道筛选」列表)。
@@ -446,9 +446,9 @@
<<<<<<< HEAD
- 业务订单条数(非取消):订单 create_time 落入区间、fulfillment_status ≠ 4;按订单创建人的人事部门落在该部门子树即计入(表格多行命中时取最深的展示部门)。与合计业绩 / 医助排行榜接诊诊单同口径。选定渠道时本列仍为全量。
+ 业务订单条数(计业绩):订单 create_time 落入区间、fulfillment_status ∉ {4,9,10}(NULL 计入);按订单创建人的人事部门落在该部门子树即计入(表格多行命中时取最深的展示部门)。与合计业绩 / 医助排行榜接诊诊单同口径。选定渠道时本列仍为全量。
=======
- 业务订单条数(非取消):订单 create_time 落入区间、fulfillment_status ≠ 4;与列表筛选
+ 业务订单条数(计业绩):订单 create_time 落入区间、fulfillment_status ∉ {4,9,10};与列表筛选
assistant_dept_id 时一致——创建人人事部门优先,无创建人则诊单 医助;落在该部门子树即计入(表格多行命中时取最深的展示部门)。与侧栏勾选「与表格业绩对齐」时的集合可能略有差异。选定渠道时本列仍为全量。
>>>>>>> master
@@ -461,7 +461,7 @@
- 「{{ tb.channel_name }}」渠道成交单数;与渠道业绩同源:区间内 非取消业务订单 条数,按订单
+ 「{{ tb.channel_name }}」渠道成交单数;与渠道业绩同源:区间内 计业绩的业务订单 条数(剔除 4/9/10),按订单
create_time 归日;渠道判定同业绩列。
@@ -974,7 +974,7 @@
>
业务订单金额合计
- (不含履约已取消,与上方业绩口径一致)
+ (不含履约 4·9·10,与上方业绩口径一致)
¥{{ formatOrderDrawerMoney(orderDrawerTotalAmount) }}
@@ -2129,7 +2129,7 @@ type OrderDrawerFilter =
}
const orderDrawerFilter = ref(null)
-/** 列表接口 extend,含 stats_order_amount_performance(与当前筛选、不含已取消口径一致) */
+/** 列表接口 extend,含 stats_order_amount_performance(与当前筛选一致:剔除履约 4/9/10) */
const orderDrawerExtend = ref | null>(null)
const orderDrawerTotalAmount = computed(() => {
@@ -2641,7 +2641,7 @@ function buildOrderDrawerListParams(): Record | null {
const params: Record = {
start_time: `${f.start} 00:00:00`,
end_time: `${f.end} 23:59:59`,
- /** 与业绩列口径一致:不列出履约已取消(4) */
+ /** 与业绩列口径一致:不列出履约 4(已取消)/9(拒收)/10(退款) */
exclude_fulfillment_cancelled: 1,
/**
* 业绩看板入口标志:后端 PrescriptionOrderLists::applyDoctorAssistantFilters 看到此标志后,
@@ -2760,7 +2760,7 @@ function openOrderDrawerByDept(tb: YejiTable, row: YejiRow, opts?: { listTab?: '
yejiTableRowDeptIds: tb.rows.map(r => r.dept_id).filter((id): id is number => typeof id === 'number' && id > 0),
}
orderDrawerTitle.value = `业务订单 · ${row.dept_name}`
- orderDrawerHint.value = `创建时间:${tb.start_date} ~ ${tb.end_date} · 与「合计业绩 / 接诊诊单」同口径:订单创建人人事部门 ∈ 本部门子树 · 不含履约已取消`
+ orderDrawerHint.value = `创建时间:${tb.start_date} ~ ${tb.end_date} · 与「合计业绩 / 接诊诊单」同口径:订单创建人人事部门 ∈ 本部门子树 · 不含履约 4/9/10`
orderDrawerPage.value = 1
orderDrawerVisible.value = true
void fetchOrderDrawerPage()
@@ -2784,7 +2784,7 @@ function openOrderDrawerByYejiTableScope(
yejiTableRowDeptIds: tb.rows.map(r => r.dept_id).filter((id): id is number => typeof id === 'number' && id > 0),
}
orderDrawerTitle.value = '业务订单 · 当前展示合计'
- orderDrawerHint.value = `创建时间:${tb.start_date} ~ ${tb.end_date} · 已选展示部门 1 个根节点(含其下全部展示行)· 不含履约已取消`
+ orderDrawerHint.value = `创建时间:${tb.start_date} ~ ${tb.end_date} · 已选展示部门 1 个根节点(含其下全部展示行)· 不含履约 4/9/10`
orderDrawerPage.value = 1
orderDrawerVisible.value = true
void fetchOrderDrawerPage()
@@ -2841,7 +2841,7 @@ function openOrderDrawerByAssistant(row: LeaderboardPack['leaderboards'][0]['row
end,
}
orderDrawerTitle.value = `业务订单 · ${row.name}`
- orderDrawerHint.value = `创建时间:${start} ~ ${end} · 创建人=${row.name}(与排行榜诊金同口径)· 不含履约已取消`
+ orderDrawerHint.value = `创建时间:${start} ~ ${end} · 创建人=${row.name}(与排行榜诊金同口径)· 不含履约 4/9/10`
orderDrawerPage.value = 1
orderDrawerVisible.value = true
void fetchOrderDrawerPage()
@@ -2865,7 +2865,7 @@ function openOrderDrawerByAssistantErCenterRevisit(
}
const slotLbl = revisitSlot === 0 ? '复诊合计' : yejiRevisitSlotColumnTitle(revisitSlot)
orderDrawerTitle.value = `业务订单 · ${assistant.name} · ${slotLbl}`
- orderDrawerHint.value = `创建时间:${start} ~ ${end} · 创建人=${assistant.name}(与部门表 / 排行榜复诊同口径)· 仅二中心复诊口径 · ${slotLbl} · 不含履约已取消`
+ orderDrawerHint.value = `创建时间:${start} ~ ${end} · 创建人=${assistant.name}(与部门表 / 排行榜复诊同口径)· 仅二中心复诊口径 · ${slotLbl} · 不含履约 4/9/10`
orderDrawerPage.value = 1
orderDrawerVisible.value = true
void fetchOrderDrawerPage()
diff --git a/admin/src/views/tcm/diagnosis/index.vue b/admin/src/views/tcm/diagnosis/index.vue
index a95d3b36..a2d26249 100644
--- a/admin/src/views/tcm/diagnosis/index.vue
+++ b/admin/src/views/tcm/diagnosis/index.vue
@@ -797,6 +797,32 @@ function buildTcmDiagnosisListRequestPayload(req: Record): Reco
}
}
+/**
+ * 待分配「订单月份」:未选月份时,未进入 Tab 前默认当月(与 handlePendingAssignTabClick 一致);
+ * 在 Tab 内清空月份表示不限月份。
+ */
+function resolvePendingAssignOrderMonthForRequest(): string {
+ const trimmed = String(formData.pending_assign_order_month ?? '').trim()
+ if (trimmed !== '') return trimmed
+ if (formData.pending_assign === '1') return ''
+ return dayjs().format('YYYY-MM')
+}
+
+/** 待分配角标 count 请求:与列表同条件,且去掉其它顶部 Tab 残留(如默认「当天挂号」) */
+function buildPendingAssignCountPayload(): Record {
+ return buildTcmDiagnosisListRequestPayload({
+ ...formData,
+ page_no: 1,
+ page_size: 1,
+ pending_assign: 1,
+ appointment_date: '',
+ has_appointment: '',
+ pending_booking: '',
+ completed_appointment: '',
+ pending_assign_order_month: resolvePendingAssignOrderMonthForRequest()
+ } as Record) as Record
+}
+
const fetchTcmDiagnosisListsForPaging = (req: Record) =>
tcmDiagnosisLists(buildTcmDiagnosisListRequestPayload(req) as any)
@@ -925,16 +951,7 @@ const fetchDateCounts = async () => {
tcmDiagnosisLists({ page_no: 1, page_size: 1 }),
tcmDiagnosisLists({ has_appointment: 0, page_no: 1, page_size: 1 }),
tcmDiagnosisLists({ completed_appointment: 1, page_no: 1, page_size: 1 }),
- tcmDiagnosisLists(
- buildTcmDiagnosisListRequestPayload({
- pending_assign: 1,
- page_no: 1,
- page_size: 1,
- pending_assign_order_month: formData.pending_assign_order_month,
- pending_assign_keyword: formData.pending_assign_keyword,
- keyword: formData.keyword
- }) as any
- )
+ tcmDiagnosisLists(buildPendingAssignCountPayload() as any)
])
dateCounts.value = {
[yesterdayStr.value]: yesterday?.count ?? 0,
diff --git a/server/app/adminapi/lists/qywx/CustomerLists.php b/server/app/adminapi/lists/qywx/CustomerLists.php
index 8255342d..21e22296 100644
--- a/server/app/adminapi/lists/qywx/CustomerLists.php
+++ b/server/app/adminapi/lists/qywx/CustomerLists.php
@@ -77,6 +77,23 @@ class CustomerLists extends BaseAdminDataLists implements ListsSearchInterface
}
}
+ // 添加时间:与 lists 排序口径一致(external_first_add_time 优先,0 则 create_time)
+ $addStart = trim((string) ($this->params['add_time_start'] ?? ''));
+ $addEnd = trim((string) ($this->params['add_time_end'] ?? ''));
+ $effExpr = 'COALESCE(NULLIF(external_first_add_time, 0), create_time)';
+ if ($addStart !== '') {
+ $t = strtotime($addStart . ' 00:00:00');
+ if ($t !== false) {
+ $query->whereRaw($effExpr . ' > 0 AND ' . $effExpr . ' >= ?', [$t]);
+ }
+ }
+ if ($addEnd !== '') {
+ $t = strtotime($addEnd . ' 23:59:59');
+ if ($t !== false) {
+ $query->whereRaw($effExpr . ' > 0 AND ' . $effExpr . ' <= ?', [$t]);
+ }
+ }
+
return $query;
}
diff --git a/server/app/adminapi/lists/tcm/PrescriptionOrderLists.php b/server/app/adminapi/lists/tcm/PrescriptionOrderLists.php
index acd0f666..1cd39e74 100644
--- a/server/app/adminapi/lists/tcm/PrescriptionOrderLists.php
+++ b/server/app/adminapi/lists/tcm/PrescriptionOrderLists.php
@@ -107,7 +107,7 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
$this->applyCreatorOrOwnPrescriptionVisibility($query);
$this->applyDataScopeForPrescriptionOrder($query);
}
- // 业绩看板侧栏等:不展示履约已取消(4),与 stats 业绩统计口径一致
+ // 业绩看板侧栏等:不展示履约 4/9/10,与 stats 业绩口径一致
if ((int) ($this->params['exclude_fulfillment_cancelled'] ?? 0) === 1) {
YejiStatsLogic::applyPrescriptionOrderNotCancelledForPerformanceQuery($query, '');
}
@@ -378,7 +378,7 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
$this->applyDataScopeForPrescriptionOrder($query);
}
}
- // 与看板「合计业绩」一致:NULL 安全剔除履约已取消(4),勿用 `<>` 以免误丢 NULL 状态行
+ // 与看板「合计业绩」一致:NULL 安全剔除履约 4/9/10
if ((int) ($this->params['exclude_fulfillment_cancelled'] ?? 0) === 1) {
YejiStatsLogic::applyPrescriptionOrderNotCancelledForPerformanceQuery($query, '');
}
@@ -570,13 +570,13 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
'stats_order_amount' => $s['order_amount'],
'stats_order_amount_not_cancelled' => $s['order_amount_not_cancelled'],
'stats_order_amount_cancelled' => $s['order_amount_cancelled'],
- /** 业绩:业务订单金额,剔除履约已取消(fulfillment_status=4),其余状态全部计入 */
- 'stats_order_amount_performance' => $s['order_amount_not_cancelled'],
+ /** 业绩:业务订单金额,剔除履约已取消/拒收/退款(4/9/10),NULL 与其它状态计入 */
+ 'stats_order_amount_performance' => $s['order_amount_performance'] ?? $s['order_amount_not_cancelled'],
'stats_linked_pay_amount' => $s['linked_pay_amount'],
'stats_linked_pay_amount_not_cancelled' => $s['linked_pay_amount_not_cancelled'],
'stats_linked_pay_amount_cancelled' => $s['linked_pay_amount_cancelled'],
- /** 业绩-关联实付:剔除履约已取消订单的关联支付金额 */
- 'stats_linked_pay_amount_performance' => $s['linked_pay_amount_not_cancelled'],
+ /** 业绩-关联实付:剔除履约 4/9/10 订单的关联支付金额 */
+ 'stats_linked_pay_amount_performance' => $s['linked_pay_amount_performance'] ?? $s['linked_pay_amount_not_cancelled'],
'stats_time_start' => $s['label_start'],
'stats_time_end' => $s['label_end'],
/** 统计口径:all=支付审核白名单全量;assistant=医助仅诊单协助业绩;restricted=与列表同域 */
@@ -693,7 +693,7 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
$t1 = $tmp;
}
$q = $this->buildBaseQueryForStats(false)->whereBetween('create_time', [$t0, $t1]);
- // 全量「合计业绩」与看板列同口径:始终剔除履约已取消(4),不依赖 exclude 参数
+ // 全量「合计业绩」与看板列同口径:始终剔除履约 4/9/10,不依赖 exclude 参数
YejiStatsLogic::applyPrescriptionOrderNotCancelledForPerformanceQuery($q, '');
$amount = round((float) (clone $q)->sum('amount'), 2);
$count = (int) (clone $q)->count();
@@ -715,7 +715,7 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
YejiStatsLogic::applyPrescriptionOrderNotCancelledForPerformanceQuery($pendingRxQuery, '');
return [
- // 处方审核待处理(不含履约已取消)
+ // 处方审核待处理(不含履约 4/9/10)
'pending_rx' => (int) $pendingRxQuery->where('prescription_audit_status', 0)->count(),
// 支付审核待处理(前提:处方已通过)
'pending_pay' => (int) (clone $query)->where('prescription_audit_status', 1)->where('payment_slip_audit_status', 0)->count(),
@@ -761,8 +761,8 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
}
$q = $this->buildBaseQueryForStats()->whereBetween('create_time', [$t0, $t1]);
- $orderTotal = round((float) (clone $q)->sum('amount'), 2);
- $orderCancelled = round((float) (clone $q)->where('fulfillment_status', 4)->sum('amount'), 2);
+ $orderTotal = round((float) (clone $q)->sum('amount'), 2);
+ $orderCancelled = round((float) (clone $q)->where('fulfillment_status', 4)->sum('amount'), 2);
$orderNotCancelled = round($orderTotal - $orderCancelled, 2);
$idsAll = (clone $q)->column('id');
@@ -770,7 +770,7 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
return $id > 0;
}));
if ($idsAll === []) {
- return $this->statsExtendPayload(
+ $empty = $this->statsExtendPayload(
$orderTotal,
$orderNotCancelled,
$orderCancelled,
@@ -780,6 +780,12 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
$l0,
$l1
);
+ $qPerfEmpty = clone $q;
+ YejiStatsLogic::applyPrescriptionOrderNotCancelledForPerformanceQuery($qPerfEmpty, '');
+ $empty['order_amount_performance'] = round((float) $qPerfEmpty->sum('amount'), 2);
+ $empty['linked_pay_amount_performance'] = 0.0;
+
+ return $empty;
}
$idsCancel = (clone $q)->where('fulfillment_status', 4)->column('id');
$idsCancel = array_values(array_filter(array_map('intval', $idsCancel), static function (int $id): bool {
@@ -791,7 +797,7 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
$linkedNotCancelled = $this->sumLinkedPayForPrescriptionOrderIds($idsNotCancelled);
$linkedCancelled = $this->sumLinkedPayForPrescriptionOrderIds($idsCancel);
- return $this->statsExtendPayload(
+ $base = $this->statsExtendPayload(
$orderTotal,
$orderNotCancelled,
$orderCancelled,
@@ -801,6 +807,21 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
$l0,
$l1
);
+
+ $qPerf = clone $q;
+ YejiStatsLogic::applyPrescriptionOrderNotCancelledForPerformanceQuery($qPerf, '');
+ $base['order_amount_performance'] = round((float) $qPerf->sum('amount'), 2);
+
+ $idsExcludedPerf = (clone $q)
+ ->whereIn('fulfillment_status', YejiStatsLogic::PRESCRIPTION_ORDER_FULFILLMENT_EXCLUDED_FROM_PERFORMANCE)
+ ->column('id');
+ $idsExcludedPerf = array_values(array_filter(array_map('intval', $idsExcludedPerf), static function (int $id): bool {
+ return $id > 0;
+ }));
+ $idsPerf = array_values(array_diff($idsAll, $idsExcludedPerf));
+ $base['linked_pay_amount_performance'] = $this->sumLinkedPayForPrescriptionOrderIds($idsPerf);
+
+ return $base;
}
/**
@@ -808,7 +829,11 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
*/
private function statsZeroPayload(): array
{
- return $this->statsExtendPayload(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, '', '');
+ $z = $this->statsExtendPayload(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, '', '');
+ $z['order_amount_performance'] = 0.0;
+ $z['linked_pay_amount_performance'] = 0.0;
+
+ return $z;
}
/**
diff --git a/server/app/adminapi/logic/dept/DeptLogic.php b/server/app/adminapi/logic/dept/DeptLogic.php
index fec6df24..2c918011 100644
--- a/server/app/adminapi/logic/dept/DeptLogic.php
+++ b/server/app/adminapi/logic/dept/DeptLogic.php
@@ -14,6 +14,7 @@
namespace app\adminapi\logic\dept;
+use app\adminapi\logic\stats\YejiStatsLogic;
use app\common\logic\BaseLogic;
use app\common\model\auth\Admin;
use app\common\model\auth\AdminDept;
@@ -212,8 +213,8 @@ class DeptLogic extends BaseLogic
->join('tcm_diagnosis dg', 'dg.id = o.diagnosis_id AND dg.delete_time IS NULL', 'INNER')
->whereNull('o.delete_time')
->where('o.diagnosis_id', '>', 0)
- ->where('dg.assistant_id', '>', 0)
- ->whereRaw('NOT (o.fulfillment_status <=> ?)', [4]);
+ ->where('dg.assistant_id', '>', 0);
+ YejiStatsLogic::applyPrescriptionOrderNotCancelledForPerformanceQuery($q, 'o');
if ($orderStartTs !== null && $orderEndTs !== null) {
$q->where('o.create_time', 'between', [$orderStartTs, $orderEndTs]);
}
diff --git a/server/app/adminapi/logic/stats/ConversionLogic.php b/server/app/adminapi/logic/stats/ConversionLogic.php
index 45e44247..8531f623 100644
--- a/server/app/adminapi/logic/stats/ConversionLogic.php
+++ b/server/app/adminapi/logic/stats/ConversionLogic.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace app\adminapi\logic\stats;
use app\adminapi\logic\dept\DeptLogic;
+use app\adminapi\logic\stats\YejiStatsLogic;
use app\adminapi\logic\tcm\DiagnosisLogic;
use app\common\model\finance\AccountCost;
use app\common\service\DataScope\DataScopeService;
@@ -1041,8 +1042,9 @@ class ConversionLogic
->alias('po')
->leftJoin('tcm_diagnosis dg', 'dg.id = po.diagnosis_id')
->whereNull('po.delete_time')
- ->where('po.create_time', 'between', [$startTimestamp, $endTimestamp])
- ->where('po.fulfillment_status', '<>', 4)
+ ->where('po.create_time', 'between', [$startTimestamp, $endTimestamp]);
+ YejiStatsLogic::applyPrescriptionOrderNotCancelledForPerformanceQuery($businessQuery, 'po');
+ $businessQuery
->fieldRaw("{$businessSourceExpr} AS source_admin_id, SUM(po.amount) AS total_amount")
->group($businessSourceExpr);
diff --git a/server/app/adminapi/logic/stats/DoctorDailyStatsLogic.php b/server/app/adminapi/logic/stats/DoctorDailyStatsLogic.php
index c85073af..e1c03b6b 100644
--- a/server/app/adminapi/logic/stats/DoctorDailyStatsLogic.php
+++ b/server/app/adminapi/logic/stats/DoctorDailyStatsLogic.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace app\adminapi\logic\stats;
+use app\adminapi\logic\stats\YejiStatsLogic;
use app\common\model\auth\Admin;
use app\common\service\DataScope\DataScopeService;
use think\facade\Db;
@@ -14,7 +15,7 @@ use think\facade\Db;
* 医生范围:admin_role.role_id = 1 且管理员 未软删(delete_time 为空);再按账号「数据范围」收窄。
*
* - 系统/手动开方:tcm_prescription.prescription_date ∈ [start,end];渠道/标签与业绩渠道列同源 EXISTS / 诊单标签
- * - 成交:订单 create_time、排除履约(4),按处方 creator_id;渠道/标签同 sumPerformance 口径
+ * - 成交:订单 create_time、排除履约 4/9/10,按处方 creator_id;渠道/标签同 sumPerformance 口径
* - 挂号:appointment_date ∈ [start,end];选渠道时挂号 channels 命中字典值;标签渠道时 patient_id ∈ 标签诊单集
*/
class DoctorDailyStatsLogic
@@ -300,9 +301,9 @@ class DoctorDailyStatsLogic
->alias('o')
->join('tcm_prescription rx', 'rx.id = o.prescription_id AND rx.delete_time IS NULL', 'INNER')
->whereNull('o.delete_time')
- ->whereBetween('o.create_time', [$startTs, $endTs])
- ->whereRaw('NOT (o.fulfillment_status <=> 4)')
- ->whereIn('rx.creator_id', $doctorIds)
+ ->whereBetween('o.create_time', [$startTs, $endTs]);
+ YejiStatsLogic::applyPrescriptionOrderNotCancelledForPerformanceQuery($q, 'o');
+ $q->whereIn('rx.creator_id', $doctorIds)
->where('o.diagnosis_id', '>', 0);
$normCh = self::normalizeAppointmentChannelInts($appointmentChannelValues);
diff --git a/server/app/adminapi/logic/stats/YejiStatsLogic.php b/server/app/adminapi/logic/stats/YejiStatsLogic.php
index 9864e76d..97262d35 100644
--- a/server/app/adminapi/logic/stats/YejiStatsLogic.php
+++ b/server/app/adminapi/logic/stats/YejiStatsLogic.php
@@ -22,15 +22,15 @@ use think\facade\Db;
* · **二中心及其组织下级在选定渠道下计 0**(与进线一致)
* - 复诊统计 = 仅**名称含「二中心」的部门及其组织架构下级**:区间内业务单按患者 create_time 排序,第 1 笔不计,第 2 笔起分列「复诊2」「复诊3」…;口径同业绩单条件;按**订单创建人**所在部门在组织树内 rollup(含下级),与排行榜复诊一致
* - 已完成挂号 = status=3 挂号条数(appointment_date),列「已完成挂号」;部门归属:有效医助→接诊医生→展示行映射。
- * - 接诊诊单 = 列名对应**业务订单条数**(非取消、create_time 区间),按**订单创建人**人事部门落在该行子树即计入;多行命中时取**最深的展示部门行**。与处方列表按 assistant_dept_id 筛选不同(列表保留旧口径:诊单医助 ∪ 创建人)。
- * - {渠道}成交单 = 选定渠道时与 {渠道}业绩同源:区间内 **非取消业务订单条数**(仅剔除 fulfillment_status=4;NULL 与其它状态计入,按 create_time 归日);dict(channels) 与 tag 口径均按订单计(按订单创建人归属),非挂号条数。
- * - 合计业绩 = 区间内业务订单 amount 求和(排除已取消(4) 与软删),**部门列**与「接诊诊单」一致:**订单创建人**人事部门落在该展示行子树则计入,多行命中取最深展示行;与排行榜诊金同口径。
+ * - 接诊诊单 = 列名对应**业务订单条数**(剔除履约 4/9/10、create_time 区间),按**订单创建人**人事部门落在该行子树即计入;多行命中时取**最深的展示部门行**。与处方列表按 assistant_dept_id 筛选不同(列表保留旧口径:诊单医助 ∪ 创建人)。
+ * - {渠道}成交单 = 选定渠道时与 {渠道}业绩同源:区间内 **计入业绩的业务订单条数**(剔除 fulfillment_status ∈ {4,9,10};NULL 与其它状态计入,按 create_time 归日);dict(channels) 与 tag 口径均按订单计(按订单创建人归属),非挂号条数。
+ * - 合计业绩 = 区间内业务订单 amount 求和(排除已取消/拒收/退款(4,9,10) 与软删),**部门列**与「接诊诊单」一致:**订单创建人**人事部门落在该展示行子树则计入,多行命中取最深展示行;与排行榜诊金同口径。
* 无法归入任一展示部门的金额单列「未归属中心」行;**底栏「合计业绩」「接诊诊单」= 当前表内各行业绩列相加**(含未归属行),与弹窗摊分口径一致。
* · 时间窗与 stats_order_amount_performance 一致;列表若另有患者筛选或账号数据域收窄,金额可能仍不同
* · 选定渠道时「{渠道}业绩」列仍按比例展示;合计列全量与渠道列含未归属补差;**二中心及其组织下级展示部门不参与该渠道进线/业绩/成交单/ROI 分子**(与同模块 DeptLogic::getErCenterSubtreeDeptIdSet 一致)
- * - {渠道}业绩 = 与「处方订单列表」业绩同口径(≠4):按 **o.create_time** 落入区间,按**订单创建人**归部门;
+ * - {渠道}业绩 = 与「处方订单列表」业绩同口径(剔除 4/9/10):按 **o.create_time** 落入区间,按**订单创建人**归部门;
* 渠道判定仅用 EXISTS:"患者有任一已完成挂号 (status=3) 且 channels 命中字典",**不限挂号时点**;无字典映射时回退 tag/creator 口径
- * - 成交单数 = 区间内非取消业务订单 **条数**;按**订单创建人**映射到「中心」;无法归属计入「未归属中心」;合计=全表 COUNT。
+ * - 成交单数 = 区间内计入业绩的业务订单 **条数**(剔除 4/9/10);按**订单创建人**映射到「中心」;无法归属计入「未归属中心」;合计=全表 COUNT。
* - 投放成本 = zyt_account_cost 在日期区间内 **amount** 合计(选定渠道时 `media_channel_code = channel_code` 过滤)。
* · **各部门行投放成本** = 总投放成本 × (该部门进线 ÷ 各展示部门进线合计)。相当于先算 **单进线成本** = 总成本 ÷ 总进线,再乘部门进线。
* · **二中心及其组织下级**不参与均摊投放成本(进线分摊分母对其计 0,与选定渠道下进线清零规则独立);其余部门按进线占比分摊。合计行成本 = 账务汇总总额。
@@ -53,6 +53,9 @@ use think\facade\Db;
*/
class YejiStatsLogic
{
+ /** 不计入业绩的履约状态:已取消(4)、拒收(9)、退款(10);NULL 与其它值仍计业绩 */
+ public const PRESCRIPTION_ORDER_FULFILLMENT_EXCLUDED_FROM_PERFORMANCE = [4, 9, 10];
+
/**
* overview / assistantLeaderboards 共用的筛选与时间上下文。
*
@@ -557,7 +560,7 @@ class YejiStatsLogic
. ($dataScopeRestricted
? ' 当前账号受数据范围限制时,底栏合计业绩与列表同域。'
: '');
- $baseNoteNoChannel = '业绩口径:除履约已取消(4)外全部计入。部门行「合计业绩」「接诊诊单」「复诊」均按**订单创建人人事部门 ∈ 该展示行子树**归属(多行命中取最深展示行),与医助排行榜诊金一致;与处方订单列表 assistant_dept_id 默认筛选(诊单医助 ∪ 创建人)不同——侧栏点击会自动收窄为创建人口径。多表「本月」区间为当月1日至服务器当日。**投放成本**按进线占比分摊,**二中心子树展示部门进线在分摊中计 0**(与部门 ErCenter 子树一致)。';
+ $baseNoteNoChannel = '业绩口径:剔除履约已取消(4)、拒收(9)、退款(10),其余(含 NULL)计入。部门行「合计业绩」「接诊诊单」「复诊」均按**订单创建人人事部门 ∈ 该展示行子树**归属(多行命中取最深展示行),与医助排行榜诊金一致;与处方订单列表 assistant_dept_id 默认筛选(诊单医助 ∪ 创建人)不同——侧栏点击会自动收窄为创建人口径。多表「本月」区间为当月1日至服务器当日。**投放成本**按进线占比分摊,**二中心子树展示部门进线在分摊中计 0**(与部门 ErCenter 子树一致)。';
return [
'start_date' => $startDate,
@@ -567,9 +570,9 @@ class YejiStatsLogic
'channel_name' => $channelInfo ? (string) $channelInfo['channel_name'] : '',
'channel_group' => $channelInfo ? (string) $channelInfo['source_group_name'] : '',
'channel_filter_note' => $channelCode !== ''
- ? ('{渠道}业绩:按订单 create_time 归日、仅剔除履约已取消(4)(NULL 与其它状态均计入);渠道判定用"该患者是否有挂号 channels 命中 dict + status=3"(不限挂号时点)。部门归属按**订单创建人**人事部门 ∈ 子树。'
- . ' {渠道}成交单数:与{渠道}业绩同源,仅剔除已取消(4)的订单条数(非挂号条数),按订单创建人归部门。'
- . ' 「已完成挂号」列同 admin 挂号已完成列表;「接诊诊单」列为业务订单条数(非取消),同样按订单创建人归部门。**二中心及其组织下级**(与部门模块 ErCenter 子树一致)**不计入**该渠道进线、业绩与成交单。**投放成本**按各部门进线条数占展示部门进线合计的比例分摊,**二中心子树进线在分摊中计 0**(未选渠道时仍显示进线数,仅不参与均摊)。ROI = 渠道业绩(不含二中心子树)÷ 总投放成本。进线按所选标签客户精确过滤。'
+ ? ('{渠道}业绩:按订单 create_time 归日、剔除履约 4/9/10(NULL 与其它状态均计入);渠道判定用"该患者是否有挂号 channels 命中 dict + status=3"(不限挂号时点)。部门归属按**订单创建人**人事部门 ∈ 子树。'
+ . ' {渠道}成交单数:与{渠道}业绩同源,剔除 4/9/10 的订单条数(非挂号条数),按订单创建人归部门。'
+ . ' 「已完成挂号」列同 admin 挂号已完成列表;「接诊诊单」列为业务订单条数(同业绩剔除口径),同样按订单创建人归部门。**二中心及其组织下级**(与部门模块 ErCenter 子树一致)**不计入**该渠道进线、业绩与成交单。**投放成本**按各部门进线条数占展示部门进线合计的比例分摊,**二中心子树进线在分摊中计 0**(未选渠道时仍显示进线数,仅不参与均摊)。ROI = 渠道业绩(不含二中心子树)÷ 总投放成本。进线按所选标签客户精确过滤。'
. ($tagFallback
? ' ⚠ 当前业务尚未回填 tcm_diagnosis.external_userid 与 user_auth.unionid,已自动退化为「该标签客户的跟进医助 → 区间内接诊/业绩」口径,仅用于趋势观察。'
: '')
@@ -1799,7 +1802,7 @@ class YejiStatsLogic
/**
* 接诊诊单聚合:返回两组按部门归属的接诊计数。
* - all: 区间内 status=3 挂号条数按部门归属(与列表计数一致),不含「仅 role_id=2」等额外限制。
- * - scoped: 选定渠道时「{渠道}成交单」= **非取消业务订单条数**(与 {渠道}业绩同源):dict 下 EXISTS 与侧栏一致(channels ∪ channel_source);有展示行时按列表部门摊行;否则走归因折叠。
+ * - scoped: 选定渠道时「{渠道}成交单」= **计入业绩的业务订单条数**(与 {渠道}业绩同源,剔除 4/9/10):dict 下 EXISTS 与侧栏一致(channels ∪ channel_source);有展示行时按列表部门摊行;否则走归因折叠。
* 未选渠道时 scoped == all(不再额外查一次)。
*
* @param int[] $appointmentChannelValues dict_data value 整型列表(通常 1=自媒体1)
@@ -2996,7 +2999,7 @@ class YejiStatsLogic
}
/**
- * 与 sumPerformanceByAdmin 对应:按订单创建人统计成交单数(非取消业务订单条数),全量/渠道分支一致。
+ * 与 sumPerformanceByAdmin 对应:按订单创建人统计成交单数(剔除履约 4/9/10),全量/渠道分支一致。
*
* @param int[] $appointmentChannelValues
*
@@ -3296,18 +3299,19 @@ class YejiStatsLogic
}
/**
- * 业绩/成交单统计:仅排除履约已取消 fulfillment_status = 4,其余(含 NULL 与任意其它状态)全部计入。
- * 与看板「合计业绩」、处方列表业绩 extend 口径一致;勿用 `<> 4`,以免误排除 NULL。
+ * 业绩/成交单统计:排除履约「已取消/拒收/退款」(fulfillment_status 4、9、10),NULL 与其它状态计入。
+ * 与看板「合计业绩」、处方列表业绩 extend 口径一致。
*
* @param \think\db\BaseQuery|\think\Model $query Db::name / Model::where 等查询实例
* @param string $tableAlias 主表别名(如 Db 侧 `alias('o')` 传 `o`);无别名时传空字符串
*/
public static function applyPrescriptionOrderNotCancelledForPerformanceQuery($query, string $tableAlias = ''): void
{
+ $inList = implode(',', array_map(static fn (int $v): string => (string) $v, self::PRESCRIPTION_ORDER_FULFILLMENT_EXCLUDED_FROM_PERFORMANCE));
if ($tableAlias !== '') {
- $query->whereRaw("NOT ({$tableAlias}.fulfillment_status <=> ?)", [4]);
+ $query->whereRaw("({$tableAlias}.fulfillment_status IS NULL OR {$tableAlias}.fulfillment_status NOT IN ({$inList}))");
} else {
- $query->whereRaw('NOT (fulfillment_status <=> ?)', [4]);
+ $query->whereRaw("(fulfillment_status IS NULL OR fulfillment_status NOT IN ({$inList}))");
}
}
@@ -3609,7 +3613,7 @@ class YejiStatsLogic
/**
* 业绩聚合:返回两组按部门归属的业绩金额。
* 口径与 /tcm.prescriptionOrder/lists 的 stats_*_performance 完全一致:
- * 业绩 = SUM(amount) WHERE NOT (fulfillment_status <=> 4) AND delete_time IS NULL
+ * 业绩 = SUM(amount) WHERE fulfillment_status 为 NULL 或 NOT IN (4,9,10),且 delete_time IS NULL
*
* - all: 部门全量业绩(不限渠道)。用于「合计业绩」列展示。
* - scoped: 「{渠道}业绩」:患者渠道 EXISTS 与侧栏/抽屉一致(channels ∪ channel_source);有展示行时部门列与「合计业绩」同为列表部门摊行。
@@ -3725,7 +3729,7 @@ class YejiStatsLogic
}
/**
- * 渠道业绩:与「处方订单列表」业绩完全同口径 —— 按 o.create_time 归日、仅剔除已取消(4)(含 NULL);
+ * 渠道业绩:与「处方订单列表」业绩完全同口径 —— 按 o.create_time 归日、剔除履约 4/9/10(NULL 与其它状态计入);
* EXISTS 仅用于"该患者属于此渠道"的判定(有任一挂号 channels 命中 + status=3,**不限挂号时点**);
* 部门归属按业绩归属表达式(订单创建人 o.creator_id)。
*/
@@ -3812,7 +3816,7 @@ class YejiStatsLogic
/**
* 单次业绩聚合:与 aggregatePerformanceAmountsByAdmin / aggregateDealOrderCount 同源 ——
* INNER 诊单,按业绩归属医助 SUM(amount) 后映射到展示「中心」。
- * WHERE: delete_time IS NULL、NOT(fulfillment <=> 4)、create_time 区间、diagnosis_id>0、归属医助>0;
+ * WHERE: delete_time IS NULL、fulfillment ∈ (NULL ∪ 除 4/9/10 外)、create_time 区间、diagnosis_id>0、归属医助>0;
* 可选 tag:diagnosis_id IN、归属医助 IN。
*
* @return array
@@ -3859,7 +3863,7 @@ class YejiStatsLogic
}
/**
- * 全量成交单数:与 sumGlobalPerformanceAmount 同一 WHERE/COUNT(非取消订单条数)。
+ * 全量成交单数:与 sumGlobalPerformanceAmount 同一 WHERE/COUNT(剔除 4/9/10)。
*/
private static function countGlobalDealOrders(int $startTs, int $endTs): int
{
@@ -4516,7 +4520,7 @@ class YejiStatsLogic
'end_date' => (string) $c['endDate'],
'channel_code' => (string) $c['channelCode'],
'rows' => $rows,
- 'note' => '与看板「未归属中心」补差一致:按订单 create_time、剔除履约已取消(4);**订单创建人**人事部门无法映射到任一展示「中心」计入各行;其余(无诊单/创建人为空)汇总在首行。账号数据权限下仅列出可见医助。',
+ 'note' => '与看板「未归属中心」补差一致:按订单 create_time、剔除履约 4/9/10;**订单创建人**人事部门无法映射到任一展示「中心」计入各行;其余(无诊单/创建人为空)汇总在首行。账号数据权限下仅列出可见医助。',
];
}
diff --git a/server/app/command/ExpressAutoUpdate.php b/server/app/command/ExpressAutoUpdate.php
index 16e960b9..90a50f92 100644
--- a/server/app/command/ExpressAutoUpdate.php
+++ b/server/app/command/ExpressAutoUpdate.php
@@ -36,8 +36,8 @@ class ExpressAutoUpdate extends Command
$startTime = microtime(true);
try {
- $result = ExpressTrackingService::autoUpdateBatch(500);
- $recon = ExpressTrackingService::reconcileAssistantReleaseForShippedPrescriptionOrders(500);
+ $result = ExpressTrackingService::autoUpdateBatch(1000);
+ $recon = ExpressTrackingService::reconcileAssistantReleaseForShippedPrescriptionOrders(1000);
$duration = round(microtime(true) - $startTime, 2);