* закончил окно редактирования карты с назначением номера в архиве

* добавил окно для редактирования выдачи / возврата карты
* раздробил логику хранения карты
This commit is contained in:
brusnitsyn
2025-12-05 18:04:02 +09:00
parent 2dfa45707c
commit 2e1b5a3d0e
17 changed files with 431 additions and 13 deletions

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers;
use App\Models\ArchiveHistory;
use Illuminate\Http\Request;
class ArchiveHistoryController extends Controller
{
public function index()
{
}
public function show($id, Request $request)
{
$archiveHistory = ArchiveHistory::find($id);
return response()->json($archiveHistory);
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Resources\ArchiveHistoryResource;
use App\Models\SI\SttMedicalHistory; use App\Models\SI\SttMedicalHistory;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -15,11 +16,13 @@ class MedicalHistoryController extends Controller
$patientInfo = null; $patientInfo = null;
if ($viewType == 'si') { if ($viewType == 'si') {
$patient = SttMedicalHistory::where('id', $id)->first(); $patient = SttMedicalHistory::where('id', $id)->first();
$archiveJournal = $patient->archiveHistory; $archiveJournal = ArchiveHistoryResource::collection($patient->archiveHistory);
$archiveInfo = $patient->archiveInfo;
$patientInfo = [ $patientInfo = [
'info' => $patient, 'info' => $patient,
'journal' => $archiveJournal, 'journal' => $archiveJournal,
'archiveInfo' => $archiveInfo,
]; ];
} }

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Http\Controllers;
use App\Models\Org;
use Illuminate\Http\Request;
class OrgController extends Controller
{
public function index()
{
$orgs = Org::all(['id', 'name', 'code']);
return response()->json($orgs);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Carbon;
class ArchiveHistoryResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'issue_at' => Carbon::parse($this->issue_at)->format('d.m.Y'),
'return_at' => Carbon::parse($this->return_at)->format('d.m.Y'),
'comment' => $this->comment,
'org_id' => $this->org_id,
'org' => $this->org->name,
'employee_name' => $this->employee_name,
'employee_post' => $this->employee_post,
'has_lost' => $this->has_lost,
];
}
}

View File

@@ -22,4 +22,9 @@ class ArchiveHistory extends Model
{ {
return $this->morphTo(); return $this->morphTo();
} }
public function org(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Org::class);
}
} }

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ArchiveInfo extends Model
{
protected $fillable = [
'historyable_type',
'historyable_id',
'num',
'post_in',
'status_id'
];
public function historyable()
{
return $this->morphTo();
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ArchiveStatus extends Model
{
public $timestamps = false;
protected $fillable = [
'text',
'variant',
'has_user_set'
];
}

View File

@@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model;
class Org extends Model class Org extends Model
{ {
protected $fillable = [ protected $fillable = [
'code',
'name' 'name'
]; ];
} }

View File

@@ -3,6 +3,7 @@
namespace App\Models\SI; namespace App\Models\SI;
use App\Models\ArchiveHistory; use App\Models\ArchiveHistory;
use App\Models\ArchiveInfo;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class SttMedicalHistory extends Model class SttMedicalHistory extends Model
@@ -33,6 +34,11 @@ class SttMedicalHistory extends Model
return $this->morphMany(ArchiveHistory::class, 'historyable'); return $this->morphMany(ArchiveHistory::class, 'historyable');
} }
public function archiveInfo()
{
return $this->morphOne(ArchiveInfo::class, 'historyable');
}
public function scopeSearch($query, $searchText) public function scopeSearch($query, $searchText)
{ {
return $query->where(function($q) use ($searchText) { return $query->where(function($q) use ($searchText) {

View File

@@ -0,0 +1,28 @@
<?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::table('orgs', function (Blueprint $table) {
$table->string('code')->after('id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('orgs', function (Blueprint $table) {
$table->dropColumn('code');
});
}
};

View File

@@ -0,0 +1,29 @@
<?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('archive_statuses', function (Blueprint $table) {
$table->id();
$table->string('text');
$table->string('variant');
$table->boolean('has_user_set')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('archive_statuses');
}
};

View File

@@ -0,0 +1,31 @@
<?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('archive_infos', function (Blueprint $table) {
$table->id();
$table->morphs('historyable');
$table->string('num');
$table->date('post_in');
$table->foreignIdFor(\App\Models\ArchiveStatus::class, 'status_id')->default(1);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('archive_infos');
}
};

View File

@@ -0,0 +1,31 @@
<?php
namespace Database\Seeders;
use App\Models\ArchiveStatus;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class ArchiveStatusSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
ArchiveStatus::create([
'text' => 'Не определен',
'variant' => 'default'
]);
ArchiveStatus::create([
'text' => 'В архиве',
'variant' => 'success'
]);
ArchiveStatus::create([
'text' => 'Выдана',
'variant' => 'warning'
]);
}
}

View File

@@ -15,11 +15,8 @@ class DatabaseSeeder extends Seeder
*/ */
public function run(): void public function run(): void
{ {
// User::factory(10)->create(); $this->call([
ArchiveStatusSeeder::class,
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
]); ]);
} }
} }

