处方增加禁用修改

This commit is contained in:
2026-06-04 15:11:16 +08:00
parent cdf8b1f76d
commit 123e9075dd
9 changed files with 342 additions and 34 deletions
-4
View File
@@ -26,10 +26,6 @@ bin-release/
/.cursor /.cursor
/.codex /.codex
/.agents /.agents
/.claude
/.claude
/.claude
/.kbctx
/server/.spool /server/.spool
/server/.claude /server/.claude
/.spool /.spool
+56 -11
View File
@@ -88,13 +88,17 @@
<strong>{{ formData.herbs.length }}</strong> 主方 {{ mainHerbCount }} / 辅方 <strong>{{ formData.herbs.length }}</strong> 主方 {{ mainHerbCount }} / 辅方
{{ auxHerbCount }}· 可手动添加从处方库或粘贴药方批量录入 {{ auxHerbCount }}· 可手动添加从处方库或粘贴药方批量录入
</p> </p>
<p v-if="herbsLocked" class="rp-toolbar__lock-tip">
<el-tag type="danger" size="small" effect="plain">禁用修改</el-tag>
禁用修改处方库模板药材已锁定不可增删改如需调整请重新从处方库导入覆盖
</p>
</div> </div>
</div> </div>
<div class="rp-toolbar__actions" role="toolbar" aria-label="处方药材快捷操作"> <div class="rp-toolbar__actions" role="toolbar" aria-label="处方药材快捷操作">
<el-button type="primary" size="small" :icon="CirclePlus" @click="handleAddHerb('主方')"> <el-button type="primary" size="small" :icon="CirclePlus" :disabled="herbsLocked" @click="handleAddHerb('主方')">
添加主方 添加主方
</el-button> </el-button>
<el-button type="primary" plain size="small" @click="handleAddHerb('辅方')"> <el-button type="primary" plain size="small" :disabled="herbsLocked" @click="handleAddHerb('辅方')">
添加辅方 添加辅方
</el-button> </el-button>
<el-button <el-button
@@ -135,7 +139,7 @@
:class="{ 'herb-editor-card--dup': isDuplicateHerbName(row.index) }" :class="{ 'herb-editor-card--dup': isDuplicateHerbName(row.index) }"
> >
<div class="herb-editor-card__title">主方 {{ row.index + 1 }}</div> <div class="herb-editor-card__title">主方 {{ row.index + 1 }}</div>
<MedicineNameSelect v-model="formData.herbs[row.index].name" class="herb-editor-card__select" /> <MedicineNameSelect v-model="formData.herbs[row.index].name" :disabled="herbsLocked" class="herb-editor-card__select" />
<div <div
v-if="isDuplicateHerbName(row.index)" v-if="isDuplicateHerbName(row.index)"
class="herb-editor-card__dup-hint text-amber-600 text-xs mb-1" class="herb-editor-card__dup-hint text-amber-600 text-xs mb-1"
@@ -148,11 +152,12 @@
:min="0.1" :min="0.1"
:step="1" :step="1"
:precision="1" :precision="1"
:disabled="herbsLocked"
placeholder="剂量(克)" placeholder="剂量(克)"
class="herb-editor-card__dose" class="herb-editor-card__dose"
/> />
<span class="text-gray-500 shrink-0"></span> <span class="text-gray-500 shrink-0"></span>
<el-button type="danger" size="small" link @click="handleRemoveHerb(row.index)"> <el-button v-if="!herbsLocked" type="danger" size="small" link @click="handleRemoveHerb(row.index)">
删除 删除
</el-button> </el-button>
</div> </div>
@@ -171,7 +176,7 @@
:class="{ 'herb-editor-card--dup': isDuplicateHerbName(row.index) }" :class="{ 'herb-editor-card--dup': isDuplicateHerbName(row.index) }"
> >
<div class="herb-editor-card__title">辅方</div> <div class="herb-editor-card__title">辅方</div>
<MedicineNameSelect v-model="formData.herbs[row.index].name" class="herb-editor-card__select" /> <MedicineNameSelect v-model="formData.herbs[row.index].name" :disabled="herbsLocked" class="herb-editor-card__select" />
<div <div
v-if="isDuplicateHerbName(row.index)" v-if="isDuplicateHerbName(row.index)"
class="herb-editor-card__dup-hint text-amber-600 text-xs mb-1" class="herb-editor-card__dup-hint text-amber-600 text-xs mb-1"
@@ -184,11 +189,12 @@
:min="0.1" :min="0.1"
:step="1" :step="1"
:precision="1" :precision="1"
:disabled="herbsLocked"
placeholder="剂量(克)" placeholder="剂量(克)"
class="herb-editor-card__dose" class="herb-editor-card__dose"
/> />
<span class="text-gray-500 shrink-0"></span> <span class="text-gray-500 shrink-0"></span>
<el-button type="danger" size="small" link @click="handleRemoveHerb(row.index)"> <el-button v-if="!herbsLocked" type="danger" size="small" link @click="handleRemoveHerb(row.index)">
删除 删除
</el-button> </el-button>
</div> </div>
@@ -1028,6 +1034,8 @@ interface Herb {
name: string name: string
dosage: number dosage: number
formula_type?: FormulaType formula_type?: FormulaType
/** 来自「禁用修改」处方库模板:锁定后不可增删改,只能再次导入覆盖 */
locked?: boolean
} }
type AuxUsageForm = { type AuxUsageForm = {
@@ -1044,11 +1052,15 @@ function normalizeFormulaType(v: unknown): FormulaType {
} }
function normalizeHerbRow(raw: any): Herb { function normalizeHerbRow(raw: any): Herb {
return { const row: Herb = {
name: String(raw?.name ?? '').trim(), name: String(raw?.name ?? '').trim(),
dosage: Number(raw?.dosage) || 0, dosage: Number(raw?.dosage) || 0,
formula_type: normalizeFormulaType(raw?.formula_type) formula_type: normalizeFormulaType(raw?.formula_type)
} }
if (raw?.locked === true || raw?.locked === 1) {
row.locked = true
}
return row
} }
function defaultAuxUsage(prescriptionType = '浓缩水丸'): AuxUsageForm { function defaultAuxUsage(prescriptionType = '浓缩水丸'): AuxUsageForm {
@@ -1430,6 +1442,12 @@ const auxHerbRows = computed(() =>
const mainHerbCount = computed(() => mainHerbRows.value.length) const mainHerbCount = computed(() => mainHerbRows.value.length)
const auxHerbCount = computed(() => auxHerbRows.value.length) const auxHerbCount = computed(() => auxHerbRows.value.length)
/**
* 整张处方是否被锁定:只要任一药材来自「禁用修改」模板即锁定。
* 锁定后:不可增删改任意药材,仅可再次「从处方库导入」覆盖。
*/
const herbsLocked = computed(() => formData.herbs.some((h) => (h as Herb).locked === true))
const libraryDoctorId = computed(() => { const libraryDoctorId = computed(() => {
const uid = Number(userStore.userInfo?.id) const uid = Number(userStore.userInfo?.id)
return uid > 0 ? uid : 0 return uid > 0 ? uid : 0
@@ -2029,10 +2047,18 @@ function findDuplicateHerbNames(): string[] {
} }
const handleAddHerb = (formulaType: FormulaType = '主方') => { const handleAddHerb = (formulaType: FormulaType = '主方') => {
if (herbsLocked.value) {
feedback.msgWarning('该处方含「禁用修改」模板,药材已锁定,请重新从处方库导入覆盖')
return
}
formData.herbs.push({ name: '', dosage: 6, formula_type: formulaType }) formData.herbs.push({ name: '', dosage: 6, formula_type: formulaType })
} }
const handleRemoveHerb = (index: number) => { const handleRemoveHerb = (index: number) => {
if (herbsLocked.value) {
feedback.msgWarning('该处方含「禁用修改」模板,药材已锁定,不可删除')
return
}
formData.herbs.splice(index, 1) formData.herbs.splice(index, 1)
} }
@@ -2089,10 +2115,18 @@ const handleImportLibrary = (row: any) => {
} }
const formulaType = normalizeFormulaType(row.formula_type) const formulaType = normalizeFormulaType(row.formula_type)
const imported = (JSON.parse(JSON.stringify(row.herbs)) as any[]).map((h) => ({ // 「禁用修改」模板:导入的药材打上锁定标记 → 整张处方锁定(不可增删改,只能再次导入覆盖)
...normalizeHerbRow(h), const isLockedTemplate = Number(row?.disable_edit) === 1
formula_type: formulaType const imported = (JSON.parse(JSON.stringify(row.herbs)) as any[]).map((h) => {
})) const herb = normalizeHerbRow(h)
herb.formula_type = formulaType
if (isLockedTemplate) {
herb.locked = true
} else {
delete herb.locked
}
return herb
})
if (libraryImportMode.value === 'replace') { if (libraryImportMode.value === 'replace') {
const kept = formData.herbs.filter((h) => normalizeFormulaType(h.formula_type) !== formulaType) const kept = formData.herbs.filter((h) => normalizeFormulaType(h.formula_type) !== formulaType)
formData.herbs = [...kept, ...imported] formData.herbs = [...kept, ...imported]
@@ -2532,6 +2566,17 @@ defineExpose({
color: var(--el-text-color-regular); color: var(--el-text-color-regular);
} }
.rp-toolbar__lock-tip {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
margin: 6px 0 0;
font-size: 12px;
line-height: 1.55;
color: var(--el-color-danger);
}
.rp-toolbar__actions { .rp-toolbar__actions {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
+46 -12
View File
@@ -679,17 +679,21 @@
粘贴文本解析药名与剂量;须与药品库名称完全一致才录入(导入至主方) 粘贴文本解析药名与剂量;须与药品库名称完全一致才录入(导入至主方)
</span> </span>
</div> </div>
<div v-if="herbsLocked" class="flex items-center gap-2 flex-wrap mt-2 text-xs text-[var(--el-color-danger)]">
<el-tag type="danger" size="small" effect="plain">禁用修改</el-tag>
含「禁用修改」处方库模板,药材已锁定,不可增删改,如需调整请重新「从处方库导入」覆盖
</div>
<div class="herb-formula-block mt-3"> <div class="herb-formula-block mt-3">
<div class="herb-formula-block__head"> <div class="herb-formula-block__head">
<el-tag type="primary" size="small">主方</el-tag> <el-tag type="primary" size="small">主方</el-tag>
<el-button type="primary" size="small" @click="addHerb('主方')">添加主方药材</el-button> <el-button type="primary" size="small" :disabled="herbsLocked" @click="addHerb('主方')">添加主方药材</el-button>
</div> </div>
<el-table :data="mainHerbRows" class="mt-2" border empty-text="暂无主方药材"> <el-table :data="mainHerbRows" class="mt-2" border empty-text="暂无主方药材">
<el-table-column label="序号" type="index" width="60" /> <el-table-column label="序号" type="index" width="60" />
<el-table-column label="药材名称" min-width="220"> <el-table-column label="药材名称" min-width="220">
<template #default="{ row }"> <template #default="{ row }">
<MedicineNameSelect v-model="editForm.herbs[row.index].name" class="w-full" /> <MedicineNameSelect v-model="editForm.herbs[row.index].name" :disabled="herbsLocked" class="w-full" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="剂量(克)" min-width="120"> <el-table-column label="剂量(克)" min-width="120">
@@ -699,6 +703,7 @@
:min="0" :min="0"
:precision="1" :precision="1"
:step="0.5" :step="0.5"
:disabled="herbsLocked"
placeholder="剂量" placeholder="剂量"
class="w-full" class="w-full"
/> />
@@ -706,7 +711,8 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" width="80"> <el-table-column label="操作" width="80">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="danger" link @click="removeHerb(row.index)">删除</el-button> <el-button v-if="!herbsLocked" type="danger" link @click="removeHerb(row.index)">删除</el-button>
<span v-else class="text-gray-400 text-xs">已锁定</span>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -715,7 +721,7 @@
<div class="herb-formula-block mt-4"> <div class="herb-formula-block mt-4">
<div class="herb-formula-block__head"> <div class="herb-formula-block__head">
<el-tag type="warning" size="small">辅方</el-tag> <el-tag type="warning" size="small">辅方</el-tag>
<el-button type="primary" size="small" plain @click="addHerb('辅方')"> <el-button type="primary" size="small" plain :disabled="herbsLocked" @click="addHerb('辅方')">
添加辅方药材 添加辅方药材
</el-button> </el-button>
</div> </div>
@@ -723,7 +729,7 @@
<el-table-column label="序号" type="index" width="60" /> <el-table-column label="序号" type="index" width="60" />
<el-table-column label="药材名称" min-width="220"> <el-table-column label="药材名称" min-width="220">
<template #default="{ row }"> <template #default="{ row }">
<MedicineNameSelect v-model="editForm.herbs[row.index].name" class="w-full" /> <MedicineNameSelect v-model="editForm.herbs[row.index].name" :disabled="herbsLocked" class="w-full" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="剂量(克)" min-width="120"> <el-table-column label="剂量(克)" min-width="120">
@@ -733,6 +739,7 @@
:min="0" :min="0"
:precision="1" :precision="1"
:step="0.5" :step="0.5"
:disabled="herbsLocked"
placeholder="剂量" placeholder="剂量"
class="w-full" class="w-full"
/> />
@@ -740,7 +747,8 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" width="80"> <el-table-column label="操作" width="80">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="danger" link @click="removeHerb(row.index)">删除</el-button> <el-button v-if="!herbsLocked" type="danger" link @click="removeHerb(row.index)">删除</el-button>
<span v-else class="text-gray-400 text-xs">已锁定</span>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -1698,7 +1706,7 @@ import jsPDF from 'jspdf'
const TcmDiagnosisEditView = defineAsyncComponent(() => import('@/views/tcm/diagnosis/edit.vue')) const TcmDiagnosisEditView = defineAsyncComponent(() => import('@/views/tcm/diagnosis/edit.vue'))
type FormulaType = '主方' | '辅方' type FormulaType = '主方' | '辅方'
type HerbRow = { name: string; dosage: number; formula_type: FormulaType } type HerbRow = { name: string; dosage: number; formula_type: FormulaType; locked?: boolean }
type AuxUsageForm = { type AuxUsageForm = {
dosage_amount?: number dosage_amount?: number
@@ -1798,11 +1806,15 @@ function normalizeFormulaType(v: unknown): FormulaType {
} }
function normalizeHerbRow(raw: any): HerbRow { function normalizeHerbRow(raw: any): HerbRow {
return { const row: HerbRow = {
name: String(raw?.name ?? '').trim(), name: String(raw?.name ?? '').trim(),
dosage: Number(raw?.dosage) || 0, dosage: Number(raw?.dosage) || 0,
formula_type: normalizeFormulaType(raw?.formula_type) formula_type: normalizeFormulaType(raw?.formula_type)
} }
if (raw?.locked === true || raw?.locked === 1) {
row.locked = true
}
return row
} }
function normalizeSlipHerbs(raw: unknown): HerbRow[] { function normalizeSlipHerbs(raw: unknown): HerbRow[] {
@@ -2481,6 +2493,12 @@ const auxHerbRows = computed(() =>
.filter(({ herb }) => normalizeFormulaType(herb.formula_type) === '辅方') .filter(({ herb }) => normalizeFormulaType(herb.formula_type) === '辅方')
) )
/**
* 整张处方是否被锁定:只要任一药材来自「禁用修改」处方库模板即锁定。
* 锁定后不可增删改任意药材,只能再次「从处方库导入」覆盖。
*/
const herbsLocked = computed(() => editForm.herbs.some((h) => (h as HerbRow).locked === true))
const slipDietaryText = computed(() => { const slipDietaryText = computed(() => {
const d = slipView.value?.dietary_taboo const d = slipView.value?.dietary_taboo
if (Array.isArray(d)) return d.filter(Boolean).join('、') if (Array.isArray(d)) return d.filter(Boolean).join('、')
@@ -3441,10 +3459,18 @@ const handleImportLibrary = (row: any) => {
return return
} }
const formulaType = normalizeFormulaType(row.formula_type) const formulaType = normalizeFormulaType(row.formula_type)
const imported = (JSON.parse(JSON.stringify(row.herbs)) as any[]).map((h) => ({ // 「禁用修改」模板:导入药材打上锁定标记 → 整张处方锁定(不可增删改,只能再次导入覆盖)
...normalizeHerbRow(h), const isLockedTemplate = Number(row?.disable_edit) === 1
formula_type: formulaType const imported = (JSON.parse(JSON.stringify(row.herbs)) as any[]).map((h) => {
})) const herb = normalizeHerbRow(h)
herb.formula_type = formulaType
if (isLockedTemplate) {
herb.locked = true
} else {
delete herb.locked
}
return herb
})
if (libraryImportMode.value === 'replace') { if (libraryImportMode.value === 'replace') {
const kept = editForm.herbs.filter((h) => normalizeFormulaType(h.formula_type) !== formulaType) const kept = editForm.herbs.filter((h) => normalizeFormulaType(h.formula_type) !== formulaType)
editForm.herbs = [...kept, ...imported] editForm.herbs = [...kept, ...imported]
@@ -3650,6 +3676,10 @@ async function handlePasteRecipeImport() {
// 添加药材(主方 / 辅方) // 添加药材(主方 / 辅方)
const addHerb = (formulaType: FormulaType = '主方') => { const addHerb = (formulaType: FormulaType = '主方') => {
if (herbsLocked.value) {
feedback.msgWarning('该处方含「禁用修改」模板,药材已锁定,请重新从处方库导入覆盖')
return
}
editForm.herbs.push({ editForm.herbs.push({
name: '', name: '',
dosage: 0, dosage: 0,
@@ -3659,6 +3689,10 @@ const addHerb = (formulaType: FormulaType = '主方') => {
// 删除药材 // 删除药材
const removeHerb = (index: number) => { const removeHerb = (index: number) => {
if (herbsLocked.value) {
feedback.msgWarning('该处方含「禁用修改」模板,药材已锁定,不可删除')
return
}
editForm.herbs.splice(index, 1) editForm.herbs.splice(index, 1)
} }
+27 -1
View File
@@ -71,6 +71,13 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="禁用修改" min-width="100">
<template #default="{ row }">
<el-tag :type="row.disable_edit ? 'danger' : 'info'">
{{ row.disable_edit ? '已禁用' : '可修改' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建人" prop="creator_name" min-width="100" /> <el-table-column label="创建人" prop="creator_name" min-width="100" />
<el-table-column label="创建时间" prop="create_time" min-width="160" /> <el-table-column label="创建时间" prop="create_time" min-width="160" />
<el-table-column label="操作" width="180" fixed="right"> <el-table-column label="操作" width="180" fixed="right">
@@ -193,6 +200,21 @@
勾选后所有医生都可以查看和使用此处方模板 勾选后所有医生都可以查看和使用此处方模板
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="禁用修改" prop="disable_edit">
<div class="flex flex-col gap-1 leading-none">
<el-switch
v-model="editForm.disable_edit"
:active-value="1"
:inactive-value="0"
active-text="开启"
inactive-text="关闭"
/>
<div class="text-xs text-gray-400 leading-relaxed">
开启后导入此模板到处方时将锁定整张处方的药材不可增删改只能再次导入覆盖
</div>
</div>
</el-form-item>
</el-form> </el-form>
<template #footer v-if="editMode !== 'view'"> <template #footer v-if="editMode !== 'view'">
@@ -236,7 +258,8 @@ const editForm = reactive({
prescription_name: '', prescription_name: '',
formula_type: '主方', formula_type: '主方',
herbs: [] as Array<{ name: string; dosage: number }>, herbs: [] as Array<{ name: string; dosage: number }>,
is_public: 0 is_public: 0,
disable_edit: 0
}) })
// 表单验证规则 // 表单验证规则
@@ -293,6 +316,7 @@ const resetForm = () => {
editForm.formula_type = '主方' editForm.formula_type = '主方'
editForm.herbs = [] editForm.herbs = []
editForm.is_public = 0 editForm.is_public = 0
editForm.disable_edit = 0
} }
// 新增处方 // 新增处方
@@ -310,6 +334,7 @@ const handleView = (row: any) => {
editForm.formula_type = row.formula_type || '主方' editForm.formula_type = row.formula_type || '主方'
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : [] editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
editForm.is_public = row.is_public ?? 0 editForm.is_public = row.is_public ?? 0
editForm.disable_edit = row.disable_edit ?? 0
showEdit.value = true showEdit.value = true
} }
@@ -321,6 +346,7 @@ const handleEdit = (row: any) => {
editForm.formula_type = row.formula_type || '主方' editForm.formula_type = row.formula_type || '主方'
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : [] editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
editForm.is_public = row.is_public ?? 0 editForm.is_public = row.is_public ?? 0
editForm.disable_edit = row.disable_edit ?? 0
showEdit.value = true showEdit.value = true
} }
@@ -56,7 +56,7 @@ class PrescriptionLibraryLists extends BaseAdminDataLists implements ListsSearch
public function lists(): array public function lists(): array
{ {
$field = [ $field = [
'id', 'prescription_name', 'formula_type', 'herbs', 'is_public', 'id', 'prescription_name', 'formula_type', 'herbs', 'is_public', 'disable_edit',
'creator_id', 'creator_name', 'create_time', 'update_time' 'creator_id', 'creator_name', 'create_time', 'update_time'
]; ];
@@ -778,6 +778,7 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
'export_channel' => '渠道', 'export_channel' => '渠道',
'export_guahao_channel_source' => '自媒体渠道(挂号渠道来源)', 'export_guahao_channel_source' => '自媒体渠道(挂号渠道来源)',
'export_medication_form' => '药品形态', 'export_medication_form' => '药品形态',
'export_prescription_name' => '药方名称',
'export_service_package' => '服务套餐', 'export_service_package' => '服务套餐',
'export_medication_days' => '天数', 'export_medication_days' => '天数',
'export_amount' => '总金额', 'export_amount' => '总金额',
@@ -11,6 +11,7 @@ use app\common\model\Order;
use app\common\model\tcm\Diagnosis; use app\common\model\tcm\Diagnosis;
use app\common\model\tcm\DiagnosisAssignLog; use app\common\model\tcm\DiagnosisAssignLog;
use app\common\model\tcm\Prescription; use app\common\model\tcm\Prescription;
use app\common\model\tcm\PrescriptionLibrary;
use app\common\model\tcm\PrescriptionOrder; use app\common\model\tcm\PrescriptionOrder;
use app\common\model\tcm\PrescriptionOrderLog; use app\common\model\tcm\PrescriptionOrderLog;
use app\common\model\tcm\PrescriptionOrderPayOrder; use app\common\model\tcm\PrescriptionOrderPayOrder;
@@ -3106,7 +3107,7 @@ class PrescriptionOrderLogic
$rxById = []; $rxById = [];
if ($rxIdList !== []) { if ($rxIdList !== []) {
$rxRows = Prescription::whereIn('id', $rxIdList)->whereNull('delete_time') $rxRows = Prescription::whereIn('id', $rxIdList)->whereNull('delete_time')
->field(['id', 'prescription_type', 'need_decoction', 'dose_unit', 'assistant_id', 'appointment_id']) ->field(['id', 'prescription_type', 'need_decoction', 'dose_unit', 'assistant_id', 'appointment_id', 'prescription_name', 'aux_usage', 'herbs', 'creator_id'])
->select() ->select()
->toArray(); ->toArray();
foreach ($rxRows as $xr) { foreach ($rxRows as $xr) {
@@ -3114,6 +3115,10 @@ class PrescriptionOrderLogic
} }
} }
// 药方名称反查:处方库(开方医师 creator_id + 公开模板),按 formula_type + 药材集合匹配,
// 复刻前端 resolveSlipAuxLibraryName 口径(主方/辅方均如此;处方记录 prescription_name 仅做兜底)。
[$libByDoctor, $libPublic] = self::buildPrescriptionLibraryNameIndexForExport($rxById);
if ($needGuahaoChannel) { if ($needGuahaoChannel) {
$yejiChannelHighlightByDiag = []; $yejiChannelHighlightByDiag = [];
if ( if (
@@ -3319,6 +3324,22 @@ class PrescriptionOrderLogic
} }
$item['export_medication_form'] = self::formatMedicationFormForExport(\is_array($rx) ? $rx : []); $item['export_medication_form'] = self::formatMedicationFormForExport(\is_array($rx) ? $rx : []);
// 药方名称:主方/辅方均反查处方库取名称(与处方笺一致),带标签;甘草/无匹配留空
[$mainRxName, $auxRxName] = self::resolvePrescriptionNamesForExport(
\is_array($rx) ? $rx : [],
$libByDoctor,
$libPublic
);
$rxNameParts = [];
if ($mainRxName !== '') {
$rxNameParts[] = '主方:' . $mainRxName;
}
if ($auxRxName !== '') {
$rxNameParts[] = '辅方:' . $auxRxName;
}
$item['export_prescription_name'] = implode(' ', $rxNameParts);
$item['export_service_package'] = self::formatServicePackageForExport( $item['export_service_package'] = self::formatServicePackageForExport(
$item['service_package'] ?? '', $item['service_package'] ?? '',
$packageNameByValue $packageNameByValue
@@ -3391,6 +3412,186 @@ class PrescriptionOrderLogic
unset($item); unset($item);
} }
/**
* 导出药方名称反查:批量加载相关开方医师处方库(creator_id 命中 + 公开模板),
* 建立 [creator_id][formula_type][herbKey] => name 与 公开 [formula_type][herbKey] => name 两级索引。
*
* @param array<int, array> $rxById 处方 id => 处方行(需含 herbs / creator_id
*
* @return array{0: array<int, array<string, array<string, string>>>, 1: array<string, array<string, string>>}
*/
private static function buildPrescriptionLibraryNameIndexForExport(array $rxById): array
{
$libByDoctor = [];
$libPublic = [];
if ($rxById === []) {
return [$libByDoctor, $libPublic];
}
$doctorIds = [];
foreach ($rxById as $rx) {
$cid = (int) ($rx['creator_id'] ?? 0);
if ($cid > 0) {
$doctorIds[$cid] = true;
}
}
$doctorIdList = array_keys($doctorIds);
try {
$libRows = PrescriptionLibrary::whereNull('delete_time')
->where(function ($q) use ($doctorIdList) {
if ($doctorIdList !== []) {
$q->whereIn('creator_id', $doctorIdList)->whereOr('is_public', 1);
} else {
$q->where('is_public', 1);
}
})
->field(['prescription_name', 'formula_type', 'herbs', 'creator_id', 'is_public'])
->select()
->toArray();
} catch (\Throwable $e) {
Log::warning('buildPrescriptionLibraryNameIndexForExport failed: ' . $e->getMessage());
return [$libByDoctor, $libPublic];
}
foreach ($libRows as $lib) {
$name = trim((string) ($lib['prescription_name'] ?? ''));
if ($name === '') {
continue;
}
$herbsRaw = $lib['herbs'] ?? null;
if (\is_string($herbsRaw) && $herbsRaw !== '') {
$decoded = json_decode($herbsRaw, true);
$herbsRaw = \is_array($decoded) ? $decoded : [];
}
if (!\is_array($herbsRaw) || $herbsRaw === []) {
continue;
}
$key = self::herbSetMatchKeyForExport($herbsRaw);
if ($key === '') {
continue;
}
$ft = ((string) ($lib['formula_type'] ?? '')) === '辅方' ? '辅方' : '主方';
if ((int) ($lib['is_public'] ?? 0) === 1) {
$libPublic[$ft][$key] ??= $name;
}
$cid = (int) ($lib['creator_id'] ?? 0);
if ($cid > 0) {
$libByDoctor[$cid][$ft][$key] ??= $name;
}
}
return [$libByDoctor, $libPublic];
}
/**
* 反查单张处方的主方 / 辅方名称(与处方笺展示一致):
* - 主方:主方药材集合匹配开方医师/公开处方库(formula_type=主方);兜底取处方 prescription_name。
* - 辅方:优先 aux_usage.prescription_name / library_name,否则辅方药材集合匹配(formula_type=辅方)。
*
* @param array $rx 处方行(含 herbs / creator_id / prescription_name / aux_usage
* @param array<int, array<string, array<string, string>>> $libByDoctor
* @param array<string, array<string, string>> $libPublic
*
* @return array{0: string, 1: string} [主方名称, 辅方名称]
*/
private static function resolvePrescriptionNamesForExport(array $rx, array $libByDoctor, array $libPublic): array
{
if ($rx === []) {
return ['', ''];
}
$doctorId = (int) ($rx['creator_id'] ?? 0);
$herbs = $rx['herbs'] ?? null;
if (\is_string($herbs) && $herbs !== '') {
$decoded = json_decode($herbs, true);
$herbs = \is_array($decoded) ? $decoded : [];
}
if (!\is_array($herbs)) {
$herbs = [];
}
$mainHerbs = [];
$auxHerbs = [];
foreach ($herbs as $h) {
if (!\is_array($h)) {
continue;
}
if (((string) ($h['formula_type'] ?? '')) === '辅方') {
$auxHerbs[] = $h;
} else {
$mainHerbs[] = $h;
}
}
$lookup = static function (string $ft, array $hs) use ($doctorId, $libByDoctor, $libPublic): string {
if ($hs === []) {
return '';
}
$key = self::herbSetMatchKeyForExport($hs);
if ($key === '') {
return '';
}
if ($doctorId > 0 && isset($libByDoctor[$doctorId][$ft][$key])) {
return $libByDoctor[$doctorId][$ft][$key];
}
return (string) ($libPublic[$ft][$key] ?? '');
};
// 主方
$mainName = $lookup('主方', $mainHerbs);
if ($mainName === '') {
$mainName = trim((string) ($rx['prescription_name'] ?? ''));
}
// 辅方:先读持久化字段
$auxName = '';
$auxUsage = $rx['aux_usage'] ?? null;
if (\is_string($auxUsage) && $auxUsage !== '') {
$decodedAux = json_decode($auxUsage, true);
$auxUsage = \is_array($decodedAux) ? $decodedAux : null;
}
if (\is_array($auxUsage)) {
$auxName = trim((string) ($auxUsage['prescription_name'] ?? ($auxUsage['library_name'] ?? '')));
}
if ($auxName === '') {
$auxName = $lookup('辅方', $auxHerbs);
}
return [$mainName, $auxName];
}
/**
* 药材集合匹配 key:每味 name:dosagedosage 规整去尾零)排序后拼接,与处方库反查双方同口径。
*/
private static function herbSetMatchKeyForExport(array $herbs): string
{
$parts = [];
foreach ($herbs as $h) {
if (!\is_array($h)) {
continue;
}
$name = trim((string) ($h['name'] ?? ''));
if ($name === '') {
continue;
}
$dosage = (float) ($h['dosage'] ?? 0);
if (floor($dosage) === $dosage) {
$dStr = (string) (int) $dosage;
} else {
$dStr = rtrim(rtrim(number_format($dosage, 4, '.', ''), '0'), '.');
}
$parts[] = $name . ':' . $dStr;
}
sort($parts);
return implode('|', $parts);
}
/** /**
* 完成订单时把关联诊单的 assistant_id 清空,让患者回到「待分配」状态 * 完成订单时把关联诊单的 assistant_id 清空,让患者回到「待分配」状态
* 仅当该患者没有其它进行中的处方订单且无指派日志时才清空,避免误覆盖 * 仅当该患者没有其它进行中的处方订单且无指派日志时才清空,避免误覆盖
@@ -16,7 +16,8 @@ class PrescriptionLibraryValidate extends BaseValidate
'prescription_name' => 'require|max:100', 'prescription_name' => 'require|max:100',
'formula_type' => 'in:主方,辅方', 'formula_type' => 'in:主方,辅方',
'herbs' => 'require|array', 'herbs' => 'require|array',
'is_public' => 'in:0,1' 'is_public' => 'in:0,1',
'disable_edit' => 'in:0,1'
]; ];
protected $message = [ protected $message = [
@@ -27,7 +28,8 @@ class PrescriptionLibraryValidate extends BaseValidate
'formula_type.in' => '处方类型只能是主方或辅方', 'formula_type.in' => '处方类型只能是主方或辅方',
'herbs.require' => '药材列表不能为空', 'herbs.require' => '药材列表不能为空',
'herbs.array' => '药材列表格式错误', 'herbs.array' => '药材列表格式错误',
'is_public.in' => '是否公开参数错误' 'is_public.in' => '是否公开参数错误',
'disable_edit.in' => '禁用修改参数错误'
]; ];
/** /**
@@ -35,7 +37,7 @@ class PrescriptionLibraryValidate extends BaseValidate
*/ */
public function sceneAdd() public function sceneAdd()
{ {
return $this->only(['prescription_name', 'formula_type', 'herbs', 'is_public']); return $this->only(['prescription_name', 'formula_type', 'herbs', 'is_public', 'disable_edit']);
} }
/** /**
@@ -43,7 +45,7 @@ class PrescriptionLibraryValidate extends BaseValidate
*/ */
public function sceneEdit() public function sceneEdit()
{ {
return $this->only(['id', 'prescription_name', 'formula_type', 'herbs', 'is_public']); return $this->only(['id', 'prescription_name', 'formula_type', 'herbs', 'is_public', 'disable_edit']);
} }
/** /**
@@ -0,0 +1,3 @@
-- 处方库:禁用修改(导入处方后锁定药材,不可增删改,只能再次导入覆盖)
ALTER TABLE `zyt_prescription_library`
ADD COLUMN `disable_edit` tinyint(1) NOT NULL DEFAULT 0 COMMENT '导入处方后是否禁止修改/删除药材:0否 1是' AFTER `is_public`;