更新
This commit is contained in:
@@ -38,6 +38,18 @@
|
|||||||
<el-radio-button label="not_passed">未通过</el-radio-button>
|
<el-radio-button label="not_passed">未通过</el-radio-button>
|
||||||
<el-radio-button label="rejected">驳回</el-radio-button>
|
<el-radio-button label="rejected">驳回</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
|
<span class="pl-toolbar__sep" aria-hidden="true" />
|
||||||
|
<span class="pl-toolbar__k" title="与列表「来源」列一致:系统代开 / 手工">来源</span>
|
||||||
|
<el-radio-group
|
||||||
|
v-model="formData.source_filter"
|
||||||
|
size="small"
|
||||||
|
class="pl-toolbar__rg pl-toolbar__rg--source"
|
||||||
|
@change="onSourceFilterChange"
|
||||||
|
>
|
||||||
|
<el-radio-button label="all">全部</el-radio-button>
|
||||||
|
<el-radio-button label="manual">手工</el-radio-button>
|
||||||
|
<el-radio-button label="system">系统代开</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-toolbar__line pl-toolbar__line--second">
|
<div class="pl-toolbar__line pl-toolbar__line--second">
|
||||||
<el-form :model="formData" inline class="pl-toolbar__form">
|
<el-form :model="formData" inline class="pl-toolbar__form">
|
||||||
@@ -189,7 +201,7 @@
|
|||||||
查看
|
查看
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="!Number(row.has_prescription_order)"
|
v-if="!Number(row.has_prescription_order) && Number(row.void_status) !== 1"
|
||||||
type="success"
|
type="success"
|
||||||
link
|
link
|
||||||
v-perms="['tcm.prescriptionOrder/create']"
|
v-perms="['tcm.prescriptionOrder/create']"
|
||||||
@@ -444,6 +456,15 @@
|
|||||||
class="mb-4"
|
class="mb-4"
|
||||||
:title="editReviveHint"
|
:title="editReviveHint"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
v-if="Number(editForm.diagnosis_id) > 0"
|
||||||
|
class="mb-3 flex flex-wrap items-center gap-2 pl-[120px]"
|
||||||
|
>
|
||||||
|
<el-button type="primary" size="small" @click="openDiagnosisPatientDetail">
|
||||||
|
查看患者诊单详情
|
||||||
|
</el-button>
|
||||||
|
<span class="text-xs text-gray-400">关联诊单 #{{ editForm.diagnosis_id }}</span>
|
||||||
|
</div>
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="患者姓名" prop="patient_name">
|
<el-form-item label="患者姓名" prop="patient_name">
|
||||||
@@ -1234,6 +1255,9 @@
|
|||||||
<el-button type="danger" :loading="auditLoading" @click="submitAudit('reject')">驳回处方</el-button>
|
<el-button type="danger" :loading="auditLoading" @click="submitAudit('reject')">驳回处方</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 与 tcm/diagnosis/edit 同一套诊单界面(只读 + 全部分页签) -->
|
||||||
|
<TcmDiagnosisEditView ref="diagnosisViewRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -1259,9 +1283,11 @@ import feedback from '@/utils/feedback'
|
|||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import DaterangePicker from '@/components/daterange-picker/index.vue'
|
import DaterangePicker from '@/components/daterange-picker/index.vue'
|
||||||
import { Search } from '@element-plus/icons-vue'
|
import { Search } from '@element-plus/icons-vue'
|
||||||
import { computed, onMounted, reactive, ref, watch, nextTick } from 'vue'
|
import { computed, onMounted, reactive, ref, watch, nextTick, defineAsyncComponent } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const TcmDiagnosisEditView = defineAsyncComponent(() => import('@/views/tcm/diagnosis/edit.vue'))
|
||||||
|
|
||||||
/** 与 server/config/project.php prescription_audit_roles 保持一致 */
|
/** 与 server/config/project.php prescription_audit_roles 保持一致 */
|
||||||
const PRESCRIPTION_AUDIT_ROLE_IDS = [0, 3]
|
const PRESCRIPTION_AUDIT_ROLE_IDS = [0, 3]
|
||||||
|
|
||||||
@@ -1289,6 +1315,8 @@ const formData = reactive({
|
|||||||
creator_ids: [] as number[],
|
creator_ids: [] as number[],
|
||||||
/** all | passed | not_passed | pending | rejected(all 表示不限,接口传参会转为空) */
|
/** all | passed | not_passed | pending | rejected(all 表示不限,接口传参会转为空) */
|
||||||
audit_filter: 'all' as string,
|
audit_filter: 'all' as string,
|
||||||
|
/** all | manual | system — 与 is_system_auto:手工(0) / 系统代开(1) */
|
||||||
|
source_filter: 'all' as string,
|
||||||
start_time: '',
|
start_time: '',
|
||||||
end_time: ''
|
end_time: ''
|
||||||
})
|
})
|
||||||
@@ -1621,13 +1649,13 @@ function resetCreateOrderForm() {
|
|||||||
createOrderFormRef.value?.clearValidate()
|
createOrderFormRef.value?.clearValidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 仅按定金门槛抬升「总金额」,不跟已选支付单合计联动(由用户自填) */
|
||||||
function syncCreateOrderAmountFromRules() {
|
function syncCreateOrderAmountFromRules() {
|
||||||
const min = createOrderDepositMin.value
|
const min = createOrderDepositMin.value
|
||||||
const paid = createOrderLinkedPaidTotal.value
|
if (min <= 0) return
|
||||||
const floor = min > 0 ? Math.round(min * 100) / 100 : 0
|
const need = Math.round(min * 100) / 100
|
||||||
const need = min > 0 ? Math.max(paid, floor) : paid > 0 ? paid : 0
|
if (Number(createOrderForm.amount) < need) {
|
||||||
if (need > 0 && Number(createOrderForm.amount) < need) {
|
createOrderForm.amount = need
|
||||||
createOrderForm.amount = Math.round(need * 100) / 100
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1664,15 +1692,11 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
|
||||||
() => [...createOrderForm.pay_order_ids],
|
|
||||||
() => {
|
|
||||||
syncCreateOrderAmountFromRules()
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
async function openCreateOrderFromPrescription(row: any) {
|
async function openCreateOrderFromPrescription(row: any) {
|
||||||
|
if (Number(row?.void_status) === 1) {
|
||||||
|
feedback.msgWarning('已作废处方不可创建业务订单')
|
||||||
|
return
|
||||||
|
}
|
||||||
resetCreateOrderForm()
|
resetCreateOrderForm()
|
||||||
createOrderPrescription.value = row
|
createOrderPrescription.value = row
|
||||||
const did = Number(row.diagnosis_id) || 0
|
const did = Number(row.diagnosis_id) || 0
|
||||||
@@ -1889,6 +1913,19 @@ const editForm = reactive({
|
|||||||
times_per_day: 2
|
times_per_day: 2
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const diagnosisViewRef = ref<{
|
||||||
|
openViewOnly: (id: number) => Promise<void>
|
||||||
|
} | null>(null)
|
||||||
|
|
||||||
|
function openDiagnosisPatientDetail() {
|
||||||
|
const id = Number(editForm.diagnosis_id)
|
||||||
|
if (!id) {
|
||||||
|
feedback.msgWarning('本处方未关联诊单,无法查看诊单患者信息')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
void diagnosisViewRef.value?.openViewOnly(id)
|
||||||
|
}
|
||||||
|
|
||||||
// 标志:是否正在加载数据(防止watch函数覆盖已加载的值)
|
// 标志:是否正在加载数据(防止watch函数覆盖已加载的值)
|
||||||
const isLoadingData = ref(false)
|
const isLoadingData = ref(false)
|
||||||
|
|
||||||
@@ -1940,10 +1977,12 @@ const rules: FormRules = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchPrescriptionLists(params: Record<string, unknown>) {
|
async function fetchPrescriptionLists(params: Record<string, unknown>) {
|
||||||
const { audit_filter, creator_ids, ...rest } = params
|
const { audit_filter, source_filter, creator_ids, ...rest } = params
|
||||||
const payload: Record<string, unknown> = {
|
const payload: Record<string, unknown> = {
|
||||||
...rest,
|
...rest,
|
||||||
audit_filter: audit_filter === 'all' || audit_filter === '' ? '' : audit_filter
|
audit_filter: audit_filter === 'all' || audit_filter === '' ? '' : audit_filter,
|
||||||
|
source_filter:
|
||||||
|
source_filter === 'all' || source_filter === '' || source_filter === undefined ? '' : source_filter
|
||||||
}
|
}
|
||||||
if (Array.isArray(creator_ids) && creator_ids.length > 0) {
|
if (Array.isArray(creator_ids) && creator_ids.length > 0) {
|
||||||
payload.creator_ids = creator_ids
|
payload.creator_ids = creator_ids
|
||||||
@@ -1993,6 +2032,10 @@ function onAuditFilterChange() {
|
|||||||
resetPage()
|
resetPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSourceFilterChange() {
|
||||||
|
resetPage()
|
||||||
|
}
|
||||||
|
|
||||||
let syncingDateTab = false
|
let syncingDateTab = false
|
||||||
watch(
|
watch(
|
||||||
() => [formData.start_time, formData.end_time],
|
() => [formData.start_time, formData.end_time],
|
||||||
@@ -2039,6 +2082,7 @@ function resetParams() {
|
|||||||
formData.patient_name = ''
|
formData.patient_name = ''
|
||||||
formData.creator_ids = []
|
formData.creator_ids = []
|
||||||
formData.audit_filter = 'all'
|
formData.audit_filter = 'all'
|
||||||
|
formData.source_filter = 'all'
|
||||||
formData.start_time = ''
|
formData.start_time = ''
|
||||||
formData.end_time = ''
|
formData.end_time = ''
|
||||||
syncingDateTab = false
|
syncingDateTab = false
|
||||||
|
|||||||
@@ -62,7 +62,77 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card class="!border-none po-filter-card shadow-sm" shadow="never">
|
<el-card class="!border-none po-filter-card shadow-sm" shadow="never">
|
||||||
|
<div
|
||||||
|
class="po-list-stats grid grid-cols-1 sm:grid-cols-2 gap-4 mb-5"
|
||||||
|
v-loading="pager.loading"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="po-stat-surface po-stat-surface--order relative overflow-hidden rounded-xl border border-slate-200/90 px-5 py-4 shadow-sm"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="po-stat-deco po-stat-deco--o pointer-events-none absolute -right-6 -top-8 h-28 w-28 rounded-full opacity-40"
|
||||||
|
/>
|
||||||
|
<div class="relative flex items-start justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-2 text-slate-500 text-xs font-medium">
|
||||||
|
<el-icon :size="16" class="text-sky-500"><Calendar /></el-icon>
|
||||||
|
{{ listStats.orderHeading }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 text-2xl sm:text-[26px] font-bold text-slate-800 tracking-tight tabular-nums">
|
||||||
|
¥{{ listStats.orderStr }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="stat-icon-wrap h-10 w-10 shrink-0 rounded-lg bg-sky-500/10 flex items-center justify-center text-sky-600"
|
||||||
|
>
|
||||||
|
<el-icon :size="20"><Document /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="relative mt-2 text-xs text-slate-500 leading-relaxed">
|
||||||
|
{{ listStats.periodLine }}
|
||||||
|
</p>
|
||||||
|
<p class="relative mt-1.5 text-xs text-slate-400 leading-relaxed">
|
||||||
|
{{ listStats.scopeHint }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="po-stat-surface po-stat-surface--pay relative overflow-hidden rounded-xl border border-slate-200/90 px-5 py-4 shadow-sm"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="po-stat-deco po-stat-deco--p pointer-events-none absolute -right-6 -top-8 h-28 w-28 rounded-full opacity-40"
|
||||||
|
/>
|
||||||
|
<div class="relative flex items-start justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-2 text-slate-500 text-xs font-medium">
|
||||||
|
<el-icon :size="16" class="text-emerald-500"><LinkIcon /></el-icon>
|
||||||
|
{{ listStats.payHeading }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 text-2xl sm:text-[26px] font-bold text-slate-800 tracking-tight tabular-nums">
|
||||||
|
¥{{ listStats.payStr }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="stat-icon-wrap h-10 w-10 shrink-0 rounded-lg bg-emerald-500/10 flex items-center justify-center text-emerald-600"
|
||||||
|
>
|
||||||
|
<el-icon :size="20"><Wallet /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="relative mt-2 text-xs text-slate-500 leading-relaxed">
|
||||||
|
{{ listStats.periodLine }}
|
||||||
|
</p>
|
||||||
|
<p class="relative mt-1.5 text-xs text-slate-400 leading-relaxed">
|
||||||
|
所选时间内创建订单所关联的支付单金额(与「实付(关联)」含义一致;不含创建时间时默认今日)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<el-form class="ls-form" :model="queryParams" inline>
|
<el-form class="ls-form" :model="queryParams" inline>
|
||||||
|
<el-form-item class="w-[360px] min-w-[240px] max-w-full" label="创建时间">
|
||||||
|
<DaterangePicker
|
||||||
|
v-model:startTime="queryParams.start_time"
|
||||||
|
v-model:endTime="queryParams.end_time"
|
||||||
|
class="!w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item class="w-[260px]" label="订单号">
|
<el-form-item class="w-[260px]" label="订单号">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.order_no"
|
v-model="queryParams.order_no"
|
||||||
@@ -429,19 +499,30 @@
|
|||||||
|
|
||||||
<!-- 顶部数据概览卡片 -->
|
<!-- 顶部数据概览卡片 -->
|
||||||
<el-row :gutter="16" class="mb-5">
|
<el-row :gutter="16" class="mb-5">
|
||||||
<el-col :xs="12" :sm="8">
|
<el-col :xs="12" :sm="6">
|
||||||
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
<div class="stat-title text-gray-500 text-xs mb-1">总金额</div>
|
<div class="stat-title text-gray-500 text-xs mb-1">总金额</div>
|
||||||
<div class="stat-value text-red-500 font-bold text-xl">¥{{ detailData.amount }}</div>
|
<div class="stat-value text-red-500 font-bold text-xl">¥{{ detailData.amount }}</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="12" :sm="8">
|
<el-col :xs="12" :sm="6">
|
||||||
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
<div class="stat-title text-gray-500 text-xs mb-1">已付总额</div>
|
<div class="stat-title text-gray-500 text-xs mb-1">已付总额</div>
|
||||||
<div class="stat-value text-green-600 font-bold text-xl">¥{{ detailData.linked_pay_paid_total || 0 }}</div>
|
<div class="stat-value text-green-600 font-bold text-xl">¥{{ detailData.linked_pay_paid_total || 0 }}</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="12" :sm="8">
|
<el-col :xs="12" :sm="6">
|
||||||
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
|
<div class="stat-title text-gray-500 text-xs mb-1">需代收</div>
|
||||||
|
<div
|
||||||
|
class="stat-value font-bold text-xl"
|
||||||
|
:class="detailAgencyToCollect > 0 ? 'text-amber-600' : 'text-gray-500'"
|
||||||
|
>
|
||||||
|
¥{{ formatMoney(detailAgencyToCollect) }}
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :xs="12" :sm="6">
|
||||||
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
<div class="stat-title text-gray-500 text-xs mb-1">已付笔数</div>
|
<div class="stat-title text-gray-500 text-xs mb-1">已付笔数</div>
|
||||||
<div class="stat-value text-primary font-bold text-xl">{{ detailLinkedPayOrders.length }} 笔</div>
|
<div class="stat-value text-primary font-bold text-xl">{{ detailLinkedPayOrders.length }} 笔</div>
|
||||||
@@ -1767,7 +1848,8 @@
|
|||||||
|
|
||||||
<script lang="ts" setup name="prescriptionOrderList">
|
<script lang="ts" setup name="prescriptionOrderList">
|
||||||
import { computed, onMounted, reactive, ref, nextTick, watch } from 'vue'
|
import { computed, onMounted, reactive, ref, nextTick, watch } from 'vue'
|
||||||
import { Refresh, Loading, ArrowDown, InfoFilled, Search } from '@element-plus/icons-vue'
|
import { Refresh, Loading, ArrowDown, InfoFilled, Search, Calendar, Document, Link as LinkIcon, Wallet } from '@element-plus/icons-vue'
|
||||||
|
import DaterangePicker from '@/components/daterange-picker/index.vue'
|
||||||
import {
|
import {
|
||||||
prescriptionOrderAuditPayment,
|
prescriptionOrderAuditPayment,
|
||||||
prescriptionOrderAuditPrescription,
|
prescriptionOrderAuditPrescription,
|
||||||
@@ -1938,6 +2020,9 @@ function handleFulfillmentStatusTabClick(value: number | '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
|
/** 创建时间区间(与列表筛选一致;不影响「今日」统计) */
|
||||||
|
start_time: '' as string,
|
||||||
|
end_time: '' as string,
|
||||||
order_no: '',
|
order_no: '',
|
||||||
prescription_id: '' as string | number,
|
prescription_id: '' as string | number,
|
||||||
/** 诊单患者姓名/手机号(后台关联 diagnosis) */
|
/** 诊单患者姓名/手机号(后台关联 diagnosis) */
|
||||||
@@ -2001,6 +2086,10 @@ async function fetchLists(params: Record<string, unknown>) {
|
|||||||
}
|
}
|
||||||
if (!p.order_no) delete p.order_no
|
if (!p.order_no) delete p.order_no
|
||||||
if (!String(p.patient_keyword || '').trim()) delete p.patient_keyword
|
if (!String(p.patient_keyword || '').trim()) delete p.patient_keyword
|
||||||
|
if (!String(p.start_time || '').trim() || !String(p.end_time || '').trim()) {
|
||||||
|
delete p.start_time
|
||||||
|
delete p.end_time
|
||||||
|
}
|
||||||
return prescriptionOrderLists(p)
|
return prescriptionOrderLists(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2009,6 +2098,45 @@ const { pager, getLists, resetPage, resetParams } = usePaging({
|
|||||||
params: queryParams
|
params: queryParams
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const listStats = computed(() => {
|
||||||
|
const ex = pager.extend as Record<string, unknown> | undefined
|
||||||
|
const o = ex?.stats_order_amount ?? ex?.today_order_amount
|
||||||
|
const pay = ex?.stats_linked_pay_amount ?? ex?.today_linked_pay_amount
|
||||||
|
const n = (v: unknown) => {
|
||||||
|
const x = Number(v)
|
||||||
|
if (!Number.isFinite(x)) return 0
|
||||||
|
return Math.round(x * 100) / 100
|
||||||
|
}
|
||||||
|
const fmt = (v: number) => v.toFixed(2)
|
||||||
|
const t0 = String(ex?.stats_time_start ?? '').trim()
|
||||||
|
const t1 = String(ex?.stats_time_end ?? '').trim()
|
||||||
|
const useRange = t0 && t1
|
||||||
|
const periodLine = useRange
|
||||||
|
? `统计时间:${t0} ~ ${t1}`
|
||||||
|
: '统计时间:—'
|
||||||
|
const scope = String(ex?.stats_scope ?? 'restricted')
|
||||||
|
let scopeHint = '统计口径:与当前列表数据可见范围一致'
|
||||||
|
if (scope === 'all') {
|
||||||
|
scopeHint =
|
||||||
|
'统计口径:支付单审核白名单角色,按全量订单;与下方筛选项(医生/医助/患者等,不含「创建时间」)一致'
|
||||||
|
} else if (scope === 'assistant') {
|
||||||
|
scopeHint = '统计口径:仅计诊单医助为本人且满足下方其它筛选项的订单'
|
||||||
|
}
|
||||||
|
const hasCustomTime =
|
||||||
|
String(queryParams.start_time || '').trim() !== '' && String(queryParams.end_time || '').trim() !== ''
|
||||||
|
const headPrefix = hasCustomTime ? '区间' : '今日'
|
||||||
|
return {
|
||||||
|
order: n(o),
|
||||||
|
pay: n(pay),
|
||||||
|
orderStr: fmt(n(o)),
|
||||||
|
payStr: fmt(n(pay)),
|
||||||
|
orderHeading: `${headPrefix}业务订单额`,
|
||||||
|
payHeading: `${headPrefix}关联实付`,
|
||||||
|
periodLine,
|
||||||
|
scopeHint
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
showPrescriptionAuditFilter,
|
showPrescriptionAuditFilter,
|
||||||
(show) => {
|
(show) => {
|
||||||
@@ -2021,6 +2149,8 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
|
queryParams.start_time = ''
|
||||||
|
queryParams.end_time = ''
|
||||||
queryParams.order_no = ''
|
queryParams.order_no = ''
|
||||||
queryParams.prescription_id = ''
|
queryParams.prescription_id = ''
|
||||||
queryParams.patient_keyword = ''
|
queryParams.patient_keyword = ''
|
||||||
@@ -2294,6 +2424,15 @@ const detailLinkedPayOrders = computed(() => {
|
|||||||
return Array.isArray(arr) ? arr : []
|
return Array.isArray(arr) ? arr : []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 需代收:业务订单总金额 − 关联已付总额 */
|
||||||
|
const detailAgencyToCollect = computed(() => {
|
||||||
|
const d = detailData.value
|
||||||
|
if (!d) return 0
|
||||||
|
const total = Number(d.amount) || 0
|
||||||
|
const paid = Number(d.linked_pay_paid_total) || 0
|
||||||
|
return Math.round((total - paid) * 100) / 100
|
||||||
|
})
|
||||||
|
|
||||||
const detailUnlinkedPayOrders = ref<any[]>([])
|
const detailUnlinkedPayOrders = ref<any[]>([])
|
||||||
|
|
||||||
async function loadDetailUnlinkedPayOrders(diagnosisId: number, prescriptionOrderId: number, linkedIds: number[]) {
|
async function loadDetailUnlinkedPayOrders(diagnosisId: number, prescriptionOrderId: number, linkedIds: number[]) {
|
||||||
@@ -3612,6 +3751,36 @@ async function downloadPrescriptionSlipPdf() {
|
|||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.po-list-stats {
|
||||||
|
--po-stat-order-a: color-mix(in srgb, var(--el-color-primary) 12%, white);
|
||||||
|
--po-stat-order-b: color-mix(in srgb, var(--el-color-primary) 5%, transparent);
|
||||||
|
--po-stat-pay-a: color-mix(in srgb, #10b981 10%, white);
|
||||||
|
--po-stat-pay-b: color-mix(in srgb, #10b981 4%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.po-stat-surface {
|
||||||
|
transition: box-shadow 0.2s ease, border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.po-stat-surface:hover {
|
||||||
|
box-shadow: 0 10px 28px -12px rgba(15, 23, 42, 0.18);
|
||||||
|
border-color: color-mix(in srgb, var(--el-color-primary) 22%, var(--el-border-color));
|
||||||
|
}
|
||||||
|
.po-stat-surface--order {
|
||||||
|
background: linear-gradient(135deg, var(--po-stat-order-a) 0%, #fff 48%, var(--po-stat-order-b) 100%);
|
||||||
|
}
|
||||||
|
.po-stat-surface--pay {
|
||||||
|
background: linear-gradient(135deg, var(--po-stat-pay-a) 0%, #fff 48%, var(--po-stat-pay-b) 100%);
|
||||||
|
}
|
||||||
|
.po-stat-deco--o {
|
||||||
|
background: radial-gradient(circle, color-mix(in srgb, var(--el-color-primary) 45%, transparent) 0%, transparent 70%);
|
||||||
|
}
|
||||||
|
.po-stat-deco--p {
|
||||||
|
background: radial-gradient(circle, color-mix(in srgb, #10b981 40%, transparent) 0%, transparent 70%);
|
||||||
|
}
|
||||||
|
.stat-icon-wrap {
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
.po-table-card {
|
.po-table-card {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
:title="drawerTitle"
|
:title="drawerTitle"
|
||||||
size="60%"
|
size="60%"
|
||||||
:before-close="handleClose"
|
:before-close="handleClose"
|
||||||
:z-index="1500"
|
:z-index="viewOnly ? 4000 : 1500"
|
||||||
:modal="true"
|
:modal="true"
|
||||||
class="diagnosis-drawer"
|
class="diagnosis-drawer"
|
||||||
>
|
>
|
||||||
@@ -18,6 +18,10 @@
|
|||||||
:rules="formRules"
|
:rules="formRules"
|
||||||
label-width="160px"
|
label-width="160px"
|
||||||
>
|
>
|
||||||
|
<fieldset
|
||||||
|
:disabled="viewOnly"
|
||||||
|
class="diagnosis-view-fieldset border-0 min-w-0 p-0 m-0"
|
||||||
|
>
|
||||||
<!-- 基本信息 -->
|
<!-- 基本信息 -->
|
||||||
<el-divider content-position="left">基本信息</el-divider>
|
<el-divider content-position="left">基本信息</el-divider>
|
||||||
|
|
||||||
@@ -520,6 +524,7 @@
|
|||||||
placeholder="请输入病史补充"
|
placeholder="请输入病史补充"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</fieldset>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
@@ -664,11 +669,11 @@
|
|||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex justify-end gap-3 px-4 pb-4">
|
<div class="flex justify-end gap-3 px-4 pb-4">
|
||||||
<el-button @click="handleClose">取消</el-button>
|
<el-button @click="handleClose">{{ viewOnly ? '关闭' : '取消' }}</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="activeTab === 'basic'"
|
v-if="!viewOnly && activeTab === 'basic'"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleSubmit"
|
@click="handleSubmit"
|
||||||
:loading="submitting"
|
:loading="submitting"
|
||||||
>
|
>
|
||||||
确定
|
确定
|
||||||
@@ -711,6 +716,8 @@ const callRecordPanelRef = ref<{ refresh?: () => void } | null>(null)
|
|||||||
const prescriptionRef = ref()
|
const prescriptionRef = ref()
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
|
/** 只读查看:与 consumers 处方等处「查看诊单」一致,全部 Tab 仅展示不可保存 */
|
||||||
|
const viewOnly = ref(false)
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const mode = ref('add')
|
const mode = ref('add')
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
@@ -721,7 +728,10 @@ const hasPhonePlainPermission = computed(() => hasPermission(['tcm.diagnosis/pho
|
|||||||
const showPhoneMaskedEdit = computed(() => mode.value === 'edit' && !hasPhonePlainPermission.value)
|
const showPhoneMaskedEdit = computed(() => mode.value === 'edit' && !hasPhonePlainPermission.value)
|
||||||
const phoneRevealUnlocked = ref(false)
|
const phoneRevealUnlocked = ref(false)
|
||||||
const activeTab = ref('basic')
|
const activeTab = ref('basic')
|
||||||
const drawerTitle = computed(() => (mode.value === 'add' ? '新增诊单' : '编辑诊单'))
|
const drawerTitle = computed(() => {
|
||||||
|
if (viewOnly.value) return '诊单详情'
|
||||||
|
return mode.value === 'add' ? '新增诊单' : '编辑诊单'
|
||||||
|
})
|
||||||
|
|
||||||
watch([visible, activeTab], () => {
|
watch([visible, activeTab], () => {
|
||||||
if (!visible.value) return
|
if (!visible.value) return
|
||||||
@@ -1075,7 +1085,22 @@ const getDictOptions = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadDiagnosisDetailIntoForm(id: number): Promise<void> {
|
||||||
|
const data = await tcmDiagnosisDetail({ id })
|
||||||
|
|
||||||
|
originalPhone.value = data.phone || ''
|
||||||
|
originalIdCard.value = data.id_card || ''
|
||||||
|
|
||||||
|
data.phone = hasPhonePlainPermission.value ? data.phone || '' : maskPhone(data.phone || '')
|
||||||
|
data.id_card = maskIdCard(data.id_card || '')
|
||||||
|
|
||||||
|
formData.value = data
|
||||||
|
const y = data.diabetes_discovery_year
|
||||||
|
formData.value.diabetes_discovery_year = y == null || y === '' ? '' : String(y)
|
||||||
|
}
|
||||||
|
|
||||||
const open = async (type: string, id?: number) => {
|
const open = async (type: string, id?: number) => {
|
||||||
|
viewOnly.value = false
|
||||||
mode.value = type
|
mode.value = type
|
||||||
visible.value = true
|
visible.value = true
|
||||||
activeTab.value = 'basic' // 重置到基本信息标签页
|
activeTab.value = 'basic' // 重置到基本信息标签页
|
||||||
@@ -1085,27 +1110,27 @@ const open = async (type: string, id?: number) => {
|
|||||||
await getDictOptions()
|
await getDictOptions()
|
||||||
|
|
||||||
if (type === 'edit' && id) {
|
if (type === 'edit' && id) {
|
||||||
const data = await tcmDiagnosisDetail({ id })
|
await loadDiagnosisDetailIntoForm(id)
|
||||||
|
|
||||||
// 保存原始完整数据
|
|
||||||
originalPhone.value = data.phone || ''
|
|
||||||
originalIdCard.value = data.id_card || ''
|
|
||||||
|
|
||||||
// 有「手机号明文」权限:表单中保持完整号码;否则表单内为脱敏(编辑无权限时用只读区+点击展开)
|
|
||||||
data.phone = hasPhonePlainPermission.value ? data.phone || '' : maskPhone(data.phone || '')
|
|
||||||
data.id_card = maskIdCard(data.id_card || '')
|
|
||||||
|
|
||||||
formData.value = data
|
|
||||||
const y = data.diabetes_discovery_year
|
|
||||||
formData.value.diabetes_discovery_year =
|
|
||||||
y == null || y === '' ? '' : String(y)
|
|
||||||
} else if (type === 'add') {
|
} else if (type === 'add') {
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
formData.value.assistant_id = userStore.userInfo?.id || ''
|
formData.value.assistant_id = userStore.userInfo?.id || ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 只读打开:与编辑同一套界面与全部 Tab,不可保存 */
|
||||||
|
const openViewOnly = async (id: number) => {
|
||||||
|
if (!id) return
|
||||||
|
viewOnly.value = true
|
||||||
|
mode.value = 'edit'
|
||||||
|
visible.value = true
|
||||||
|
activeTab.value = 'basic'
|
||||||
|
phoneRevealUnlocked.value = false
|
||||||
|
await getDictOptions()
|
||||||
|
await loadDiagnosisDetailIntoForm(id)
|
||||||
|
}
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
if (viewOnly.value) return
|
||||||
await formRef.value?.validate()
|
await formRef.value?.validate()
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
@@ -1239,12 +1264,14 @@ const handleClose = () => {
|
|||||||
isPhoneFocused.value = false
|
isPhoneFocused.value = false
|
||||||
isIdCardFocused.value = false
|
isIdCardFocused.value = false
|
||||||
phoneRevealUnlocked.value = false
|
phoneRevealUnlocked.value = false
|
||||||
|
viewOnly.value = false
|
||||||
|
|
||||||
visible.value = false
|
visible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
open,
|
open,
|
||||||
|
openViewOnly,
|
||||||
refreshCaseList: () => caseRecordListRef.value?.refresh?.()
|
refreshCaseList: () => caseRecordListRef.value?.refresh?.()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -1252,7 +1279,15 @@ defineExpose({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.edit-drawer {
|
.edit-drawer {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
||||||
|
.diagnosis-view-fieldset:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
:deep(.el-input__wrapper),
|
||||||
|
:deep(.el-textarea__inner) {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.el-form) {
|
:deep(.el-form) {
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -1415,14 +1450,7 @@ defineExpose({
|
|||||||
z-index: 3000 !important;
|
z-index: 3000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 降低抽屉及其遮罩层的z-index */
|
/* 勿对全局 .el-drawer / .el-overlay 写 z-index,会覆盖子组件的 :z-index(例如从消费处方里嵌套打开只读诊单时会被压在下面) */
|
||||||
.el-drawer {
|
|
||||||
z-index: 1500 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-overlay {
|
|
||||||
z-index: 1499 ;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 确保日期选择器和下拉选择器的弹出层在最上层 */
|
/* 确保日期选择器和下拉选择器的弹出层在最上层 */
|
||||||
.el-picker__popper,
|
.el-picker__popper,
|
||||||
|
|||||||
@@ -70,6 +70,25 @@ class PrescriptionLists extends BaseAdminDataLists implements ListsSearchInterfa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 来源: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
|
private function applyVisibilityScope($query): void
|
||||||
{
|
{
|
||||||
$query->where(function ($query) {
|
$query->where(function ($query) {
|
||||||
@@ -116,6 +135,7 @@ class PrescriptionLists extends BaseAdminDataLists implements ListsSearchInterfa
|
|||||||
{
|
{
|
||||||
$query = Prescription::where($this->searchWhere);
|
$query = Prescription::where($this->searchWhere);
|
||||||
$this->applyAuditFilter($query);
|
$this->applyAuditFilter($query);
|
||||||
|
$this->applySourceFilter($query);
|
||||||
$this->applyVisibilityScope($query);
|
$this->applyVisibilityScope($query);
|
||||||
$this->applyCreatorIdsFilter($query);
|
$this->applyCreatorIdsFilter($query);
|
||||||
|
|
||||||
@@ -208,6 +228,7 @@ class PrescriptionLists extends BaseAdminDataLists implements ListsSearchInterfa
|
|||||||
{
|
{
|
||||||
$query = Prescription::where($this->searchWhere);
|
$query = Prescription::where($this->searchWhere);
|
||||||
$this->applyAuditFilter($query);
|
$this->applyAuditFilter($query);
|
||||||
|
$this->applySourceFilter($query);
|
||||||
$this->applyVisibilityScope($query);
|
$this->applyVisibilityScope($query);
|
||||||
$this->applyCreatorIdsFilter($query);
|
$this->applyCreatorIdsFilter($query);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ use app\common\model\tcm\Prescription;
|
|||||||
use app\common\model\tcm\PrescriptionOrder;
|
use app\common\model\tcm\PrescriptionOrder;
|
||||||
use app\common\model\tcm\PrescriptionOrderPayOrder;
|
use app\common\model\tcm\PrescriptionOrderPayOrder;
|
||||||
use app\common\service\gancao\GancaoScmRecipelService;
|
use app\common\service\gancao\GancaoScmRecipelService;
|
||||||
|
use think\facade\Config;
|
||||||
|
use think\db\Query;
|
||||||
|
|
||||||
class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
||||||
{
|
{
|
||||||
@@ -22,12 +24,45 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
|||||||
return [
|
return [
|
||||||
'=' => ['prescription_id', 'fulfillment_status', 'prescription_audit_status', 'payment_slip_audit_status'],
|
'=' => ['prescription_id', 'fulfillment_status', 'prescription_audit_status', 'payment_slip_audit_status'],
|
||||||
'%like%' => ['order_no'],
|
'%like%' => ['order_no'],
|
||||||
|
'between_time' => 'create_time',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function lists(): array
|
/**
|
||||||
|
* 列表/统计条数:含全部搜索条件(含创建时间区间)
|
||||||
|
*/
|
||||||
|
private function buildFilteredQuery(): Query
|
||||||
{
|
{
|
||||||
$query = PrescriptionOrder::where($this->searchWhere)->whereNull('delete_time');
|
$query = PrescriptionOrder::where($this->searchWhere)->whereNull('delete_time');
|
||||||
|
$this->applyPermissionAndExtraFilters($query);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 今日汇总:与列表相同的「非时间」条件(医生/医助/患者/状态/单号等),不含创建时间区间,避免与「今日」语义重复
|
||||||
|
*
|
||||||
|
* @return array<int, array{0: string, 1: string, 2: mixed}>
|
||||||
|
*/
|
||||||
|
private function searchWhereWithoutCreateTimeRange(): array
|
||||||
|
{
|
||||||
|
return array_values(array_filter(
|
||||||
|
$this->searchWhere,
|
||||||
|
static function ($w): bool {
|
||||||
|
if (!\is_array($w) || \count($w) < 2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (($w[0] ?? '') === 'create_time' && ($w[1] ?? '') === 'between') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyPermissionAndExtraFilters($query): void
|
||||||
|
{
|
||||||
$this->applyDoctorAssistantFilters($query);
|
$this->applyDoctorAssistantFilters($query);
|
||||||
$this->applyPatientKeywordFilter($query);
|
$this->applyPatientKeywordFilter($query);
|
||||||
if (!PrescriptionOrderLogic::canSeeAllPrescriptionOrders($this->adminInfo)) {
|
if (!PrescriptionOrderLogic::canSeeAllPrescriptionOrders($this->adminInfo)) {
|
||||||
@@ -39,6 +74,85 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 医助:仅「诊单医助=本人」的订单计业绩
|
||||||
|
*/
|
||||||
|
private function applyAssistantDiagnosisOnlyFilter($query): void
|
||||||
|
{
|
||||||
|
$poTbl = (new PrescriptionOrder())->getTable();
|
||||||
|
$diagTbl = (new Diagnosis())->getTable();
|
||||||
|
$aid = (int) $this->adminId;
|
||||||
|
$query->whereExists(
|
||||||
|
"SELECT 1 FROM `{$diagTbl}` dg WHERE dg.`id` = `{$poTbl}`.`diagnosis_id` AND dg.`delete_time` IS NULL AND dg.`assistant_id` = {$aid}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计用查询骨架:不含 create_time 区间;其它筛选项 + 统计专用数据域(不重复 where)
|
||||||
|
*/
|
||||||
|
private function buildBaseQueryForStats(): Query
|
||||||
|
{
|
||||||
|
$query = PrescriptionOrder::where($this->searchWhereWithoutCreateTimeRange())->whereNull('delete_time');
|
||||||
|
$this->applyDoctorAssistantFilters($query);
|
||||||
|
$this->applyPatientKeywordFilter($query);
|
||||||
|
if (PrescriptionOrderLogic::canViewOrderListStatsAllScope($this->adminInfo)) {
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
$assistantRid = (int) Config::get('project.prescription_order_stats_assistant_role_id', 2);
|
||||||
|
$myRoles = array_map('intval', $this->adminInfo['role_id'] ?? []);
|
||||||
|
if ($assistantRid > 0 && in_array($assistantRid, $myRoles, true)) {
|
||||||
|
$this->applyAssistantDiagnosisOnlyFilter($query);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
if (!PrescriptionOrderLogic::canSeeAllPrescriptionOrders($this->adminInfo)) {
|
||||||
|
$diagIds = Diagnosis::where('assistant_id', $this->adminId)->whereNull('delete_time')->column('id');
|
||||||
|
$query->where(function ($q) use ($diagIds) {
|
||||||
|
$q->where('creator_id', $this->adminId);
|
||||||
|
if ($diagIds !== []) {
|
||||||
|
$q->whereOr('diagnosis_id', 'in', $diagIds);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计所对应的时间:与入参 start_time / end_time 一致;未传则按自然日「今天」
|
||||||
|
*
|
||||||
|
* @return array{0: int, 1: int, 2: string, 3: string}
|
||||||
|
*/
|
||||||
|
private function resolveStatsTimeWindow(): array
|
||||||
|
{
|
||||||
|
$st = trim((string) ($this->params['start_time'] ?? ''));
|
||||||
|
$et = trim((string) ($this->params['end_time'] ?? ''));
|
||||||
|
if ($st !== '' && $et !== '') {
|
||||||
|
$t0 = strtotime($st);
|
||||||
|
$t1 = strtotime($et);
|
||||||
|
if ($t0 !== false && $t1 !== false) {
|
||||||
|
return [(int) $t0, (int) $t1, $st, $et];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$t0 = strtotime(date('Y-m-d 00:00:00'));
|
||||||
|
$t1 = strtotime(date('Y-m-d 23:59:59'));
|
||||||
|
if ($t0 === false || $t1 === false) {
|
||||||
|
return [0, 0, '', ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
(int) $t0,
|
||||||
|
(int) $t1,
|
||||||
|
date('Y-m-d 00:00:00'),
|
||||||
|
date('Y-m-d 23:59:59'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lists(): array
|
||||||
|
{
|
||||||
|
$query = $this->buildFilteredQuery();
|
||||||
$lists = $query
|
$lists = $query
|
||||||
->order('id', 'desc')
|
->order('id', 'desc')
|
||||||
->limit($this->limitOffset, $this->limitLength)
|
->limit($this->limitOffset, $this->limitLength)
|
||||||
@@ -159,26 +273,87 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
|||||||
|
|
||||||
public function extend(): array
|
public function extend(): array
|
||||||
{
|
{
|
||||||
|
$s = $this->computeListStatsForExtend();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'deposit_min_amount' => PrescriptionOrderLogic::depositMinAmount(),
|
'deposit_min_amount' => PrescriptionOrderLogic::depositMinAmount(),
|
||||||
'gancao_scm_enabled' => GancaoScmRecipelService::isConfigured(),
|
'gancao_scm_enabled' => GancaoScmRecipelService::isConfigured(),
|
||||||
|
'stats_order_amount' => $s['order_amount'],
|
||||||
|
'stats_linked_pay_amount' => $s['linked_pay_amount'],
|
||||||
|
'stats_time_start' => $s['label_start'],
|
||||||
|
'stats_time_end' => $s['label_end'],
|
||||||
|
/** 统计口径:all=支付审核白名单全量;assistant=医助仅诊单协助业绩;restricted=与列表同域 */
|
||||||
|
'stats_scope' => $s['scope'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{order_amount: float, linked_pay_amount: float, label_start: string, label_end: string, scope: string}
|
||||||
|
*/
|
||||||
|
private function computeListStatsForExtend(): array
|
||||||
|
{
|
||||||
|
[$t0, $t1, $l0, $l1] = $this->resolveStatsTimeWindow();
|
||||||
|
if ($t0 <= 0 || $t1 <= 0) {
|
||||||
|
return [
|
||||||
|
'order_amount' => 0.0,
|
||||||
|
'linked_pay_amount' => 0.0,
|
||||||
|
'label_start' => '',
|
||||||
|
'label_end' => '',
|
||||||
|
'scope' => 'restricted',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ($t1 < $t0) {
|
||||||
|
$tmp = $t0;
|
||||||
|
$t0 = $t1;
|
||||||
|
$t1 = $tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
$q = $this->buildBaseQueryForStats()->whereBetween('create_time', [$t0, $t1]);
|
||||||
|
$orderAmount = round((float) (clone $q)->sum('amount'), 2);
|
||||||
|
$ids = (clone $q)->column('id');
|
||||||
|
$ids = array_values(array_filter(array_map('intval', $ids), static function (int $id): bool {
|
||||||
|
return $id > 0;
|
||||||
|
}));
|
||||||
|
if ($ids === []) {
|
||||||
|
return $this->statsExtendPayload($orderAmount, 0.0, $l0, $l1);
|
||||||
|
}
|
||||||
|
$sumPaid = (float) PrescriptionOrderPayOrder::alias('l')
|
||||||
|
->join('order o', 'l.pay_order_id = o.id')
|
||||||
|
->whereIn('l.prescription_order_id', $ids)
|
||||||
|
->whereNull('o.delete_time')
|
||||||
|
->sum('o.amount');
|
||||||
|
$sumPaid = round($sumPaid, 2);
|
||||||
|
|
||||||
|
return $this->statsExtendPayload($orderAmount, $sumPaid, $l0, $l1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{order_amount: float, linked_pay_amount: float, label_start: string, label_end: string, scope: string}
|
||||||
|
*/
|
||||||
|
private function statsExtendPayload(float $orderAmount, float $linked, string $l0, string $l1): array
|
||||||
|
{
|
||||||
|
$scope = 'restricted';
|
||||||
|
if (PrescriptionOrderLogic::canViewOrderListStatsAllScope($this->adminInfo)) {
|
||||||
|
$scope = 'all';
|
||||||
|
} else {
|
||||||
|
$ar = (int) Config::get('project.prescription_order_stats_assistant_role_id', 2);
|
||||||
|
if ($ar > 0 && in_array($ar, array_map('intval', $this->adminInfo['role_id'] ?? []), true)) {
|
||||||
|
$scope = 'assistant';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'order_amount' => $orderAmount,
|
||||||
|
'linked_pay_amount' => $linked,
|
||||||
|
'label_start' => $l0,
|
||||||
|
'label_end' => $l1,
|
||||||
|
'scope' => $scope,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function count(): int
|
public function count(): int
|
||||||
{
|
{
|
||||||
$query = PrescriptionOrder::where($this->searchWhere)->whereNull('delete_time');
|
$query = $this->buildFilteredQuery();
|
||||||
$this->applyDoctorAssistantFilters($query);
|
|
||||||
$this->applyPatientKeywordFilter($query);
|
|
||||||
if (!PrescriptionOrderLogic::canSeeAllPrescriptionOrders($this->adminInfo)) {
|
|
||||||
$diagIds = Diagnosis::where('assistant_id', $this->adminId)->whereNull('delete_time')->column('id');
|
|
||||||
$query->where(function ($q) use ($diagIds) {
|
|
||||||
$q->where('creator_id', $this->adminId);
|
|
||||||
if ($diagIds !== []) {
|
|
||||||
$q->whereOr('diagnosis_id', 'in', $diagIds);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int) $query->count();
|
return (int) $query->count();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,22 @@ class PrescriptionOrderLogic
|
|||||||
return self::roleIntersect($adminInfo, is_array($allow) ? $allow : []);
|
return self::roleIntersect($adminInfo, is_array($allow) ? $allow : []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表顶栏金额统计:与「关联支付单」审核白名单同档的角色可见「全量」统计(不缩小到本人/医助诊单域)
|
||||||
|
*/
|
||||||
|
public static function canViewOrderListStatsAllScope(array $adminInfo): bool
|
||||||
|
{
|
||||||
|
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$allow = Config::get('project.prescription_order_payment_audit_roles', [0, 3, 4, 9, 6]);
|
||||||
|
if (!is_array($allow)) {
|
||||||
|
$allow = [0, 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::roleIntersect($adminInfo, $allow);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string,mixed> $row
|
* @param array<string,mixed> $row
|
||||||
*/
|
*/
|
||||||
@@ -424,6 +440,12 @@ class PrescriptionOrderLogic
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((int) ($rx['void_status'] ?? 0) === 1) {
|
||||||
|
self::$error = '已作废处方不可创建业务订单';
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (PrescriptionOrder::where('prescription_id', $rxId)->whereNull('delete_time')->where('fulfillment_status', '<>', 4)->count() > 0) {
|
if (PrescriptionOrder::where('prescription_id', $rxId)->whereNull('delete_time')->where('fulfillment_status', '<>', 4)->count() > 0) {
|
||||||
self::$error = '该处方已存在有效业务订单(未撤回前不可重复创建)';
|
self::$error = '该处方已存在有效业务订单(未撤回前不可重复创建)';
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,9 @@ return [
|
|||||||
// 业务订单「关联支付单」审核:以下角色 ID + root 可操作(可与财务角色一致)
|
// 业务订单「关联支付单」审核:以下角色 ID + root 可操作(可与财务角色一致)
|
||||||
'prescription_order_payment_audit_roles' => [0, 3, 4, 9, 6],
|
'prescription_order_payment_audit_roles' => [0, 3, 4, 9, 6],
|
||||||
|
|
||||||
|
// 业务订单列表金额统计:医助角色 ID;非支付审核白名单的医助仅统计其诊单 assistant_id=本人 的业绩
|
||||||
|
'prescription_order_stats_assistant_role_id' => 2,
|
||||||
|
|
||||||
// 腾讯云实时音视频(TRTC)配置
|
// 腾讯云实时音视频(TRTC)配置
|
||||||
'trtc' => [
|
'trtc' => [
|
||||||
'sdkAppId' => (int)env('trtc.sdk_app_id', 1600127710),
|
'sdkAppId' => (int)env('trtc.sdk_app_id', 1600127710),
|
||||||
|
|||||||
Reference in New Issue
Block a user