import { ref, reactive, computed } from 'vue' import { useRoute } from 'vue-router' // Components import { toast } from '~/components/pub/ui/toast' // Models import type { TreeItem } from '~/components/pub/my-ui/select-tree/type' import type { DataTableLoader } from '~/components/pub/my-ui/data-table/type' import { paymentTypes, sepRefTypeCodes, participantGroups } from '~/lib/constants.vclaim' import { genDoctor, type Doctor } from '~/models/doctor' // Stores import { useUserStore } from '~/stores/user' // Services import { getList as getSpecialistList, getValueTreeItems as getSpecialistTreeItems, } from '~/services/specialist.service' import { getDetail as getDoctorDetail, getValueLabelList as getDoctorValueLabelList } from '~/services/doctor.service' import { create as createEncounter, getDetail as getEncounterDetail, update as updateEncounter, } from '~/services/encounter.service' import { getList as getMemberList } from '~/services/vclaim-member.service' import { getList as getSepList } from '~/services/vclaim-sep.service' import { uploadAttachment } from '~/services/supporting-document.service' // Handlers import { patients, selectedPatient, selectedPatientObject, paginationMeta, getPatientsList, getPatientCurrent, getPatientByIdentifierSearch, } from '~/handlers/patient.handler' export function useEncounterEntry(props: { id: number classCode?: 'ambulatory' | 'emergency' | 'inpatient' | 'outpatient' subClassCode?: 'reg' | 'rehab' | 'chemo' | 'emg' | 'eon' | 'op' | 'icu' | 'hcu' | 'vk' }) { const route = useRoute() const userStore = useUserStore() const openPatient = ref(false) const isLoading = reactive({ isTableLoading: false, }) const paymentsList = ref>([]) const sepsList = ref>([]) const participantGroupsList = ref>([]) const specialistsTree = ref([]) const specialistsData = ref([]) const doctorsList = ref>([]) const recSelectId = ref(null) const isSaving = ref(false) const isLoadingDetail = ref(false) const encounterData = ref(null) const formObjects = ref({}) const isSepValid = ref(false) const isMemberValid = ref(false) const isCheckingSep = ref(false) const sepNumber = ref('') const vclaimReference = ref(null) const sepFile = ref(null) const sippFile = ref(null) const selectedDoctor = ref(genDoctor()) const isEditMode = computed(() => props.id > 0) const isSaveDisabled = computed(() => { return !selectedPatient.value || !selectedPatientObject.value || isSaving.value || isLoadingDetail.value }) function getListPath(): string { if (props.classCode === 'ambulatory') { return '/ambulatory/encounter' } if (props.classCode === 'emergency') { return '/emergency/encounter' } if (props.classCode === 'inpatient') { return '/inpatient/encounter' } return '/encounter' } function toKebabCase(str: string): string { return str.replace(/([A-Z])/g, '-$1').toLowerCase() } function toNavigateSep(values: any) { const queryParams = new URLSearchParams() if (values['subSpecialistCode']) { const isSub = getIsSubspecialist(values['subSpecialistCode'], specialistsTree.value) if (!isSub) { values['specialistCode'] = values['subSpecialistCode'] delete values['subSpecialistCode'] } } Object.keys(values).forEach((field) => { if (values[field]) { queryParams.append(toKebabCase(field), values[field]) } }) navigateTo('/integration/bpjs-vclaim/sep/add' + `?${queryParams.toString()}`) } function getIsSubspecialist(value: string, items: TreeItem[]): boolean { for (const item of items) { if (item.value === value) { return false } if (item.children) { for (const child of item.children) { if (child.value === value) { return true } } } } return false } function getSpecialistCodeFromId(id: number | null | undefined): string | null { if (!id) return null if (encounterData.value?.specialist?.id === id) { return encounterData.value.specialist.code || null } for (const specialist of specialistsData.value) { if (specialist.id === id) { return specialist.code || null } if (specialist.subspecialists && Array.isArray(specialist.subspecialists)) { for (const subspecialist of specialist.subspecialists) { if (subspecialist.id === id) { return subspecialist.code || null } } } } return null } function getSubspecialistCodeFromId(id: number | null | undefined): string | null { if (!id) return null if (encounterData.value?.subspecialist?.id === id) { return encounterData.value.subspecialist.code || null } for (const specialist of specialistsData.value) { if (specialist.subspecialists && Array.isArray(specialist.subspecialists)) { for (const subspecialist of specialist.subspecialists) { if (subspecialist.id === id) { return subspecialist.code || null } } } } return null } function getSpecialistIdsFromCode(code: string): { specialist_id: number | null; subspecialist_id: number | null } { if (!code) { return { specialist_id: null, subspecialist_id: null } } const isSub = getIsSubspecialist(code, specialistsTree.value) if (isSub) { for (const specialist of specialistsData.value) { if (specialist.subspecialists && Array.isArray(specialist.subspecialists)) { for (const subspecialist of specialist.subspecialists) { if (subspecialist.code === code) { return { specialist_id: specialist.id ? Number(specialist.id) : null, subspecialist_id: subspecialist.id ? Number(subspecialist.id) : null, } } } } } } else { for (const specialist of specialistsData.value) { if (specialist.code === code) { return { specialist_id: specialist.id ? Number(specialist.id) : null, subspecialist_id: null, } } } } return { specialist_id: null, subspecialist_id: null } } async function getDoctorInfo(value: string) { const resp = await getDoctorDetail(value, { includes: 'unit,specialist,subspecialist' }) if (resp.success) { selectedDoctor.value = resp.body.data } } async function getValidateMember(member: string) { if (isCheckingSep.value) return isMemberValid.value = false try { const result = await getMemberList({ mode: 'by-card', number: member, date: new Date().toISOString().split('T')[0], }) if (result.success && result.body?.response !== null) { const response = result.body?.response || {} if (Object.keys(response).length > 0) { formObjects.value.nationalIdentity = response.peserta?.nik || '' } isMemberValid.value = result.body?.metaData?.code === '200' } } catch (error) { console.error('Error checking member:', error) } } async function getValidateSepNumber(sepNumberValue: string) { vclaimReference.value = null if (!sepNumberValue || sepNumberValue.trim() === '') { isSepValid.value = false isCheckingSep.value = false return } try { isSepValid.value = false isCheckingSep.value = true const result = await getSepList({ number: sepNumberValue.trim() }) if (result.success && result.body?.response !== null) { const response = result.body?.response || {} if (Object.keys(response).length > 0) { formObjects.value.patientName = response.peserta?.nama || '-' formObjects.value.medicalRecordNumber = response.peserta?.noMr || '-' formObjects.value.cardNumber = response.peserta?.noKartu || '-' formObjects.value.registerDate = response.tglSep || null formObjects.value.sepReference = response.noSep || '-' formObjects.value.sepControlDate = response.tglSep || null formObjects.value.sepTrafficStatus = response.nmstatusKecelakaan || '-' formObjects.value.diagnosis = response.diagnosa || '-' vclaimReference.value = { noSep: response.noSep || sepNumberValue.trim(), tglRujukan: response.tglSep ? new Date(response.tglSep).toISOString() : null, ppkDirujuk: response.noRujukan || 'rssa', jnsPelayanan: response.jnsPelayanan === 'Rawat Jalan' ? '2' : response.jnsPelayanan === 'Rawat Inap' ? '1' : null, catatan: response.catatan || '', diagRujukan: response.diagnosa || '', tipeRujukan: response.tujuanKunj?.kode ?? '0', poliRujukan: response.poli || '', user: userStore.user?.user_name || '', } } isSepValid.value = result.body?.metaData?.code === '200' isMemberValid.value = isSepValid.value } } catch (error) { console.error('Error checking SEP:', error) isSepValid.value = false } finally { isCheckingSep.value = false } } async function handleFetchSpecialists() { try { const specialistsResult = await getSpecialistList({ 'page-size': 100, includes: 'subspecialists' }) if (specialistsResult.success) { const specialists = specialistsResult.body?.data || [] specialistsData.value = specialists specialistsTree.value = getSpecialistTreeItems(specialists) } } catch (error) { console.error('Error fetching specialist-subspecialist tree:', error) } } async function handleFetchDoctors(subSpecialistId: string | null = null) { try { const filterParams: any = { 'page-size': 100, includes: 'employee-Person,unit,specialist,subspecialist' } if (!subSpecialistId) { doctorsList.value = await getDoctorValueLabelList(filterParams, true) return } const isSub = getIsSubspecialist(subSpecialistId, specialistsTree.value) if (isSub) { filterParams['subspecialist-id'] = subSpecialistId } else { filterParams['specialist-id'] = subSpecialistId } doctorsList.value = await getDoctorValueLabelList(filterParams, true) } catch (error) { console.error('Error fetching doctors:', error) doctorsList.value = [] } } async function handleInit() { selectedPatientObject.value = null paymentsList.value = Object.keys(paymentTypes).map((item) => ({ value: item.toString(), label: paymentTypes[item], })) as any sepsList.value = Object.keys(sepRefTypeCodes).map((item) => ({ value: item.toString(), label: sepRefTypeCodes[item], })) as any participantGroupsList.value = Object.keys(participantGroups).map((item) => ({ value: item.toString(), label: participantGroups[item], })) as any await handleFetchDoctors() await handleFetchSpecialists() if (route.query) { formObjects.value = { ...formObjects.value } const queries = route.query as any if (queries['sep-number']) { formObjects.value.sepNumber = queries['sep-number'] formObjects.value.paymentType = 'jkn' } } } async function getFetchEncounterDetail() { if (!isEditMode.value || props.id <= 0) { return } try { isLoadingDetail.value = true const result = await getEncounterDetail(props.id, { includes: 'patient,patient-person,specialist,subspecialist,Appointment_Doctor,EncounterDocuments', }) if (result.success && result.body?.data) { encounterData.value = result.body.data await mapEncounterToForm(encounterData.value) isLoadingDetail.value = false } else { const errorMsg = result.body?.message || 'Gagal memuat data kunjungan' toast({ title: 'Gagal', description: errorMsg, variant: 'destructive', }) await navigateTo(getListPath()) } } catch (error: any) { toast({ title: 'Gagal', description: error?.message || 'Gagal memuat data kunjungan', variant: 'destructive', }) await navigateTo(getListPath()) } finally { isLoadingDetail.value = false } } async function mapEncounterToForm(encounter: any) { if (!encounter) return if (encounter.patient) { selectedPatient.value = String(encounter.patient.id) selectedPatientObject.value = encounter.patient if (!encounter.patient.person) { await getPatientCurrent(selectedPatient.value) } } const formData: any = {} if (encounter.patient?.person) { formData.patientName = encounter.patient.person.name || '' formData.nationalIdentity = encounter.patient.person.residentIdentityNumber || '' formData.medicalRecordNumber = encounter.patient.number || '' } else if (selectedPatientObject.value?.person) { formData.patientName = selectedPatientObject.value.person.name || '' formData.nationalIdentity = selectedPatientObject.value.person.residentIdentityNumber || '' formData.medicalRecordNumber = selectedPatientObject.value.number || '' } const doctorCode = encounter.appointment_doctor_code || encounter.responsible_doctor_code if (doctorCode) { formData.doctorCode = String(doctorCode) await getDoctorInfo(doctorCode) } if (encounter.subspecialist_id) { const subspecialistCode = getSubspecialistCodeFromId(encounter.subspecialist_id) if (subspecialistCode) { formData.subSpecialistId = subspecialistCode } } else if (encounter.specialist_id) { const specialistCode = getSpecialistCodeFromId(encounter.specialist_id) if (specialistCode) { formData.subSpecialistId = specialistCode } } if (!formData.subSpecialistId) { if (encounter.subspecialist?.code) { formData.subSpecialistId = encounter.subspecialist.code } else if (encounter.specialist?.code) { formData.subSpecialistId = encounter.specialist.code } } if (encounter.registeredAt) { const date = new Date(encounter.registeredAt) formData.registerDate = date.toISOString().split('T')[0] } if (encounter.visitDate) { const date = new Date(encounter.visitDate) formData.registerDate = date.toISOString().split('T')[0] } if (encounter.paymentMethod_code) { formData.paymentMethodCode = encounter.paymentMethod_code if (encounter.paymentMethod_code === 'insurance') { formData.paymentType = 'jkn' } else { const validPaymentTypes = ['jkn', 'jkmm', 'spm', 'pks'] if (validPaymentTypes.includes(encounter.paymentMethod_code)) { formData.paymentType = encounter.paymentMethod_code } else { formData.paymentType = 'spm' } } } else { formData.paymentType = 'spm' } formData.cardNumber = encounter.member_number || '' formData.sepNumber = encounter.ref_number || '' formData.sepType = encounter.sep_type || '' formData.patientCategory = encounter.participant_group_code || '' // Map BPJS reference data if available if (encounter.vclaimReference) { formData.sepReference = encounter.vclaimReference?.noSep || '' } else if (encounter.ref_number) { formData.sepReference = encounter.ref_number } if (formData.sepNumber) { sepNumber.value = formData.sepNumber } if (formData.subSpecialistId) { await handleFetchDoctors(formData.subSpecialistId) } formObjects.value = { ...formData } } async function handleSaveEncounter(formValues: any) { if (!selectedPatient.value || !selectedPatientObject.value) { toast({ title: 'Gagal', description: 'Pasien harus dipilih terlebih dahulu', variant: 'destructive', }) return } try { isSaving.value = true const isAdmin = false const employeeId = userStore.user?.employee_id || userStore.user?.employee?.id || 0 const formatDate = (dateString: string): string => { if (!dateString) return '' const date = new Date(dateString) return date.toISOString() } const { specialist_id, subspecialist_id } = getSpecialistIdsFromCode(formValues.subSpecialistId || '') const patientId = formValues.patient_id || selectedPatientObject.value?.id || Number(selectedPatient.value) const registeredAtValue = formValues.registeredAt || formValues.registerDate || '' const visitDateValue = formValues.visitDate || formValues.registeredAt || formValues.registerDate || '' const memberNumber = formValues.member_number ?? formValues.cardNumber ?? formValues.memberNumber ?? null const refNumber = formValues.ref_number ?? formValues.sepNumber ?? formValues.refNumber ?? null sepFile.value = formValues.sepFile || null sippFile.value = formValues.sippFile || null let paymentMethodCode = formValues.paymentMethod_code ?? null if (!paymentMethodCode) { if (formValues.paymentType === 'jkn' || formValues.paymentType === 'jkmm') { paymentMethodCode = 'insurance' } else if (formValues.paymentType === 'spm') { paymentMethodCode = 'cash' } else if (formValues.paymentType === 'pks') { paymentMethodCode = 'membership' } else { paymentMethodCode = 'cash' } } const payload: any = { patient_id: patientId, appointment_doctor_code: formValues.doctorCode || null, class_code: props.classCode || '', subClass_code: props.subClassCode || '', infra_id: formValues.infra_id ?? null, unit_code: userStore?.user?.unit_code ?? null, refSource_name: formValues.refSource_name ?? 'RSSA', refTypeCode: formValues.paymentType === 'jkn' ? 'bpjs' : '', vclaimReference: vclaimReference.value ?? null, paymentType: formValues.paymentType, registeredAt: formatDate(registeredAtValue), visitDate: formatDate(visitDateValue), } if (props.classCode !== 'inpatient') { delete payload.infra_id } if (employeeId && employeeId > 0) { payload.adm_employee_id = employeeId } if (specialist_id) { payload.specialist_id = specialist_id } if (subspecialist_id) { payload.subspecialist_id = subspecialist_id } if (paymentMethodCode) { payload.paymentMethod_code = paymentMethodCode } if (!payload.vclaimReference) { payload.vclaimReference = { noSep: refNumber, } } if (paymentMethodCode === 'insurance') { payload.insuranceCompany_id = formValues.insuranceCompany_id ?? null if (memberNumber) payload.member_number = memberNumber if (formValues.refTypeCode) payload.refTypeCode = formValues.refTypeCode if (formValues.vclaimReference) payload.vclaimReference = formValues.vclaimReference } else { if (paymentMethodCode === 'membership' && memberNumber) { payload.member_number = memberNumber } } if (isAdmin && props.classCode === 'ambulatory') { payload.visitMode_code = 'adm' payload.allocatedVisitCount = 0 } let result if (isEditMode.value) { result = await updateEncounter(props.id, payload) } else { console.log('💾 [ADD MODE] Sending POST request:', { payload }) result = await createEncounter(payload) } if (result.success) { // const encounterId = isEditMode.value ? props.id : result.body?.data?.id if (patientId) { if (sepFile.value) { await uploadAttachment(sepFile.value, patientId, 'vclaim-sep') } if (sippFile.value) { await uploadAttachment(sippFile.value, patientId, 'vclaim-sipp') } } toast({ title: 'Berhasil', description: isEditMode.value ? 'Kunjungan berhasil diperbarui' : 'Kunjungan berhasil dibuat', variant: 'default', }) console.log('✅ [SAVE] Success - Redirecting to list page') await navigateTo(getListPath()) } else { const errorMessage = result.body?.message || (isEditMode.value ? 'Gagal memperbarui kunjungan' : 'Gagal membuat kunjungan') console.error('❌ [SAVE] Failed:', errorMessage) toast({ title: 'Gagal', description: errorMessage, variant: 'destructive', }) } } catch (error: any) { console.error('❌ [SAVE] Error saving encounter:', error) toast({ title: 'Gagal', description: error?.message || (isEditMode.value ? 'Gagal memperbarui kunjungan' : 'Gagal membuat kunjungan'), variant: 'destructive', }) } finally { isSaving.value = false } } return { patients, paymentsList, sepsList, sepNumber, participantGroupsList, specialistsTree, doctorsList, recSelectId, isSaving, isLoadingDetail, encounterData, formObjects, openPatient, isMemberValid, isSepValid, isCheckingSep, isEditMode, isSaveDisabled, isLoading, selectedDoctor, selectedPatient, selectedPatientObject, paginationMeta, getFetchEncounterDetail, mapEncounterToForm, toKebabCase, toNavigateSep, getListPath, getSpecialistCodeFromId, getSubspecialistCodeFromId, getIsSubspecialist, getSpecialistIdsFromCode, getPatientsList, getPatientCurrent, getPatientByIdentifierSearch, getDoctorInfo, getValidateMember, getValidateSepNumber, handleFetchSpecialists, handleFetchDoctors, handleInit, handleSaveEncounter, } }