1
0
Fork 0

patient user_id

master
panliang 2023-10-14 14:31:04 +08:00
parent 1bae9b8f52
commit 647c0807d0
18 changed files with 430 additions and 73 deletions

View File

@ -2,6 +2,7 @@
namespace App\Admin\Controllers;
use App\Admin\Services\UserService;
use App\Enums\Gender;
use App\Admin\Components;
use App\Admin\Services\PatientService;
@ -30,7 +31,7 @@ class PatientController extends AdminController
->filterTogglable(false)
->columnsTogglable(false)
->headerToolbar([
$this->createButton(true, 'lg'),
$this->createButton(),
amis('reload')->align('right'),
$this->exportAction(),
])
@ -65,11 +66,22 @@ class PatientController extends AdminController
{
return $this->baseForm()->body([
amisMake()->SelectControl()->options($this->getTypeOptions())->name('type_id')->label(__('patient.type_id'))->required(),
amisMake()->TextControl()->name('name')->label(__('patient.name'))->required(true),
amisMake()->SelectControl()->options(Gender::options())->name('sex')->label(__('patient.sex')),
amisMake()->TextControl()->name('phone')->label(__('patient.phone')),
amisMake()->TextControl()->name('address')->label(__('patient.address')),
amisMake()->DateControl()->name('birthday')->label(__('patient.birthday')),
amisMake()->SelectControl()->source(admin_url('/api/user/list'))->labelField('phone')->valueField('id')->name('user_id')->label(__('patient.user_id'))->clearable()->onEvent([
'change' => [
'actions' => [
['actionType' => 'setValue', 'componentId' => 'patient_name', 'args' => ['value' => '${event.data.selectedItems.name}']],
['actionType' => 'setValue', 'componentId' => 'patient_sex', 'args' => ['value' => '${event.data.selectedItems.sex}']],
['actionType' => 'setValue', 'componentId' => 'patient_phone', 'args' => ['value' => '${event.data.selectedItems.phone}']],
['actionType' => 'setValue', 'componentId' => 'patient_address', 'args' => ['value' => '${event.data.selectedItems.address}']],
['actionType' => 'setValue', 'componentId' => 'patient_birthday', 'args' => ['value' => '${event.data.selectedItems.birthday}']],
]
]
]),
amisMake()->TextControl()->name('name')->label(__('patient.name'))->id('patient_name')->required(true),
amisMake()->SelectControl()->options(Gender::options())->name('sex')->label(__('patient.sex'))->id('patient_sex'),
amisMake()->TextControl()->name('phone')->label(__('patient.phone'))->id('patient_phone'),
amisMake()->TextControl()->name('address')->label(__('patient.address'))->id('patient_address'),
amisMake()->DateControl()->name('birthday')->label(__('patient.birthday'))->id('patient_birthday'),
amisMake()->DateControl()->name('treat_at')->label(__('patient.treat_at')),
amisMake()->SelectControl()->options($this->getAdminUserOptions())->searchable()->name('doctor_id')->label(__('patient.doctor_id')),
amisMake()->SelectControl()->options($this->getAdminUserOptions())->searchable()->name('inviter_id')->label(__('patient.inviter_id')),

View File

@ -0,0 +1,157 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Services\UserService;
use App\Enums\Gender;
use Illuminate\Http\Request;
use Slowlyo\OwlAdmin\Controllers\AdminController;
use Slowlyo\OwlAdmin\Renderers\Form;
use Slowlyo\OwlAdmin\Renderers\Page;
use Slowlyo\OwlAdmin\Support\Excel\AdminExport;
class UserController extends AdminController
{
protected string $serviceName = UserService::class;
public function list(): Page
{
$crud = $this->baseCRUD()
->filterTogglable(false)
->columnsTogglable(false)
->alwaysShowPagination()
->headerToolbar([
$this->createButton(true),
$this->exportAction(),
])
->filter($this->baseFilter()->actions()->body([
amisMake()->TextControl()->name('keyword')->label(__('user.keyword'))->size('md')->placeholder(__('user.phone') . '或' . __('user.name'))->clearable(),
// amisMake()->Button()->label(__('admin.reset'))->actionType('clear-and-submit'),
amisMake()->Component()->setType('submit')->label(__('admin.search'))->level('primary'),
]))
->columns([
amisMake()->TableColumn()->name('id')->label(__('user.id')),
amisMake()->TableColumn()->name('phone')->label(__('user.phone')),
amisMake()->TableColumn()->name('name')->label(__('user.name')),
amisMake()->TableColumn()->name('sex_text')->label(__('user.sex')),
amisMake()->TableColumn()->name('age')->label(__('user.age')),
amisMake()->TableColumn()->name('address')->label(__('user.address')),
amisMake()->TableColumn()->name('created_at')->label(__('user.created_at')),
$this->rowActions(true),
]);
return $this->baseList($crud);
}
public function form($isEdit): Form
{
return $this->baseForm()->body([
amisMake()->TextControl()->name('phone')->label(__('user.phone'))->required(),
amisMake()->TextControl()->set('type', 'input-password')->name('password')->label(__('user.password'))->required(!$isEdit),
amisMake()->TextControl()->name('name')->label(__('user.name')),
amisMake()->SelectControl()->options(Gender::options())->name('sex')->label(__('user.sex')),
amisMake()->TextControl()->name('address')->label(__('user.address')),
amisMake()->DateControl()->name('birthday')->label(__('user.birthday')),
]);
}
public function detail()
{
return $this->baseDetail()->body([
amisMake()->TextControl()->name('id')->label(__('user.id'))->static(),
amisMake()->TextControl()->name('phone')->label(__('user.phone'))->static(),
amisMake()->TextControl()->name('name')->label(__('user.name'))->static(),
amisMake()->TextControl()->name('sex_text')->label(__('user.sex'))->static(),
amisMake()->TextControl()->name('age')->label(__('user.age'))->static(),
amisMake()->DateControl()->name('birthday')->label(__('user.birthday'))->static(),
amisMake()->TextControl()->name('address')->label(__('user.address'))->static(),
amisMake()->DateTimeControl()->name('created_at')->label(__('user.created_at'))->static(),
]);
}
public function getList(Request $request)
{
$list = $this->service->listQuery()->get();
return $this->response()->success($list);
}
protected function exportAction($disableSelectedItem = false)
{
$event = fn($script) => ['click' => ['actions' => [['actionType' => 'custom', 'script' => $script]]]];
$downloadPath = '/' . admin_url('_download_export', true);
$exportPath = $this->getExportPath();
$doAction = <<<JS
doAction([
{ actionType: "ajax", args: { api: { url: url.toString(), method: "get" } } },
{
actionType: "custom",
expression: "\${event.data.responseResult.responseStatus === 0}",
script: "window.open('{$downloadPath}?path='+event.data.responseResult.responseData.path)"
}
])
JS;
$buttons = [
amisMake()->VanillaAction()->label(__('admin.export.all'))->onEvent(
$event(<<<JS
let url = new URL("{$exportPath}", window.location.origin)
let param = window.location.href.split('?')[1]
if (param) {
url = url + '&' + param
}
{$doAction}
JS
)
),
];
return amisMake()
->DropdownButton()
->label(__('admin.export.title'))
->set('icon', 'fa-solid fa-download')
->buttons($buttons)
->align('right')
->closeOnClick();
}
protected function export()
{
// 默认在 storage/app/ 下
$path = sprintf('%s-%s.xlsx', $this->exportFileName(), date('YmdHis'));
// 导出本页和导出选中项都是通过 _ids 查询
$ids = request()->input('_ids');
// listQuery() 为列表查询条件,与获取列表数据一致
$query = $this->service->listQuery()
->when($ids, fn($query) => $query->whereIn($this->service->primaryKey(), explode(',', $ids)));
// 此处使用 laravel-excel 导出,可自行修改
AdminExport::make($query)
->setHeadings([
__('user.id'),
__('user.phone'),
__('user.name'),
__('user.sex'),
__('user.age'),
__('user.birthday'),
__('user.address'),
__('user.created_at'),
])
->setMap(function ($item) {
return [
$item->id,
$item->phone,
$item->name,
$item->sex_text,
$item->age,
$item->birthday_format,
$item->address,
$item->created_at,
];
})
->store($path);
return $this->response()->success(compact('path'));
}
}

View File

@ -3,6 +3,7 @@
namespace App\Admin\Services;
use App\ModelFilters\PatientFilter;
use Illuminate\Validation\Rule;
use App\Models\{Patient, PatientRecord};
use Illuminate\Support\Facades\Validator;
@ -10,7 +11,7 @@ class PatientService extends BaseService
{
protected string $modelName = Patient::class;
protected array $withRelationships = ['doctor', 'inviter', 'saler', 'type'];
protected array $withRelationships = ['doctor', 'inviter', 'saler', 'type', 'user'];
protected string $modelFilterName = PatientFilter::class;
@ -53,13 +54,22 @@ class PatientService extends BaseService
*/
public function validate($data, $model = null)
{
if (!$model) {
$validator = Validator::make($data, [
'name' => ['required'],
]);
if ($validator->fails()) {
return $validator->errors()->first();
}
$userRule = Rule::unique('patients', 'user_id');
$createRule = [
'type_id' => 'required',
'name' => 'required',
'user_id' => $userRule->where('type_id', data_get($data, 'type_id'))
];
$updateRule = [
'user_id' => $userRule->where('type_id', data_get($data, 'type_id', $model?->type_id))
];
$validator = Validator::make($data, $model ? $updateRule : $createRule, [
'type_id.required' => __('patient.type_id') . '必填',
'name.required' => __('patient.name') . '必填',
'user_id.unique' => __('patient.user_id') . '已经存在',
]);
if ($validator->fails()) {
return $validator->errors()->first();
}
return true;
}

View File

@ -0,0 +1,53 @@
<?php
namespace App\Admin\Services;
use App\ModelFilters\UserFilter;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class UserService extends BaseService
{
protected string $modelName = User::class;
protected array $withRelationships = [];
protected string $modelFilterName = UserFilter::class;
public function listQuery()
{
$filter = $this->getModelFilter();
$query = $this->query();
if ($this->withRelationships) {
$query->with($this->withRelationships);
}
if ($filter) {
$query->filter(request()->input(), $filter);
}
return $query->sort();
}
public function validate($data, $model = null)
{
$phoneRule = Rule::unique('users', 'phone');
if ($model) {
$phoneRule->ignore($model->id);
}
$validator = Validator::make($data, [
'phone' => ['required', 'phone', $phoneRule],
], [
'phone.required' => __('user.phone') . '必填',
'phone.phone' => __('user.phone') . ' 不是合法手机号',
'phone.unique' => __('user.phone') . '已经存在',
]);
if ($validator->fails()) {
return $validator->errors()->first();
}
return true;
}
}

View File

@ -32,8 +32,9 @@ Route::group([
$router->get('category/content', '\App\Admin\Controllers\CategoryController@getContent')->name('api.category.content');
$router->get('patient/options', '\App\Admin\Controllers\PatientController@getSelectOptions')->name('api.patient.options');
$router->get('category/permission-list', '\App\Admin\Controllers\CategoryController@getPermissionList')->name('api.category.permission_list');
$router->get('user/list', '\App\Admin\Controllers\UserController@getList');
});
$router->resource('user', \App\Admin\Controllers\UserController::class)->names('admin.user');
// 字典表
$router->resource('keywords', \App\Admin\Controllers\KeywordsController::class)->names('admin.keywords');
// 分类管理

View File

@ -30,7 +30,7 @@ class PatientRecordNotify extends Command
public function handle()
{
$now = now();
$list = PatientRecord::with(['patient'])
$list = PatientRecord::with(['patient.user'])
->where('is_notified', 0)
->whereNotNull('notify_user_id')
->whereNotNull('next_treat_at')
@ -46,12 +46,15 @@ class PatientRecordNotify extends Command
));
$api = $app->getClient();
// 微信公众号关联账户
$users = UserSocialite::where('user_type', (new AdminUser)->getMorphClass())->where('type', SocialiteType::WxOfficial)->whereIn('user_id', $list->pluck('notify_user_id'))->get();
$adminUsers = UserSocialite::where('user_type', (new AdminUser)->getMorphClass())
->where('type', SocialiteType::WxOfficial)
->whereIn('user_id', $list->pluck('notify_user_id'))
->get();
foreach ($list as $item) {
$user = $users->firstWhere('user_id', $item->notify_user_id);
if ($user) {
$adminUser = $adminUsers->firstWhere('user_id', $item->notify_user_id);
if ($adminUser) {
$response = $api->postJson('/cgi-bin/message/template/send', [
'touser' => $user->openid,
'touser' => $adminUser->openid,
'template_id' => 'zdkOoIk7bfyzpW9Tuu-pxqh2no-93FCcqstFKLOTfu0',
'url' => url('/h5/pages/record/detail?id=' . $item->id),
'data' => [
@ -70,8 +73,8 @@ class PatientRecordNotify extends Command
logger('病历记录: 通知, 模板消息发送, 失败', $response->toArray(false));
continue;
}
$item->update(['is_notified' => 1]);
}
$item->update(['is_notified' => 1]);
}
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\ModelFilters;
use EloquentFilter\ModelFilter;
class UserFilter extends ModelFilter
{
/**
* Related Models that have ModelFilters as well as the method on the ModelFilter
* As [relationMethod => [input_key1, input_key2]].
*
* @var array
*/
public $relations = [];
public function phone($key)
{
$this->whereLike('phone', $key);
}
public function name($key)
{
$this->whereLike('name', $key);
}
public function keyword($key)
{
$str = '%'.$key.'%';
$this->where(fn ($q) => $q->where('phone', 'like', $str)->orWhere('name', 'like', $str));
}
public function ignoreType($key)
{
$this->whereDoesntHave('patients', fn ($q) => $q->where('type_id', $key));
}
}

View File

@ -17,9 +17,10 @@ class Patient extends Model
use HasDateTimeFormatter, Filterable;
protected $fillable = [
'type_id', 'user_id',
'name', 'sex', 'phone', 'address', 'birthday',
'treat_at', 'illness', 'remarks', 'images',
'doctor_id', 'inviter_id', 'saler_id', 'type_id',
'doctor_id', 'inviter_id', 'saler_id',
];
protected $appends = ['age', 'sex_text', 'treat_format', 'birthday_format'];
@ -59,6 +60,11 @@ class Patient extends Model
);
}
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
public function doctor()
{
return $this->belongsTo(AdminUser::class, 'doctor_id');

View File

@ -2,43 +2,57 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use App\Enums\Gender;
use App\Traits\HasDateTimeFormatter;
use EloquentFilter\Filterable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Foundation\Auth\User as Authenticate;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
class User extends Authenticate
{
use HasApiTokens, HasFactory, Notifiable;
use HasApiTokens, Filterable, HasDateTimeFormatter;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
];
protected $fillable = ['address', 'birthday', 'name', 'password', 'phone', 'sex'];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
protected $appends = ['age', 'sex_text', 'birthday_format'];
protected $hidden = ['password'];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'sex' => Gender::class,
'birthday' => 'date',
'password' => 'hashed',
];
public function patients()
{
return $this->hasMany(Patient::class, 'user_id');
}
protected function age(): Attribute
{
return new Attribute(
get: fn () => $this->birthday ? $this->birthday->diffInYears() : '',
);
}
protected function sexText(): Attribute
{
return new Attribute(
get: fn () => $this->sex ? $this->sex->text() : '',
);
}
protected function birthdayFormat(): Attribute
{
return new Attribute(
get: fn () => $this->birthday ? $this->birthday->format('Y-m-d') : '',
);
}
public function scopeSort($q)
{
return $q->orderBy('created_at', 'desc');
}
}

View File

@ -3,6 +3,7 @@
namespace Database\Factories;
use App\Models\Keyword;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\Patient;
use App\Enums\Gender;
@ -23,13 +24,16 @@ class PatientFactory extends Factory
public function definition(): array
{
$faker = $this->faker;
$type_id = Keyword::where('type_key', 'treat_type')->inRandomOrder()->value('id');
$user = User::query()->inRandomOrder()->first();
return [
'type_id' => Keyword::where('type_key', 'treat_type')->inRandomOrder()->value('id'),
'name' => $faker->name,
'sex' => $faker->randomElement(array_keys(Gender::map())),
'phone' => $faker->phoneNumber,
'address' => $faker->address,
'birthday' => $faker->dateTimeBetween('-30 years', '-10 years'),
'type_id' => $type_id,
'user_id' => $user->id,
'name' => $user->name,
'sex' => $user->sex,
'phone' => $user->phone,
'address' => $user->address,
'birthday' => $user->birthday,
'treat_at' => $faker->dateTimeBetween('-7 days'),
'illness' => '基本稳定',
'doctor_id' => AdminUser::inRandomOrder()->value('id'),

View File

@ -2,14 +2,14 @@
namespace Database\Factories;
use App\Enums\Gender;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
protected $model = User::class;
/**
* Define the model's default state.
*
@ -17,12 +17,14 @@ class UserFactory extends Factory
*/
public function definition(): array
{
$faker = $this->faker;
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
'address' => $faker->address,
'birthday' => $faker->dateTimeBetween('-30 years', '-10 years'),
'name' => $faker->name,
'password' => '$10$MXFSBvoV02d.nMg/5//5ru/os27JgSJhsePidDr/uiIw6UQ.MigP2',
'phone' => $faker->phoneNumber,
'sex' => $faker->randomElement(array_keys(Gender::map()))
];
}

View File

@ -14,8 +14,8 @@ return new class extends Migration
{
Schema::create('patients', function (Blueprint $table) {
$table->id();
$table->string('name')->comment('姓名');
$table->unsignedBigInteger('type_id')->comment('类别, keywords.id');
$table->string('name')->comment('姓名');
$table->string('sex')->default(Gender::None->value)->comment('性别');
$table->string('phone')->nullable()->comment('联系方式');
$table->string('address')->nullable()->comment('地址');

View File

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('phone')->comment('联系方式')->unique();
$table->string('password')->comment('登录密码');
$table->string('name')->nullable()->comment('姓名');
$table->string('sex')->default(\App\Enums\Gender::None->value)->comment('性别');
$table->string('address')->nullable()->comment('地址');
$table->date('birthday')->nullable()->comment('出生年月');
$table->timestamps();
});
Schema::table('patients', function (Blueprint $table) {
$table->unsignedBigInteger('user_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::table('patients', function (Blueprint $table) {
$table->dropColumn('user_id');
});
}
};

View File

@ -20,6 +20,7 @@ class AdminMenuSeeder extends Seeder
$menus = [
['title' => '主页', 'icon' => 'icon-park:home-two', 'url' => '/dashboard', 'is_home' => 1],
['title' => '分类管理', 'icon' => 'icon-park:all-application', 'url' => '/category'],
['title' => '客户管理', 'icon' => 'icon-park:user', 'url' => '/user'],
['title' => '病人管理', 'icon' => 'icon-park:peoples-two', 'url' => '/patient'],
['title' => '病历管理', 'icon' => 'icon-park:newspaper-folding', 'url' => '/record'],
['title' => '客户统计', 'icon' => 'icon-park:user-positioning', 'url' => '/total/record'],

View File

@ -4,8 +4,8 @@ namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Database\Factories\{PatientFactory, PatientRecordFactory};
use App\Models\{Patient, PatientRecord};
use Database\Factories\{PatientFactory, PatientRecordFactory, UserFactory};
use App\Models\{Patient, PatientRecord, User};
class PatientSeeder extends Seeder
{
@ -14,9 +14,11 @@ class PatientSeeder extends Seeder
*/
public function run(): void
{
User::truncate();
Patient::truncate();
PatientRecord::truncate();
(new PatientFactory())->count(100)->create();
(new PatientRecordFactory())->count(1000)->create();
(new UserFactory())->count(50)->create();
(new PatientFactory())->count(80)->create();
(new PatientRecordFactory())->count(200)->create();
}
}

View File

@ -20,7 +20,7 @@ return [
'sell_price' => '实收价',
'order_status' => '收费情况',
'notify_at' => '通知时间',
'notify_user_id' => '通知',
'notify_user_id' => '通知医师',
'notify_remarks' => '通知备注',
'is_notified' => '开启通知',
'next_treat_at' => '下次就诊时间',

View File

@ -19,4 +19,5 @@ return [
'remarks' => '备注',
'created_at' => '录入时间',
'images' => '图片资料',
'user_id' => '客户',
];

View File

@ -0,0 +1,14 @@
<?php
return [
'id' => 'ID',
'phone' => '手机号',
'name' => '姓名',
'sex' => '性别',
'address' => '地址',
'birthday' => '出生年月',
'age' => '年龄',
'created_at' => '注册时间',
'password' => '登录密码',
'keyword' => '关键字',
];