262 lines
11 KiB
PHP
262 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
// Exercise the real lists/count methods with synthetic in-memory rows only.
|
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
|
|
|
final class AssistantNameQuery
|
|
{
|
|
public static array $tables = [];
|
|
public static array $adminReads = [];
|
|
private array $conditions = [];
|
|
private ?array $fields = null;
|
|
private int $offset = 0;
|
|
private ?int $length = null;
|
|
|
|
public function __construct(private string $table) {}
|
|
|
|
public function where($field, $operator = null, $value = null): self
|
|
{
|
|
if (is_array($field)) {
|
|
foreach ($field as $condition) {
|
|
$this->where(...$condition);
|
|
}
|
|
return $this;
|
|
}
|
|
if (func_num_args() === 2) {
|
|
$value = $operator;
|
|
$operator = '=';
|
|
}
|
|
if ($operator === 'in') {
|
|
return $this->whereIn($field, $value);
|
|
}
|
|
if ($operator !== '=') {
|
|
throw new RuntimeException('Unexpected fixture operator: ' . $operator);
|
|
}
|
|
$this->conditions[] = [$field, '=', $value];
|
|
return $this;
|
|
}
|
|
|
|
public function whereIn(string $field, array $values): self
|
|
{
|
|
$this->conditions[] = [$field, 'in', $values];
|
|
return $this;
|
|
}
|
|
|
|
public function whereNull(string $field): self
|
|
{
|
|
$this->conditions[] = [$field, 'null', null];
|
|
return $this;
|
|
}
|
|
|
|
public function whereRaw(string $sql): self
|
|
{
|
|
if (!in_array($sql, ['0 = 1', '(assistant_id IS NULL OR assistant_id = 0)'], true)) {
|
|
throw new RuntimeException('Unexpected fixture SQL: ' . $sql);
|
|
}
|
|
$this->conditions[] = ['', 'raw', $sql];
|
|
return $this;
|
|
}
|
|
|
|
public function with(array $relations): self { return $this; }
|
|
public function append(array $fields): self { return $this; }
|
|
public function orderRaw(string $order): self { return $this; }
|
|
public function order(string $field, string $direction): self { return $this; }
|
|
public function group(string $field): self { return $this; }
|
|
public function getTableFields(): array { return []; }
|
|
|
|
public function field($fields): self
|
|
{
|
|
$this->fields = is_array($fields) ? $fields : array_map('trim', explode(',', $fields));
|
|
return $this;
|
|
}
|
|
|
|
public function limit(int $offset, int $length): self
|
|
{
|
|
$this->offset = $offset;
|
|
$this->length = $length;
|
|
return $this;
|
|
}
|
|
|
|
private function rows(): array
|
|
{
|
|
$rows = array_values(array_filter(self::$tables[$this->table] ?? [], function (array $row): bool {
|
|
foreach ($this->conditions as [$field, $operator, $value]) {
|
|
$actual = $row[$field] ?? null;
|
|
if (($operator === '=' && $actual != $value)
|
|
|| ($operator === 'in' && !in_array($actual, $value))
|
|
|| ($operator === 'null' && $actual !== null)
|
|
|| ($operator === 'raw' && ($value === '0 = 1' || ($row['assistant_id'] ?? 0) != 0))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}));
|
|
return array_slice($rows, $this->offset, $this->length);
|
|
}
|
|
|
|
public function select(): think\Collection
|
|
{
|
|
if ($this->table === 'admin') {
|
|
throw new RuntimeException('Assistant enrichment must not read full admin records');
|
|
}
|
|
$rows = $this->rows();
|
|
if ($this->fields !== null) {
|
|
$rows = array_map(fn (array $row): array => array_intersect_key($row, array_flip($this->fields)), $rows);
|
|
}
|
|
return new think\Collection($rows);
|
|
}
|
|
|
|
public function column(string $field, string $key = ''): array
|
|
{
|
|
if ($this->table === 'admin') {
|
|
self::$adminReads[] = ['conditions' => $this->conditions, 'field' => $field, 'key' => $key];
|
|
}
|
|
return array_column($this->rows(), $field, $key !== '' ? $key : null);
|
|
}
|
|
|
|
public function count(): int { return count($this->rows()); }
|
|
}
|
|
|
|
class AssistantNameModel
|
|
{
|
|
protected const TABLE = 'empty';
|
|
public function getTable(): string { return static::TABLE; }
|
|
public static function where(...$args): AssistantNameQuery
|
|
{
|
|
return (new AssistantNameQuery(static::TABLE))->where(...$args);
|
|
}
|
|
public static function whereIn(...$args): AssistantNameQuery
|
|
{
|
|
return (new AssistantNameQuery(static::TABLE))->whereIn(...$args);
|
|
}
|
|
}
|
|
final class AssistantNameDiagnosis extends AssistantNameModel { protected const TABLE = 'tcm_diagnosis'; }
|
|
final class AssistantNameAdmin extends AssistantNameModel { protected const TABLE = 'admin'; }
|
|
final class AssistantNameRoles extends AssistantNameModel { protected const TABLE = 'admin_role'; }
|
|
final class AssistantNameAppointment extends AssistantNameModel { protected const TABLE = 'doctor_appointment'; }
|
|
final class AssistantNameDb
|
|
{
|
|
public static function name(string $table): AssistantNameQuery { return new AssistantNameQuery($table); }
|
|
}
|
|
final class AssistantNameRevisitDue
|
|
{
|
|
public static function buildListPayload(...$args): array { return []; }
|
|
public static function emptyPayload(): array { return []; }
|
|
}
|
|
|
|
class_alias(AssistantNameDiagnosis::class, 'app\\common\\model\\tcm\\Diagnosis');
|
|
class_alias(AssistantNameAdmin::class, 'app\\common\\model\\auth\\Admin');
|
|
class_alias(AssistantNameRoles::class, 'app\\common\\model\\auth\\AdminRole');
|
|
class_alias(AssistantNameAppointment::class, 'app\\common\\model\\doctor\\Appointment');
|
|
foreach (['tcm\\Prescription', 'tcm\\CallRecord', 'tcm\\BloodRecord', 'tcm\\DietRecord',
|
|
'tcm\\ExerciseRecord', 'dict\\DictData'] as $model) {
|
|
class_alias(AssistantNameModel::class, 'app\\common\\model\\' . $model);
|
|
}
|
|
class_alias(AssistantNameDb::class, 'think\\facade\\Db');
|
|
class_alias(AssistantNameRevisitDue::class, 'app\\common\\service\\tcm\\RevisitDueService');
|
|
|
|
final class AssistantNameLists extends app\adminapi\lists\tcm\DiagnosisLists
|
|
{
|
|
public function __construct(private ?array $visibleIds = null, array $params = [], int $offset = 0,
|
|
int $length = 100, int $actor = 91)
|
|
{
|
|
$this->params = $params;
|
|
$this->adminId = $actor;
|
|
$this->adminInfo = ['admin_id' => $actor];
|
|
$this->searchWhere = [];
|
|
$this->limitOffset = $offset;
|
|
$this->limitLength = $length;
|
|
}
|
|
protected function dataScopeShouldApply(): bool { return true; }
|
|
protected function getDataScopeVisibleAdminIds(): ?array { return $this->visibleIds; }
|
|
}
|
|
|
|
$assertions = 0;
|
|
function assistantNameExpect(bool $ok, string $message): void
|
|
{
|
|
global $assertions;
|
|
if (!$ok) {
|
|
throw new RuntimeException('FAIL: ' . $message);
|
|
}
|
|
$assertions++;
|
|
}
|
|
|
|
// Input order is already the normal no-appointment descending-id order.
|
|
AssistantNameQuery::$tables = [
|
|
'tcm_diagnosis' => [
|
|
['id' => 109, 'assistant_id' => 41],
|
|
['id' => 108, 'assistant_id' => '42'],
|
|
['id' => 107, 'assistant_id' => 41],
|
|
['id' => 106, 'assistant_id' => null],
|
|
['id' => 105, 'assistant_id' => 0],
|
|
['id' => 104, 'assistant_id' => 404],
|
|
['id' => 103, 'assistant_id' => 43],
|
|
['id' => 102, 'assistant_id' => 44],
|
|
['id' => 101, 'assistant_id' => -1],
|
|
],
|
|
'admin' => [
|
|
['id' => 41, 'name' => ' 林医助 ', 'delete_time' => null, 'disable' => 0, 'account' => 'private-41'],
|
|
['id' => 42, 'name' => '王医助', 'delete_time' => null, 'disable' => 1, 'account' => 'private-42'],
|
|
['id' => 43, 'name' => '已删除医助', 'delete_time' => 123, 'disable' => 0],
|
|
['id' => 44, 'name' => ' ', 'delete_time' => null, 'disable' => 0],
|
|
['id' => 45, 'name' => '未关联人员', 'delete_time' => null, 'disable' => 0],
|
|
],
|
|
];
|
|
$lists = new AssistantNameLists();
|
|
$rows = $lists->lists();
|
|
assistantNameExpect(array_column($rows, 'assistant_name', 'id') === [
|
|
109 => '林医助', 108 => '王医助', 107 => '林医助', 106 => '', 105 => '',
|
|
104 => '', 103 => '', 102 => '', 101 => '',
|
|
], 'each row gets the assigned name; unassigned, missing, deleted, blank and invalid assignments remain empty');
|
|
assistantNameExpect(AssistantNameQuery::$adminReads === [[
|
|
'conditions' => [['id', 'in', [41, 42, 404, 43, 44]], ['delete_time', 'null', null]],
|
|
'field' => 'name', 'key' => 'id',
|
|
]], 'one batch reads only positive unique assigned ids and the name/id projection');
|
|
assistantNameExpect($rows[1]['assistant_id'] === '42' && $rows[1]['assistant_name'] === '王医助',
|
|
'disabled but still assigned staff retain their name without changing the original id');
|
|
assistantNameExpect(!str_contains(json_encode($rows, JSON_UNESCAPED_UNICODE), 'private-')
|
|
&& !str_contains(json_encode($rows, JSON_UNESCAPED_UNICODE), '未关联人员'),
|
|
'enrichment exposes neither admin account fields nor unrelated names');
|
|
assistantNameExpect($lists->count() === count($rows), 'enrichment does not change total count');
|
|
|
|
AssistantNameQuery::$adminReads = [];
|
|
$scoped = new AssistantNameLists([41]);
|
|
$rows = $scoped->lists();
|
|
assistantNameExpect(array_column($rows, 'id') === [109, 107] && $scoped->count() === 2,
|
|
'real data-scope filtering remains active for lists and count');
|
|
assistantNameExpect(AssistantNameQuery::$adminReads[0]['conditions'][0] === ['id', 'in', [41]],
|
|
'names outside the authorized rows are never queried');
|
|
|
|
AssistantNameQuery::$adminReads = [];
|
|
$page = new AssistantNameLists(null, [], 1, 1);
|
|
$rows = $page->lists();
|
|
assistantNameExpect(array_column($rows, 'id') === [108] && $rows[0]['assistant_name'] === '王医助'
|
|
&& $page->count() === 9, 'pagination and count are preserved');
|
|
assistantNameExpect(AssistantNameQuery::$adminReads[0]['conditions'][0] === ['id', 'in', [42]],
|
|
'only names on the requested page are queried');
|
|
|
|
AssistantNameQuery::$tables['admin_role'] = [['admin_id' => 41, 'role_id' => 2]];
|
|
AssistantNameQuery::$adminReads = [];
|
|
$self = new AssistantNameLists(null, [], 0, 100, 41);
|
|
assistantNameExpect(array_column($self->lists(), 'id') === [109, 107] && $self->count() === 2,
|
|
'assistant-role self filtering is unchanged');
|
|
assistantNameExpect(AssistantNameQuery::$adminReads[0]['conditions'][0] === ['id', 'in', [41]],
|
|
'assistant-role name hydration uses the self-filtered result only');
|
|
AssistantNameQuery::$tables['admin_role'] = [];
|
|
|
|
foreach ([new AssistantNameLists([]), new AssistantNameLists(null, [], 50, 10)] as $empty) {
|
|
AssistantNameQuery::$adminReads = [];
|
|
assistantNameExpect($empty->lists() === [] && AssistantNameQuery::$adminReads === [],
|
|
'empty authorization scope or an empty page does not query admin data');
|
|
}
|
|
AssistantNameQuery::$adminReads = [];
|
|
$pending = new AssistantNameLists(null, ['pending_assign' => 1]);
|
|
$rows = $pending->lists();
|
|
assistantNameExpect(array_column($rows, 'id') === [106, 105] && $pending->count() === 2
|
|
&& array_column($rows, 'assistant_name') === ['', ''] && AssistantNameQuery::$adminReads === [],
|
|
'unassigned-only lists have an explicit empty name and need no admin query');
|
|
|
|
echo "Diagnosis assistant names: {$assertions} assertions passed (no DB or network)\n";
|