* оптимизация обновления при редактировании спец контингента * добавил поддержку заключительных диагнозов * изменил определение законченной операции * добавил поддержку исхода операции * добавил определение отмены для операции через назначение * работа над диапазонами календарей, подсчет статистики * добавил статусы отчетов и подкорректировал привязку спец контингента к отчету * добавил новые сервисы для будущего кеширования * частичное разделение логики подсчета пациентов
270 lines
9.0 KiB
Vue
270 lines
9.0 KiB
Vue
<script setup>
|
|
import {computed, reactive, ref, watch} from "vue";
|
|
import {NButton, NDatePicker, NForm, NFormItem, NInput, NModal, NSelect} from "naive-ui";
|
|
import {useDebounceFn} from "@vueuse/core";
|
|
import {useReportStore} from "../../../Stores/report.js";
|
|
|
|
const props = defineProps({
|
|
patient: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
sourceStatus: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
})
|
|
|
|
const show = defineModel('show', {type: Boolean, default: false})
|
|
const reportStore = useReportStore()
|
|
const formRef = ref(null)
|
|
const submitting = ref(false)
|
|
const mkbLoading = ref(false)
|
|
const mkbOptions = ref([])
|
|
|
|
const form = reactive({
|
|
full_name: '',
|
|
birth_date: null,
|
|
admitted_at: null,
|
|
patient_kind: 'plan',
|
|
manual_status: 'current',
|
|
outcome_at: null,
|
|
diagnosis_code: '',
|
|
diagnosis_name: '',
|
|
})
|
|
|
|
const rules = {
|
|
full_name: {
|
|
required: true,
|
|
message: 'Укажите ФИО',
|
|
trigger: ['blur', 'input'],
|
|
},
|
|
birth_date: {
|
|
required: true,
|
|
type: 'number',
|
|
message: 'Укажите дату рождения',
|
|
trigger: ['change', 'blur'],
|
|
validator: (_rule, value) => value && value <= Date.now(),
|
|
},
|
|
patient_kind: {
|
|
required: true,
|
|
message: 'Укажите тип пациента',
|
|
trigger: ['change'],
|
|
},
|
|
}
|
|
|
|
const patientKindOptions = [
|
|
{label: 'Планово', value: 'plan'},
|
|
{label: 'Экстренно', value: 'emergency'},
|
|
]
|
|
|
|
const manualStatusOptions = [
|
|
{label: 'Состоит', value: 'current'},
|
|
{label: 'Выписан', value: 'discharged'},
|
|
{label: 'Умер', value: 'deceased'},
|
|
]
|
|
|
|
const normalizeSpaces = (value) => value.replace(/\s+/g, ' ').trim()
|
|
const pad = (value) => String(value).padStart(2, '0')
|
|
const formatLocalDate = (timestamp) => {
|
|
const date = new Date(timestamp)
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
|
}
|
|
const formatLocalDateTime = (timestamp) => {
|
|
const date = new Date(timestamp)
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
|
}
|
|
const parseDateToTimestamp = (value) => {
|
|
if (!value) return null
|
|
const parts = String(value).split('.')
|
|
if (parts.length !== 3) return null
|
|
const [day, month, year] = parts.map((item) => Number(item))
|
|
if (!day || !month || !year) return null
|
|
return new Date(year, month - 1, day).getTime()
|
|
}
|
|
const parseDateTimeToTimestamp = (value) => {
|
|
if (!value) return null
|
|
const nativeDate = new Date(value)
|
|
if (!Number.isNaN(nativeDate.getTime())) {
|
|
return nativeDate.getTime()
|
|
}
|
|
|
|
const [datePart, timePart] = String(value).split(' ')
|
|
const dateTimestamp = parseDateToTimestamp(datePart)
|
|
if (!dateTimestamp) return null
|
|
const [hours = 0, minutes = 0] = (timePart ?? '00:00').split(':').map((item) => Number(item))
|
|
const date = new Date(dateTimestamp)
|
|
date.setHours(hours || 0, minutes || 0, 0, 0)
|
|
return date.getTime()
|
|
}
|
|
|
|
const hydrateForm = () => {
|
|
form.full_name = props.patient?.fullname ?? ''
|
|
form.birth_date = parseDateToTimestamp(props.patient?.birth_date)
|
|
form.admitted_at = parseDateTimeToTimestamp(props.patient?.admitted_at)
|
|
form.patient_kind = props.patient?.patient_kind ?? 'plan'
|
|
form.manual_status = props.patient?.outcome_type ?? 'current'
|
|
form.outcome_at = parseDateTimeToTimestamp(props.patient?.outcome_date)
|
|
form.diagnosis_code = props.patient?.mkb?.ds ?? ''
|
|
form.diagnosis_name = props.patient?.mkb?.name ?? ''
|
|
|
|
if (form.diagnosis_code) {
|
|
mkbOptions.value = [{
|
|
label: [form.diagnosis_code, form.diagnosis_name].filter(Boolean).join(' '),
|
|
value: form.diagnosis_code,
|
|
name: form.diagnosis_name,
|
|
}]
|
|
} else {
|
|
mkbOptions.value = []
|
|
}
|
|
}
|
|
|
|
watch(() => show.value, (opened) => {
|
|
if (!opened) return
|
|
hydrateForm()
|
|
}, {immediate: true})
|
|
|
|
watch(() => props.patient, () => {
|
|
if (show.value) {
|
|
hydrateForm()
|
|
}
|
|
})
|
|
|
|
const loadMkbOptions = useDebounceFn(async (query) => {
|
|
if (!query || query.trim().length < 2) {
|
|
mkbOptions.value = []
|
|
return
|
|
}
|
|
|
|
mkbLoading.value = true
|
|
try {
|
|
const items = await reportStore.searchMkb(query.trim())
|
|
mkbOptions.value = (items ?? []).map((item) => ({
|
|
label: item.label,
|
|
value: item.code,
|
|
name: item.name,
|
|
}))
|
|
} finally {
|
|
mkbLoading.value = false
|
|
}
|
|
}, 300)
|
|
|
|
const handleMkbSearch = async (query) => {
|
|
await loadMkbOptions(query)
|
|
}
|
|
|
|
const handleMkbChange = (value, option) => {
|
|
if (value === null || value === undefined || value === '') {
|
|
form.diagnosis_code = ''
|
|
form.diagnosis_name = ''
|
|
return
|
|
}
|
|
|
|
const selected = (!Array.isArray(option) && option)
|
|
? option
|
|
: mkbOptions.value.find((item) => item.value === value)
|
|
|
|
form.diagnosis_code = selected?.value ?? value ?? ''
|
|
form.diagnosis_name = selected?.name ?? ''
|
|
}
|
|
|
|
const isSubmitDisabled = computed(() => submitting.value || !props.patient?.department_patient_id)
|
|
const close = () => {
|
|
show.value = false
|
|
}
|
|
|
|
const submit = async () => {
|
|
if (!props.patient?.department_patient_id || submitting.value) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
await formRef.value?.validate()
|
|
} catch {
|
|
return
|
|
}
|
|
|
|
submitting.value = true
|
|
|
|
try {
|
|
await reportStore.updateManualPatient(
|
|
props.patient.department_patient_id,
|
|
{
|
|
full_name: normalizeSpaces(form.full_name),
|
|
birth_date: formatLocalDate(form.birth_date),
|
|
admitted_at: form.admitted_at ? formatLocalDateTime(form.admitted_at) : null,
|
|
patient_kind: form.patient_kind,
|
|
manual_status: form.manual_status,
|
|
outcome_at: form.manual_status === 'current'
|
|
? null
|
|
: (form.outcome_at ? formatLocalDateTime(form.outcome_at) : null),
|
|
diagnosis_code: form.diagnosis_code ? normalizeSpaces(form.diagnosis_code).toUpperCase() : null,
|
|
diagnosis_name: form.diagnosis_name ? normalizeSpaces(form.diagnosis_name) : null,
|
|
},
|
|
{
|
|
reloadStatuses: [
|
|
props.sourceStatus,
|
|
`special-${form.patient_kind}`,
|
|
'special-outcome-discharged',
|
|
'special-outcome-deceased',
|
|
'special-outcome-transferred',
|
|
],
|
|
}
|
|
)
|
|
|
|
close()
|
|
window.$message.success('Изменения сохранены')
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<NModal v-model:show="show" preset="card" title="Редактирование пациента спецконтингента" class="max-w-xl">
|
|
<NForm ref="formRef" :model="form" :rules="rules" label-placement="top">
|
|
<NFormItem label="ФИО" path="full_name">
|
|
<NInput v-model:value="form.full_name" />
|
|
</NFormItem>
|
|
<NFormItem label="Тип пациента" path="patient_kind">
|
|
<NSelect v-model:value="form.patient_kind" :options="patientKindOptions" />
|
|
</NFormItem>
|
|
<NFormItem label="Статус">
|
|
<NSelect v-model:value="form.manual_status" :options="manualStatusOptions" />
|
|
</NFormItem>
|
|
<NFormItem label="Дата рождения" path="birth_date">
|
|
<NDatePicker v-model:value="form.birth_date" type="date" class="w-full" />
|
|
</NFormItem>
|
|
<NFormItem label="Дата и время поступления">
|
|
<NDatePicker v-model:value="form.admitted_at" type="datetime" class="w-full" clearable />
|
|
</NFormItem>
|
|
<NFormItem v-if="form.manual_status !== 'current'" label="Дата и время исхода">
|
|
<NDatePicker v-model:value="form.outcome_at" type="datetime" class="w-full" clearable />
|
|
</NFormItem>
|
|
<NFormItem label="Диагноз (МКБ)">
|
|
<NSelect
|
|
v-model:value="form.diagnosis_code"
|
|
class="w-full"
|
|
filterable
|
|
clearable
|
|
remote
|
|
:options="mkbOptions"
|
|
:loading="mkbLoading"
|
|
placeholder="Начните ввод кода или названия диагноза"
|
|
@search="handleMkbSearch"
|
|
@update:value="handleMkbChange"
|
|
/>
|
|
</NFormItem>
|
|
</NForm>
|
|
|
|
<template #footer>
|
|
<div class="flex justify-end gap-2">
|
|
<NButton @click="close">Отмена</NButton>
|
|
<NButton type="primary" :loading="submitting" :disabled="isSubmitDisabled" @click="submit">
|
|
Сохранить
|
|
</NButton>
|
|
</div>
|
|
</template>
|
|
</NModal>
|
|
</template>
|