View File

@@ -1,5 +1,6 @@
<script setup> <script setup>
import { NModal, NDataTable } from 'naive-ui' import { NModal, NDataTable, NSpace, NFlex, NButton, NForm, NFormItem, NInput, NDatePicker, NDivider, NSwitch, NTag } from 'naive-ui'
import ArchiveHistoryMoveModal from '../ArchiveHistoryMoveModal/Index.vue'
import {ref, watch} from "vue"; import {ref, watch} from "vue";
const open = defineModel('open') const open = defineModel('open')
@@ -11,6 +12,13 @@ const props = defineProps({
const loading = ref(true) const loading = ref(true)
const patient = ref({}) const patient = ref({})
const showArchiveHistoryModal = ref(false)
const selectedArchiveHistoryId = ref(null)
const patientData = ref({
...patient.value.info
})
const loadPatientData = async () => { const loadPatientData = async () => {
if (!props.patientId) return if (!props.patientId) return
@@ -19,6 +27,7 @@ const loadPatientData = async () => {
try { try {
axios.get(`/api/si/patients/${props.patientId}`).then(res => { axios.get(`/api/si/patients/${props.patientId}`).then(res => {
patient.value = res.data patient.value = res.data
patientData.value = res.data.info
}) })
} catch (error) { } catch (error) {
// message.error('Ошибка при загрузке данных пациента') // message.error('Ошибка при загрузке данных пациента')
@@ -28,28 +37,43 @@ const loadPatientData = async () => {
} }
} }
const onShowArchiveHistoryModal = (id) => {
selectedArchiveHistoryId.value = id
showArchiveHistoryModal.value = true
}
const columns = [ const columns = [
{ {
title: 'Выдача', title: 'Выдача',
key: 'issue_at' key: 'issue_at',
width: 100
}, },
{ {
title: 'Возврат', title: 'Возврат',
key: 'return_at' key: 'return_at',
width: 100
}, },
{ {
title: 'Организация', title: 'Организация',
key: 'org_id' key: 'org',
width: 220
}, },
{ {
title: 'ФИО', title: 'ФИО',
key: 'employee_name' key: 'employee_name',
width: 180
}, },
{ {
title: 'Должность', title: 'Должность',
key: 'employee_post' key: 'employee_post',
width: 120
}, },
] ]
const rowProps = (row) => ({
onDblclick: () => {
onShowArchiveHistoryModal(row.id)
}
})
// Наблюдаем за изменением patientId // Наблюдаем за изменением patientId
watch(() => props.patientId, (newId) => { watch(() => props.patientId, (newId) => {
@@ -64,8 +88,42 @@ watch(() => props.patientId, (newId) => {
<template #header> <template #header>
{{ patient.info?.medcardnum }} {{ patient.info?.family }} {{ patient.info?.name }} {{ patient.info?.ot }} {{ patient.info?.medcardnum }} {{ patient.info?.family }} {{ patient.info?.name }} {{ patient.info?.ot }}
</template> </template>
<NDataTable :columns="columns" :data="patient?.journal" /> <NSpace vertical>
<NFlex inline justify="space-between" align="center" :wrap="false">
<NForm inline :show-feedback="false">
<NFormItem label="Статус карты">
<NTag type="default" :bordered="false" round>
Не определен
</NTag>
</NFormItem>
<NFormItem label="№ в архиве">
<NInput v-model:value="patientData.narhiv" />
</NFormItem>
<NFormItem label="Дата поступления карты в архив">
<NDatePicker v-model:value="patientData.datearhiv" format="dd.MM.yyyy" />
</NFormItem>
</NForm>
</NFlex>
<NButton @click="onShowArchiveHistoryModal(null)">
Добавить
</NButton>
<NDataTable :row-props="rowProps" min-height="420px" max-height="420px" :columns="columns" :data="patient?.journal" />
</NSpace>
<template #action>
<NFlex justify="end" align="center">
<NSpace align="center" :size="0">
<NButton secondary>
Закрыть без сохранения
</NButton>
<NDivider vertical />
<NButton secondary type="primary">
Сохранить
</NButton>
</NSpace>
</NFlex>
</template>
</NModal> </NModal>
<ArchiveHistoryMoveModal @close-without-save="selectedArchiveHistoryId = null" v-model:open="showArchiveHistoryModal" :archive-history-id="selectedArchiveHistoryId" />
</template> </template>
<style scoped> <style scoped>

View File

@@ -0,0 +1,115 @@
<script setup>
import {NButton, NDatePicker, NDivider, NFlex, NForm, NFormItem, NInput, NModal, NSpace, NSwitch, NSelect} from "naive-ui";
import {ref, watch} from "vue";
const open = defineModel('open')
const props = defineProps({
archiveHistoryId: {
type: Number
}
})
const emits = defineEmits(['closeWithoutSave'])
const loading = ref(false)
const archiveHistory = ref({
issue_at: null,
org_id: null,
return_at: null,
employee_name: null,
employee_post: null,
comment: null,
has_lost: false,
})
const orgs = ref([])
const onResetData = () => {
archiveHistory.value = {
issue_at: null,
org_id: null,
return_at: null,
employee_name: null,
employee_post: null,
comment: null,
has_lost: false,
}
}
const onCloseWithoutSave = () => {
emits('closeWithoutSave')
open.value = false
}
const loadArchiveHistoryData = async () => {
try {
axios.get('/api/orgs').then(res => {
orgs.value = res.data
})
if (!props.archiveHistoryId) return
axios.get(`/api/archive/histories/${props.archiveHistoryId}`).then(res => {
archiveHistory.value = res.data
})
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}
// Наблюдаем за изменением archiveHistoryId
watch(() => props.archiveHistoryId, (newId) => {
if (newId) {
loadArchiveHistoryData()
} else {
onResetData()
}
}, { immediate: true })
</script>
<template>
<NModal v-model:show="open" preset="card" class="max-w-2xl" closable @close="open = false">
<template #header>
{{ archiveHistoryId === null ? 'Добавить' : 'Редактировать' }} запись выдачи
</template>
<NForm>
<NFormItem label="Дата выдачи">
<NDatePicker v-model:value="archiveHistory.issue_at" format="dd.MM.yyyy" />
</NFormItem>
<NFormItem label="Дата возврата">
<NDatePicker v-model:value="archiveHistory.return_at" format="dd.MM.yyyy" />
</NFormItem>
<NFormItem label="Организация">
<NSelect v-model:value="archiveHistory.org_id" :options="orgs" label-field="name" value-field="id" filterable />
</NFormItem>
<NFormItem label="Имя сотрудника">
<NInput v-model:value="archiveHistory.employee_name" />
</NFormItem>
<NFormItem label="Должность">
<NInput v-model:value="archiveHistory.employee_post" />
</NFormItem>
<NFormItem label="Примечание">
<NInput v-model:value="archiveHistory.comment" type="textarea" rows="3" :resizable="false" />
</NFormItem>
</NForm>
<template #action>
<NFlex justify="space-between" align="center">
<NFormItem :show-feedback="false" label-placement="left" label="Карта утеряна">
<NSwitch v-model:value="archiveHistory.has_lost" />
</NFormItem>
<NSpace align="center" :size="0">
<NButton secondary @click="onCloseWithoutSave">
Закрыть без сохранения
</NButton>
<NDivider vertical />
<NButton secondary type="primary">
Сохранить
</NButton>
</NSpace>
</NFlex>
</template>
</NModal>
</template>
<style scoped>
</style>

View File

@@ -12,4 +12,14 @@ Route::prefix('si')->group(function () {
Route::get('{id}', [\App\Http\Controllers\MedicalHistoryController::class, 'patient']); Route::get('{id}', [\App\Http\Controllers\MedicalHistoryController::class, 'patient']);
}); });
}); });
Route::prefix('archive')->group(function () {
Route::prefix('histories')->group(function () {
Route::get('{id}', [\App\Http\Controllers\ArchiveHistoryController::class, 'show']);
});
});
Route::prefix('orgs')->group(function () {
Route::get('/', [\App\Http\Controllers\OrgController::class, 'index']);
});
//Route::get('') //Route::get('')