From 28cd110dae345a378c64135218d5b801d47adcef Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 12 Aug 2026 15:22:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../logic/tcm/PrescriptionOrderLogic.php | 62 ++++-- ...scription_order_export_name_regression.php | 198 ++++++++++-------- 2 files changed, 153 insertions(+), 107 deletions(-) diff --git a/server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php b/server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php index 283a8ce2a..9135f14fa 100755 --- a/server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php +++ b/server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php @@ -3963,9 +3963,9 @@ class PrescriptionOrderLogic } } - // 药方名称反查:处方库(开方医师 creator_id + 公开模板),按 formula_type + 药材集合匹配, - // 复刻前端 resolveSlipAuxLibraryName 口径(主方/辅方均如此;处方记录 prescription_name 仅做兜底)。 - [$libByDoctor, $libPublic] = self::buildPrescriptionLibraryNameIndexForExport($rxById); + // 药方名称反查:处方库(开方医师 → 公开 → 全库同药材集合兜底),按 formula_type + 药材集合匹配。 + // 辅方模板额外全量加载,避免「系统代开 / 非模板所属医师」因 creator 范围过窄丢辅方名。 + [$libByDoctor, $libPublic, $libAny] = self::buildPrescriptionLibraryNameIndexForExport($rxById); if ($needGuahaoChannel) { $yejiChannelHighlightByDiag = []; @@ -4174,11 +4174,12 @@ class PrescriptionOrderLogic $item['export_medication_form'] = self::formatMedicationFormForExport(\is_array($rx) ? $rx : []); - // 药方名称:主方/辅方均反查处方库取名称(与处方笺一致),带标签;甘草/无匹配留空 + // 药方名称:主方/辅方均反查处方库取名称(与处方笺一致),带标签;有辅方药材时即使未匹配到库名也保留「辅方」标记 [$mainRxName, $auxRxName] = self::resolvePrescriptionNamesForExport( \is_array($rx) ? $rx : [], $libByDoctor, - $libPublic + $libPublic, + $libAny ); $rxNameParts = []; if ($mainRxName !== '') { @@ -4298,19 +4299,24 @@ class PrescriptionOrderLogic } /** - * 导出药方名称反查:批量加载相关开方医师处方库(creator_id 命中 + 公开模板), - * 建立 [creator_id][formula_type][herbKey] => name 与 公开 [formula_type][herbKey] => name 两级索引。 + * 导出药方名称反查:批量加载相关开方医师处方库(creator_id 命中 + 公开模板 + 全量辅方模板), + * 建立三级索引:医师私有 / 公开 / 任意已加载(跨医师同药材集合兜底)。 * * @param array $rxById 处方 id => 处方行(需含 herbs / creator_id) * - * @return array{0: array>>, 1: array>} + * @return array{ + * 0: array>>, + * 1: array>, + * 2: array> + * } */ private static function buildPrescriptionLibraryNameIndexForExport(array $rxById): array { $libByDoctor = []; $libPublic = []; + $libAny = []; if ($rxById === []) { - return [$libByDoctor, $libPublic]; + return [$libByDoctor, $libPublic, $libAny]; } $doctorIds = []; @@ -4323,12 +4329,15 @@ class PrescriptionOrderLogic $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); + $q->whereIn('creator_id', $doctorIdList) + ->whereOr('is_public', 1) + ->whereOr('formula_type', '辅方'); } else { - $q->where('is_public', 1); + $q->where('is_public', 1)->whereOr('formula_type', '辅方'); } }) ->field(['prescription_name', 'formula_type', 'herbs', 'creator_id', 'is_public']) @@ -4337,7 +4346,7 @@ class PrescriptionOrderLogic } catch (\Throwable $e) { Log::warning('buildPrescriptionLibraryNameIndexForExport failed: ' . $e->getMessage()); - return [$libByDoctor, $libPublic]; + return [$libByDoctor, $libPublic, $libAny]; } foreach ($libRows as $lib) { @@ -4359,6 +4368,9 @@ class PrescriptionOrderLogic } $ft = ((string) ($lib['formula_type'] ?? '')) === '辅方' ? '辅方' : '主方'; + // 跨医师兜底:同药材集合优先保留先写入的名称(稳定、可预期) + $libAny[$ft][$key] ??= $name; + if ((int) ($lib['is_public'] ?? 0) === 1) { $libPublic[$ft][$key] ??= $name; } @@ -4368,23 +4380,28 @@ class PrescriptionOrderLogic } } - return [$libByDoctor, $libPublic]; + return [$libByDoctor, $libPublic, $libAny]; } /** * 反查单张处方的主方 / 辅方名称(与处方笺展示一致): - * - 主方:主方药材集合匹配开方医师/公开处方库(formula_type=主方);兜底取处方 prescription_name。 + * - 主方:主方药材集合匹配 开方医师 → 公开 → 任意已加载;兜底取处方 prescription_name。 * - 辅方:仅在实际存在辅方药材时,优先 aux_usage.prescription_name / library_name, - * 否则按辅方药材集合匹配(formula_type=辅方)。 + * 否则按辅方药材集合匹配;仍无匹配时回落为「辅方」(保证导出名含辅方标记)。 * * @param array $rx 处方行(含 herbs / creator_id / prescription_name / aux_usage) * @param array>> $libByDoctor * @param array> $libPublic + * @param array> $libAny * * @return array{0: string, 1: string} [主方名称, 辅方名称] */ - private static function resolvePrescriptionNamesForExport(array $rx, array $libByDoctor, array $libPublic): array - { + private static function resolvePrescriptionNamesForExport( + array $rx, + array $libByDoctor, + array $libPublic, + array $libAny = [] + ): array { if ($rx === []) { return ['', '']; } @@ -4393,7 +4410,7 @@ class PrescriptionOrderLogic [$mainHerbs, $auxHerbs] = self::splitPrescriptionHerbsFromRx($rx); - $lookup = static function (string $ft, array $hs) use ($doctorId, $libByDoctor, $libPublic): string { + $lookup = static function (string $ft, array $hs) use ($doctorId, $libByDoctor, $libPublic, $libAny): string { if ($hs === []) { return ''; } @@ -4404,8 +4421,11 @@ class PrescriptionOrderLogic if ($doctorId > 0 && isset($libByDoctor[$doctorId][$ft][$key])) { return $libByDoctor[$doctorId][$ft][$key]; } + if (isset($libPublic[$ft][$key])) { + return $libPublic[$ft][$key]; + } - return (string) ($libPublic[$ft][$key] ?? ''); + return (string) ($libAny[$ft][$key] ?? ''); }; // 主方 @@ -4432,6 +4452,10 @@ class PrescriptionOrderLogic if ($auxName === '') { $auxName = $lookup('辅方', $auxHerbs); } + // 有辅方药材但未匹配到库名/持久化名时,仍输出标记,避免导出名静默丢掉辅方 + if ($auxName === '') { + $auxName = '辅方'; + } return [$mainName, $auxName]; } diff --git a/server/tests/pharmacy/prescription_order_export_name_regression.php b/server/tests/pharmacy/prescription_order_export_name_regression.php index cc0eafc27..0d0bd1d94 100644 --- a/server/tests/pharmacy/prescription_order_export_name_regression.php +++ b/server/tests/pharmacy/prescription_order_export_name_regression.php @@ -1,88 +1,110 @@ - '黄芪', 'dosage' => 10, 'formula_type' => '主方']; -$auxHerb = ['name' => '龙骨', 'dosage' => 15, 'formula_type' => '辅方']; - -$assertSame( - ['主方名', ''], - $resolveNames->invoke(null, [ - 'prescription_name' => '主方名', - 'herbs' => [$mainHerb], - 'aux_usage' => ['prescription_name' => '已删除的辅方名'], - ], [], []), - 'export must ignore a stale auxiliary prescription name when no auxiliary herbs exist' -); - -$assertSame( - ['主方名', ''], - $resolveNames->invoke(null, [ - 'prescription_name' => '主方名', - 'herbs' => json_encode([$mainHerb], JSON_UNESCAPED_UNICODE), - 'aux_usage' => json_encode(['library_name' => '已删除的处方库辅方名'], JSON_UNESCAPED_UNICODE), - ], [], []), - 'JSON-backed export data must ignore a stale auxiliary library name when no auxiliary herbs exist' -); - -$assertSame( - ['主方名', ''], - $resolveNames->invoke(null, [ - 'prescription_name' => '主方名', - 'creator_id' => 7, - 'herbs' => [$mainHerb], - 'aux_usage' => ['prescription_name' => '残留名称', 'usage_days' => 7], - ], [ - 7 => [ - '辅方' => ['龙骨:15' => '不应命中的处方库辅方名'], - ], - ], [ - '辅方' => ['龙骨:15' => '不应命中的公开辅方名'], - ]), - 'stale auxiliary usage and library indexes must not imply that an auxiliary formula exists' -); - -$assertSame( - ['主方名', '持久化辅方名'], - $resolveNames->invoke(null, [ - 'prescription_name' => '主方名', - 'herbs' => [$mainHerb, $auxHerb], - 'aux_usage' => json_encode(['prescription_name' => '持久化辅方名'], JSON_UNESCAPED_UNICODE), - ], [], []), - 'export must keep the persisted auxiliary prescription name when auxiliary herbs exist' -); - -$assertSame( - ['主方名', '处方库辅方名'], - $resolveNames->invoke(null, [ - 'prescription_name' => '主方名', - 'creator_id' => 7, - 'herbs' => [$mainHerb, $auxHerb], - ], [ - 7 => [ - '辅方' => ['龙骨:15' => '处方库辅方名'], - ], - ], []), - 'export must still resolve an auxiliary prescription name from the library when auxiliary herbs exist' -); - -fwrite(STDOUT, sprintf("Prescription order export name regression tests passed: %d\n", $passed)); + '黄芪', 'dosage' => 10, 'formula_type' => '主方']; +$auxHerb = ['name' => '龙骨', 'dosage' => 15, 'formula_type' => '辅方']; + +$assertSame( + ['主方名', ''], + $resolveNames->invoke(null, [ + 'prescription_name' => '主方名', + 'herbs' => [$mainHerb], + 'aux_usage' => ['prescription_name' => '已删除的辅方名'], + ], [], []), + 'export must ignore a stale auxiliary prescription name when no auxiliary herbs exist' +); + +$assertSame( + ['主方名', ''], + $resolveNames->invoke(null, [ + 'prescription_name' => '主方名', + 'herbs' => json_encode([$mainHerb], JSON_UNESCAPED_UNICODE), + 'aux_usage' => json_encode(['library_name' => '已删除的处方库辅方名'], JSON_UNESCAPED_UNICODE), + ], [], []), + 'JSON-backed export data must ignore a stale auxiliary library name when no auxiliary herbs exist' +); + +$assertSame( + ['主方名', ''], + $resolveNames->invoke(null, [ + 'prescription_name' => '主方名', + 'creator_id' => 7, + 'herbs' => [$mainHerb], + 'aux_usage' => ['prescription_name' => '残留名称', 'usage_days' => 7], + ], [ + 7 => [ + '辅方' => ['龙骨:15' => '不应命中的处方库辅方名'], + ], + ], [ + '辅方' => ['龙骨:15' => '不应命中的公开辅方名'], + ]), + 'stale auxiliary usage and library indexes must not imply that an auxiliary formula exists' +); + +$assertSame( + ['主方名', '持久化辅方名'], + $resolveNames->invoke(null, [ + 'prescription_name' => '主方名', + 'herbs' => [$mainHerb, $auxHerb], + 'aux_usage' => json_encode(['prescription_name' => '持久化辅方名'], JSON_UNESCAPED_UNICODE), + ], [], []), + 'export must keep the persisted auxiliary prescription name when auxiliary herbs exist' +); + +$assertSame( + ['主方名', '处方库辅方名'], + $resolveNames->invoke(null, [ + 'prescription_name' => '主方名', + 'creator_id' => 7, + 'herbs' => [$mainHerb, $auxHerb], + ], [ + 7 => [ + '辅方' => ['龙骨:15' => '处方库辅方名'], + ], + ], []), + 'export must still resolve an auxiliary prescription name from the library when auxiliary herbs exist' +); + +$assertSame( + ['系统代开', '失眠(辅方)'], + $resolveNames->invoke(null, [ + 'prescription_name' => '系统代开', + 'creator_id' => 99, + 'herbs' => [$mainHerb, $auxHerb], + ], [], [], [ + '辅方' => ['龙骨:15' => '失眠(辅方)'], + ]), + 'export must resolve auxiliary name via cross-doctor libAny when creator has no private/public hit' +); + +$assertSame( + ['系统代开', '辅方'], + $resolveNames->invoke(null, [ + 'prescription_name' => '系统代开', + 'creator_id' => 99, + 'herbs' => [$mainHerb, $auxHerb], + ], [], [], []), + 'export must still mark 辅方 when auxiliary herbs exist but no library/persisted name matches' +); + +fwrite(STDOUT, sprintf("Prescription order export name regression tests passed: %d\n", $passed));