This commit is contained in:
gr
2026-05-14 11:02:29 +08:00
316 changed files with 3430 additions and 447 deletions
@@ -19,6 +19,7 @@ use app\common\model\auth\Admin;
use app\common\service\DataScope\DataScopeService;
use app\common\service\ExpressTrackService;
use app\common\service\gancao\GancaoScmRecipelService;
use think\db\Query;
use think\facade\Config;
use think\facade\Db;
use think\facade\Log;
@@ -268,6 +269,166 @@ class PrescriptionOrderLogic
return self::roleIntersect($adminInfo, $allow);
}
/**
* 与业务订单列表金额统计一致:命中「统计医助」角色且非农单支付审核档时,业务侧按「仅限本人关联诊单的业绩域」收口。
* 提成结算等对业绩归因 SQL 收窄时需与之对齐。
*/
public static function statsRestrictedToOwnAssistantPerformanceDomain(array $adminInfo): bool
{
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
return false;
}
if (self::canViewOrderListStatsAllScope($adminInfo)) {
return false;
}
$assistantRid = (int) Config::get('project.prescription_order_stats_assistant_role_id', 2);
return $assistantRid > 0 && self::roleIntersect($adminInfo, [$assistantRid]);
}
/**
* 提成结算等与列表对齐:按开方医生 / 诊单医助 ∪ 订单创建人 筛选(与 PrescriptionOrderLists::applyDoctorAssistantFilters 在非业绩抽屉场景一致)。
*
* @param array<string, mixed> $params
*/
public static function applyCommissionOrderDoctorAssistantFilters(Query $query, string $alias, array $params): void
{
if (isset($params['doctor_id']) && (int) $params['doctor_id'] > 0) {
$doctorId = (int) $params['doctor_id'];
$rxTbl = (new Prescription())->getTable();
$query->whereExists(
"SELECT 1 FROM `{$rxTbl}` rx WHERE rx.`id` = `{$alias}`.`prescription_id` "
. "AND rx.`delete_time` IS NULL AND rx.`creator_id` = {$doctorId}"
);
}
if (isset($params['assistant_id']) && (int) $params['assistant_id'] > 0) {
$assistantId = (int) $params['assistant_id'];
$diagTbl = (new Diagnosis())->getTable();
$query->where(function ($q) use ($alias, $diagTbl, $assistantId): void {
$q->where("{$alias}.creator_id", $assistantId);
$q->whereOrRaw(
"EXISTS (SELECT 1 FROM `{$diagTbl}` dg WHERE dg.`id` = `{$alias}`.`diagnosis_id`"
. " AND dg.`delete_time` IS NULL AND dg.`assistant_id` = {$assistantId})"
);
});
}
}
/**
* 与处方订单列表 HasDataScopeFilter + applyCreatorOrOwnPrescriptionVisibility 等价;主业务订单别名 $alias。
*
* @param array<string, mixed> $params 可含 assistant_id(显式收窄时校验数据域)
*/
public static function applyPrescriptionOrderListAccessForCommissionQuery(
Query $query,
string $alias,
int $viewerAdminId,
array $viewerAdminInfo,
array $params = []
): void {
if ($viewerAdminId <= 0) {
return;
}
self::applyCommissionOrderDoctorAssistantFilters($query, $alias, $params);
if (self::canSeeAllPrescriptionOrders($viewerAdminInfo)) {
self::applyCommissionOrderDataScope($query, $alias, $viewerAdminId, $viewerAdminInfo);
return;
}
$rxTbl = (new Prescription())->getTable();
$diagTbl = (new Diagnosis())->getTable();
$explicitAssistant = (int) ($params['assistant_id'] ?? 0);
$allowExplicitAssistantVisibility = false;
if ($explicitAssistant > 0) {
if (!DataScopeService::isEnabled()) {
$allowExplicitAssistantVisibility = true;
} else {
$visibleIds = DataScopeService::getVisibleAdminIds($viewerAdminId, $viewerAdminInfo);
if ($visibleIds === null || in_array($explicitAssistant, $visibleIds, true)) {
$allowExplicitAssistantVisibility = true;
}
}
}
$query->where(function ($q) use (
$alias,
$rxTbl,
$diagTbl,
$viewerAdminId,
$explicitAssistant,
$allowExplicitAssistantVisibility,
$viewerAdminInfo
): void {
if (self::canViewOrdersForOwnPrescription($viewerAdminInfo)) {
$q->where(function ($qq) use ($alias, $rxTbl, $diagTbl, $viewerAdminId): void {
$qq->where("{$alias}.creator_id", $viewerAdminId);
$qq->whereOrRaw(
"EXISTS (SELECT 1 FROM `{$rxTbl}` rx WHERE rx.`id` = `{$alias}`.`prescription_id`"
. " AND rx.`delete_time` IS NULL AND rx.`creator_id` = {$viewerAdminId})"
);
$qq->whereOrRaw(
"EXISTS (SELECT 1 FROM `{$diagTbl}` dg WHERE dg.`id` = `{$alias}`.`diagnosis_id`"
. " AND dg.`delete_time` IS NULL AND dg.`assistant_id` = {$viewerAdminId})"
);
});
} else {
$q->where(function ($qq) use ($alias, $diagTbl, $viewerAdminId): void {
$qq->where("{$alias}.creator_id", $viewerAdminId);
$qq->whereOrRaw(
"EXISTS (SELECT 1 FROM `{$diagTbl}` dg WHERE dg.`id` = `{$alias}`.`diagnosis_id`"
. " AND dg.`delete_time` IS NULL AND dg.`assistant_id` = {$viewerAdminId})"
);
});
}
if ($allowExplicitAssistantVisibility) {
$q->whereOr("{$alias}.creator_id", $explicitAssistant);
$q->whereOrRaw(
"EXISTS (SELECT 1 FROM `{$diagTbl}` dg WHERE dg.`id` = `{$alias}`.`diagnosis_id`"
. " AND dg.`delete_time` IS NULL AND dg.`assistant_id` = {$explicitAssistant})"
);
}
});
self::applyCommissionOrderDataScope($query, $alias, $viewerAdminId, $viewerAdminInfo);
}
/**
* 列表数据域:创建人 ∈ 可见 或 诊单医助 ∈ 可见。
*/
private static function applyCommissionOrderDataScope(
Query $query,
string $alias,
int $viewerAdminId,
array $viewerAdminInfo
): void {
if (!DataScopeService::isEnabled()) {
return;
}
$ids = DataScopeService::getVisibleAdminIds($viewerAdminId, $viewerAdminInfo);
if ($ids === null) {
return;
}
if ($ids === []) {
$query->whereRaw('0 = 1');
return;
}
$diagTbl = (new Diagnosis())->getTable();
$inList = implode(',', array_map('intval', $ids));
$query->where(function ($q) use ($alias, $diagTbl, $inList): void {
$q->whereIn("{$alias}.creator_id", explode(',', $inList));
$q->whereOrRaw(
"EXISTS (SELECT 1 FROM `{$diagTbl}` dg WHERE dg.`id` = `{$alias}`.`diagnosis_id` "
. "AND dg.`delete_time` IS NULL AND dg.`assistant_id` IN ({$inList}))"
);
});
}
/**
* @param array<string,mixed> $row
*/
@@ -2263,7 +2424,7 @@ class PrescriptionOrderLogic
return $out;
}
private static function fulfillmentStatusLabel(int $fs): string
public static function fulfillmentStatusLabel(int $fs): string
{
$m = [
1 => '待双审通过',
@@ -2283,6 +2444,209 @@ class PrescriptionOrderLogic
return $m[$fs] ?? ('状态' . (string) $fs);
}
/**
* 导出用:诊单医助在 admin_dept 中的部门路径(多部门「;」、路径内「 / 」)
*/
public static function formatAssistantDeptPathForExport(int $assistantAdminId): string
{
if ($assistantAdminId <= 0) {
return '';
}
$deptIds = Db::name('admin_dept')->where('admin_id', $assistantAdminId)->column('dept_id');
$pathSegments = [];
foreach ($deptIds as $did) {
$p = self::buildDeptPath((int) $did);
if ($p !== '') {
$pathSegments[] = $p;
}
}
$pathSegments = array_values(array_unique($pathSegments));
return $pathSegments === [] ? '' : implode('', $pathSegments);
}
/**
* 导出用:药品形态(丸剂 / 饮片+代煎|自煎等)
*
* @param array<string, mixed> $rx
*/
public static function formatMedicationFormForExport(array $rx): string
{
$type = trim((string) ($rx['prescription_type'] ?? ''));
if ($type === '') {
return '';
}
if ($type === '饮片') {
$nd = (int) ($rx['need_decoction'] ?? 0);
return $type . ($nd === 1 ? '(代煎)' : '(自煎)');
}
return $type;
}
/**
* 业务订单列表导出:写入与 PrescriptionOrderLists::setExcelFields 对应的字段(export=2 时由列表类调用)
*
* @param array<int, array<string, mixed>> $lists
*/
public static function appendPrescriptionOrderListExportRows(array &$lists, array $adminInfo): void
{
if ($lists === []) {
return;
}
$diagIds = [];
$rxIds = [];
$poIds = [];
foreach ($lists as $row) {
$poIds[] = (int) ($row['id'] ?? 0);
$d = (int) ($row['diagnosis_id'] ?? 0);
if ($d > 0) {
$diagIds[$d] = true;
}
$r = (int) ($row['prescription_id'] ?? 0);
if ($r > 0) {
$rxIds[$r] = true;
}
}
$poIds = array_values(array_filter(array_unique($poIds), static fn (int $id): bool => $id > 0));
$diagIdList = array_keys($diagIds);
$rxIdList = array_keys($rxIds);
$diagById = [];
if ($diagIdList !== []) {
$diagRows = Diagnosis::whereIn('id', $diagIdList)->whereNull('delete_time')
->field(['id', 'patient_name', 'gender', 'age', 'phone', 'assistant_id'])
->select()
->toArray();
foreach ($diagRows as $dr) {
$diagById[(int) $dr['id']] = $dr;
}
}
$rxById = [];
if ($rxIdList !== []) {
$rxRows = Prescription::whereIn('id', $rxIdList)->whereNull('delete_time')
->field(['id', 'prescription_type', 'need_decoction', 'dose_unit'])
->select()
->toArray();
foreach ($rxRows as $xr) {
$rxById[(int) $xr['id']] = $xr;
}
}
$logRows = PrescriptionOrderLog::whereIn('prescription_order_id', $poIds)
->whereIn('action', ['audit_rx_approve', 'audit_rx_reject', 'revoke_rx_audit'])
->field(['prescription_order_id', 'id', 'action', 'admin_name', 'create_time'])
->order('id', 'asc')
->select()
->toArray();
$auditEffectiveByPo = [];
foreach ($logRows as $lg) {
$pid = (int) ($lg['prescription_order_id'] ?? 0);
if ($pid <= 0) {
continue;
}
$act = (string) ($lg['action'] ?? '');
if ($act === 'revoke_rx_audit') {
$auditEffectiveByPo[$pid] = null;
continue;
}
if ($act === 'audit_rx_approve' || $act === 'audit_rx_reject') {
$auditEffectiveByPo[$pid] = $lg;
}
}
$assistantDeptCache = [];
foreach ($lists as &$item) {
$poId = (int) ($item['id'] ?? 0);
$fs = (int) ($item['fulfillment_status'] ?? 0);
$item['export_fulfillment_status_text'] = self::fulfillmentStatusLabel($fs);
$ct = (int) ($item['create_time'] ?? 0);
$item['export_order_time'] = $ct > 0 ? date('Y-m-d H:i:s', $ct) : '';
$diagId = (int) ($item['diagnosis_id'] ?? 0);
$dg = $diagById[$diagId] ?? null;
$item['export_patient_name'] = \is_array($dg) ? (string) ($dg['patient_name'] ?? '') : '';
if (\is_array($dg)) {
$g = (int) ($dg['gender'] ?? 0);
$item['export_patient_gender'] = $g === 1 ? '男' : '女';
$item['export_patient_age'] = (string) ($dg['age'] ?? '');
$item['export_patient_phone'] = (string) ($dg['phone'] ?? '');
$astId = (int) ($dg['assistant_id'] ?? 0);
if ($astId > 0) {
if (!isset($assistantDeptCache[$astId])) {
$assistantDeptCache[$astId] = self::formatAssistantDeptPathForExport($astId);
}
$item['export_assistant_dept'] = $assistantDeptCache[$astId];
} else {
$item['export_assistant_dept'] = '';
}
} else {
$item['export_patient_gender'] = '';
$item['export_patient_age'] = '';
$item['export_patient_phone'] = '';
$item['export_assistant_dept'] = '';
}
$rxId = (int) ($item['prescription_id'] ?? 0);
$rx = $rxById[$rxId] ?? [];
$item['export_medication_form'] = self::formatMedicationFormForExport(\is_array($rx) ? $rx : []);
$item['export_channel'] = (string) ($item['service_channel'] ?? '');
$md = $item['medication_days'] ?? null;
$item['export_medication_days'] = $md !== null && $md !== '' ? (string) $md : '';
$amt = round((float) ($item['amount'] ?? 0), 2);
$paid = round((float) ($item['linked_pay_paid_total'] ?? 0), 2);
$item['export_amount'] = number_format($amt, 2, '.', '');
$item['export_paid_amount'] = number_format($paid, 2, '.', '');
$item['export_agency_collect'] = number_format(round($amt - $paid, 2), 2, '.', '');
$item['export_tracking_number'] = (string) ($item['tracking_number'] ?? '');
$isGc = trim((string) ($item['gancao_reciperl_order_no'] ?? '')) !== '';
$item['export_supply_mode'] = $isGc ? '甘草' : '自营';
// 「处方成本」列:与列表/详情一致,取订单 internal_cost(含「测试价格」预报价写入;甘草/自营均导出)
if (self::canViewInternalCost($adminInfo)) {
$rawCost = $item['internal_cost'] ?? null;
$item['export_gancao_prescription_cost'] = $rawCost !== null && $rawCost !== ''
? number_format(round((float) $rawCost, 2), 2, '.', '')
: '';
} else {
$item['export_gancao_prescription_cost'] = '';
}
$isFu = (int) ($item['is_follow_up'] ?? 0) === 1;
$item['export_first_visit_assistant'] = $isFu ? (string) ($item['prev_staff'] ?? '') : '';
$hit = (int) ($item['po_assign_snapshot_hit'] ?? 0);
$rel = (int) ($item['po_assign_is_release'] ?? 0);
$er = (int) ($item['po_assign_to_er_center'] ?? 0);
if ($hit !== 1) {
$item['export_center_label'] = '—';
} elseif ($rel === 1) {
$item['export_center_label'] = '已释放';
} elseif ($er === 1) {
$item['export_center_label'] = '二中心';
} else {
$item['export_center_label'] = '一中心';
}
$paSt = (int) ($item['prescription_audit_status'] ?? 0);
$aud = $auditEffectiveByPo[$poId] ?? null;
if (($paSt === 1 || $paSt === 2) && \is_array($aud)) {
$at = (int) ($aud['create_time'] ?? 0);
$item['export_rx_audit_time'] = $at > 0 ? date('Y-m-d H:i:s', $at) : '';
$item['export_rx_auditor'] = (string) ($aud['admin_name'] ?? '');
} else {
$item['export_rx_audit_time'] = '';
$item['export_rx_auditor'] = '';
}
}
unset($item);
}
/**
* 完成订单时把关联诊单的 assistant_id 清空,让患者回到「待分配」状态
* 仅当该患者没有其它进行中的处方订单且无指派日志时才清空,避免误覆盖