Skip to content

Commit 206e70e

Browse files
committed
feat: add CPPT template management for CP module
- Create new database table mlite_clinical_pathway_cppt_template for storing SOAP/CPPT templates linked to ICD codes - Add full CRUD admin interface for creating, editing, and deleting CPPT templates - Integrate automatic display of matching templates on clinical pathway printouts - Update upgrade scripts for database schema version 6.3.5 - Update admin navigation and dashboard text to include the new feature - Update .gitignore to exclude .trae/ directory
1 parent e2fd20d commit 206e70e

8 files changed

Lines changed: 452 additions & 365 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.DS_Store
2+
.trae/
23
config.php
34
admin/tmp
45
admin/pla.php

mlite_db.sql

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2377,6 +2377,24 @@ CREATE TABLE `mlite_clinical_pathway_audit` (
23772377
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
23782378

23792379

2380+
CREATE TABLE `mlite_clinical_pathway_cppt_template` (
2381+
`id` int(11) NOT NULL AUTO_INCREMENT,
2382+
`kd_penyakit` varchar(10) NOT NULL,
2383+
`ppra` varchar(100) NOT NULL,
2384+
`subjective` text NOT NULL,
2385+
`objective` text NOT NULL,
2386+
`assessment` text NOT NULL,
2387+
`plan` text NOT NULL,
2388+
`aktif` enum('Ya','Tidak') NOT NULL DEFAULT 'Ya',
2389+
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
2390+
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
2391+
PRIMARY KEY (`id`),
2392+
UNIQUE KEY `cppt_template_kd_penyakit` (`kd_penyakit`),
2393+
KEY `cppt_template_aktif` (`aktif`),
2394+
CONSTRAINT `fk_cppt_template_penyakit` FOREIGN KEY (`kd_penyakit`) REFERENCES `penyakit` (`kd_penyakit`) ON UPDATE CASCADE
2395+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
2396+
2397+
23802398
CREATE TABLE `mlite_mini_pacs_instance` (
23812399
`id` int NOT NULL AUTO_INCREMENT,
23822400
`series_id` int NOT NULL,

plugins/clinical_pathway/Admin.php

Lines changed: 196 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public function navigation()
2525
'Dashboard' => 'manage',
2626
'Master CP' => 'master',
2727
'Template Harian' => 'template',
28+
'Template CPPT' => 'cppttemplate',
2829
'Mapping ICD' => 'mapping',
2930
'Evidence Engine' => 'evidence',
3031
'Generator CP' => 'generator',
@@ -142,6 +143,42 @@ public function getDeleteactivity($id)
142143
redirect(url([ADMIN, 'clinical_pathway', 'template']) . '&cp_id=' . $cpId);
143144
}
144145

146+
public function anyCppttemplate()
147+
{
148+
$this->_addHeaderFiles();
149+
150+
$selectedId = (int) ($_GET['id'] ?? 0);
151+
$selected = $selectedId ? $this->getCpptTemplateRow($selectedId) : [];
152+
153+
return $this->draw('cppt.template.html', [
154+
'selected' => htmlspecialchars_array($selected),
155+
'rows' => htmlspecialchars_array($this->getCpptTemplateList()),
156+
'ppra_options' => $this->getCpptPpraOptions($selected['ppra'] ?? '')
157+
]);
158+
}
159+
160+
public function postSavecppttemplate()
161+
{
162+
$result = $this->saveCpptTemplate($_POST);
163+
164+
$this->notify($result['status'] ? 'success' : 'failure', $result['message']);
165+
redirect(url([ADMIN, 'clinical_pathway', 'cppttemplate']));
166+
}
167+
168+
public function getDeletecppttemplate($id)
169+
{
170+
$row = $this->db('mlite_clinical_pathway_cppt_template')->oneArray('id', (int) $id);
171+
172+
if ($row) {
173+
$this->db('mlite_clinical_pathway_cppt_template')->delete((int) $id);
174+
$this->notify('success', 'Template CPPT berhasil dihapus.');
175+
} else {
176+
$this->notify('failure', 'Template CPPT tidak ditemukan.');
177+
}
178+
179+
redirect(url([ADMIN, 'clinical_pathway', 'cppttemplate']));
180+
}
181+
145182
public function anyMapping()
146183
{
147184
$this->_addHeaderFiles();
@@ -562,6 +599,117 @@ protected function getCpOptions()
562599
return $this->db('mlite_clinical_pathway')->asc('nama_cp')->toArray();
563600
}
564601

602+
protected function getCpptTemplateList()
603+
{
604+
$sql = "SELECT t.*, py.nm_penyakit
605+
FROM mlite_clinical_pathway_cppt_template t
606+
LEFT JOIN penyakit py ON py.kd_penyakit = t.kd_penyakit
607+
ORDER BY t.aktif DESC, t.ppra ASC, t.kd_penyakit ASC";
608+
609+
return $this->pdo()->query($sql)->fetchAll(PDO::FETCH_ASSOC);
610+
}
611+
612+
protected function getCpptPpraOptions($selected = '')
613+
{
614+
$options = [
615+
'PPRA',
616+
'Medis',
617+
'Keperawatan',
618+
'Farmasi Klinis',
619+
'Gizi',
620+
'Rehab Medik',
621+
'Edukasi'
622+
];
623+
624+
$selected = trim((string) $selected);
625+
if ($selected !== '' && !in_array($selected, $options, true)) {
626+
$options[] = $selected;
627+
}
628+
629+
sort($options);
630+
631+
return $options;
632+
}
633+
634+
protected function getCpptTemplateRow($id)
635+
{
636+
$sql = "SELECT t.*, py.nm_penyakit
637+
FROM mlite_clinical_pathway_cppt_template t
638+
LEFT JOIN penyakit py ON py.kd_penyakit = t.kd_penyakit
639+
WHERE t.id = ?
640+
LIMIT 1";
641+
642+
$stmt = $this->pdo()->prepare($sql);
643+
$stmt->execute([(int) $id]);
644+
645+
return $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
646+
}
647+
648+
protected function saveCpptTemplate($data)
649+
{
650+
$now = date('Y-m-d H:i:s');
651+
$kdPenyakit = strtoupper(trim((string) ($data['kd_penyakit'] ?? '')));
652+
$subjective = trim((string) ($data['subjective'] ?? ''));
653+
$objective = trim((string) ($data['objective'] ?? ''));
654+
$assessment = trim((string) ($data['assessment'] ?? ''));
655+
$plan = trim((string) ($data['plan'] ?? ''));
656+
$ppra = trim((string) ($data['ppra'] ?? ''));
657+
$aktif = ($data['aktif'] ?? 'Ya') === 'Tidak' ? 'Tidak' : 'Ya';
658+
659+
if ($kdPenyakit === '' || $ppra === '' || $subjective === '' || $objective === '' || $assessment === '' || $plan === '') {
660+
return ['status' => false, 'message' => 'Kode ICD, kategori PPRA, S, O, A, dan P wajib diisi.'];
661+
}
662+
663+
if (!in_array($ppra, $this->getCpptPpraOptions($ppra), true)) {
664+
return ['status' => false, 'message' => 'Kategori PPRA tidak valid.'];
665+
}
666+
667+
$diagnosis = $this->db('penyakit')->oneArray('kd_penyakit', $kdPenyakit);
668+
if (!$diagnosis) {
669+
return ['status' => false, 'message' => 'Kode ICD tidak ditemukan pada master penyakit.'];
670+
}
671+
672+
$payload = [
673+
'kd_penyakit' => $kdPenyakit,
674+
'ppra' => $ppra,
675+
'subjective' => $subjective,
676+
'objective' => $objective,
677+
'assessment' => $assessment,
678+
'plan' => $plan,
679+
'aktif' => $aktif,
680+
'updated_at' => $now
681+
];
682+
683+
$duplicate = $this->db('mlite_clinical_pathway_cppt_template')
684+
->where('kd_penyakit', $kdPenyakit)
685+
->oneArray();
686+
687+
if (!empty($data['id'])) {
688+
$id = (int) $data['id'];
689+
if ($duplicate && (int) $duplicate['id'] !== $id) {
690+
return ['status' => false, 'message' => 'Template CPPT untuk ICD ini sudah ada.'];
691+
}
692+
693+
$saved = $this->db('mlite_clinical_pathway_cppt_template')->where('id', $id)->save($payload);
694+
return [
695+
'status' => (bool) $saved,
696+
'message' => $saved ? 'Template CPPT berhasil diperbarui.' : 'Template CPPT gagal diperbarui.'
697+
];
698+
}
699+
700+
if ($duplicate) {
701+
return ['status' => false, 'message' => 'Template CPPT untuk ICD ini sudah ada.'];
702+
}
703+
704+
$payload['created_at'] = $now;
705+
$saved = $this->db('mlite_clinical_pathway_cppt_template')->save($payload);
706+
707+
return [
708+
'status' => (bool) $saved,
709+
'message' => $saved ? 'Template CPPT berhasil disimpan.' : 'Template CPPT gagal disimpan.'
710+
];
711+
}
712+
565713
protected function saveMaster($data)
566714
{
567715
$now = date('Y-m-d H:i:s');
@@ -2354,12 +2502,15 @@ protected function getPrintableCpByNoRawat($noRawat)
23542502
return [];
23552503
}
23562504

2505+
$diagnoses = $this->getPatientDiagnoses($noRawat, $header['status_lanjut']);
2506+
23572507
return [
23582508
'header' => $header,
23592509
'hospital' => $this->getHospitalProfile(),
23602510
'tariffs' => $this->getHospitalTariffSummary($noRawat, $header['status_lanjut']),
23612511
'care_team' => $this->getCareTeamByNoRawat($noRawat, $header['status_lanjut'], $header['tgl_registrasi'], $header['kd_dokter'] ?? null),
2362-
'diagnoses' => $this->getPatientDiagnoses($noRawat, $header['status_lanjut']),
2512+
'diagnoses' => $diagnoses,
2513+
'cppt_template' => $this->getPrintableCpptTemplate($diagnoses, $header['kd_penyakit'] ?? ''),
23632514
'days' => $this->getPrintableExecutionDays((int) $header['clinical_pathway_patient_id'], (int) $header['target_los']),
23642515
'matrix' => $this->getPrintableMatrixRows((int) $header['clinical_pathway_patient_id'], (int) $header['target_los']),
23652516
'variances' => $this->getPrintableVariances((int) $header['clinical_pathway_patient_id']),
@@ -2368,6 +2519,50 @@ protected function getPrintableCpByNoRawat($noRawat)
23682519
];
23692520
}
23702521

2522+
protected function getPrintableCpptTemplate(array $diagnoses, $fallbackCode = '')
2523+
{
2524+
$codes = [];
2525+
foreach ($diagnoses as $diagnosis) {
2526+
$code = strtoupper(trim((string) ($diagnosis['kd_penyakit'] ?? '')));
2527+
if ($code !== '' && !in_array($code, $codes, true)) {
2528+
$codes[] = $code;
2529+
}
2530+
}
2531+
2532+
$fallbackCode = strtoupper(trim((string) $fallbackCode));
2533+
if ($fallbackCode !== '' && !in_array($fallbackCode, $codes, true)) {
2534+
$codes[] = $fallbackCode;
2535+
}
2536+
2537+
if (!$codes) {
2538+
return [];
2539+
}
2540+
2541+
foreach ($codes as $code) {
2542+
$row = $this->getCpptTemplateByDiagnosisCode($code);
2543+
if ($row) {
2544+
return $row;
2545+
}
2546+
}
2547+
2548+
return [];
2549+
}
2550+
2551+
protected function getCpptTemplateByDiagnosisCode($kdPenyakit)
2552+
{
2553+
$sql = "SELECT t.*, py.nm_penyakit
2554+
FROM mlite_clinical_pathway_cppt_template t
2555+
LEFT JOIN penyakit py ON py.kd_penyakit = t.kd_penyakit
2556+
WHERE t.kd_penyakit = ?
2557+
AND t.aktif = 'Ya'
2558+
LIMIT 1";
2559+
2560+
$stmt = $this->pdo()->prepare($sql);
2561+
$stmt->execute([strtoupper(trim((string) $kdPenyakit))]);
2562+
2563+
return $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
2564+
}
2565+
23712566
protected function getHospitalProfile()
23722567
{
23732568
$logo = trim((string) $this->settings('settings.logo'));
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<div class="row">
2+
<div class="col-md-4">
3+
<div class="panel panel-default">
4+
<div class="panel-heading">
5+
<h3 class="panel-title">{if: isset_or($selected.id)}Edit{else}Tambah{/if} Template CPPT</h3>
6+
</div>
7+
<div class="panel-body">
8+
<form action="{?=url([ADMIN, 'clinical_pathway', 'savecppttemplate'])?}" method="POST">
9+
<input type="hidden" name="t" value="{?=$_SESSION['token']?}">
10+
<input type="hidden" name="id" value="{?=isset_or($selected.id)?}">
11+
12+
<div class="form-group">
13+
<label>Kode ICD</label>
14+
<input type="text" name="kd_penyakit" class="form-control" value="{?=isset_or($selected.kd_penyakit)?}" placeholder="contoh: N39.0" required>
15+
<p class="help-block">Gunakan kode diagnosis dari master penyakit mLITE.</p>
16+
</div>
17+
18+
<div class="form-group">
19+
<label>Diagnosa</label>
20+
<input type="text" class="form-control" value="{?=isset_or($selected.nm_penyakit)?}" readonly placeholder="Nama diagnosa akan tampil jika kode ICD valid">
21+
</div>
22+
23+
<div class="form-group">
24+
<label>Kategori PPRA</label>
25+
<select name="ppra" class="form-control" required>
26+
<option value="">-- pilih kategori PPRA --</option>
27+
{loop: $ppra_options}
28+
<option value="{$value}" {if: isset_or($selected.ppra) == $value}selected{/if}>{$value}</option>
29+
{/loop}
30+
</select>
31+
</div>
32+
33+
<div class="form-group">
34+
<label>S (Subjective)</label>
35+
<textarea name="subjective" class="form-control" rows="4" required>{?=isset_or($selected.subjective)?}</textarea>
36+
</div>
37+
38+
<div class="form-group">
39+
<label>O (Objective)</label>
40+
<textarea name="objective" class="form-control" rows="4" required>{?=isset_or($selected.objective)?}</textarea>
41+
</div>
42+
43+
<div class="form-group">
44+
<label>A (Assessment)</label>
45+
<textarea name="assessment" class="form-control" rows="3" required>{?=isset_or($selected.assessment)?}</textarea>
46+
</div>
47+
48+
<div class="form-group">
49+
<label>P (Plan)</label>
50+
<textarea name="plan" class="form-control" rows="4" required>{?=isset_or($selected.plan)?}</textarea>
51+
</div>
52+
53+
<div class="form-group">
54+
<label>Status</label>
55+
<select name="aktif" class="form-control">
56+
<option value="Ya" {if: isset_or($selected.aktif, 'Ya') == 'Ya'}selected{/if}>Aktif</option>
57+
<option value="Tidak" {if: isset_or($selected.aktif) == 'Tidak'}selected{/if}>Tidak Aktif</option>
58+
</select>
59+
</div>
60+
61+
<button type="submit" class="btn btn-success btn-block">Simpan Template CPPT</button>
62+
{if: isset_or($selected.id)}
63+
<a href="{?=url([ADMIN, 'clinical_pathway', 'cppttemplate'])?}" class="btn btn-default btn-block">Batal Edit</a>
64+
{/if}
65+
</form>
66+
</div>
67+
</div>
68+
</div>
69+
70+
<div class="col-md-8">
71+
<div class="panel panel-default">
72+
<div class="panel-heading">
73+
<h3 class="panel-title">Master Template CPPT</h3>
74+
</div>
75+
<div class="panel-body">
76+
<p class="text-muted">Template ini menjadi acuan pengisian SOAP/CPPT oleh PPRA. Jika diagnosis utama pasien cocok dengan kode ICD di bawah, maka template akan dicetak otomatis sebagai lampiran pada halaman terakhir Clinical Pathway.</p>
77+
<div class="table-responsive">
78+
<table class="table table-striped table-bordered dataTables">
79+
<thead>
80+
<tr>
81+
<th>PPRA</th>
82+
<th>Kode ICD</th>
83+
<th>Diagnosa</th>
84+
<th>S</th>
85+
<th>O</th>
86+
<th>A</th>
87+
<th>P</th>
88+
<th>Status</th>
89+
<th>Aksi</th>
90+
</tr>
91+
</thead>
92+
<tbody>
93+
{loop: $rows}
94+
<tr>
95+
<td>{?=isset_or($value.ppra)?}</td>
96+
<td>{?=isset_or($value.kd_penyakit)?}</td>
97+
<td>{?=isset_or($value.nm_penyakit)?}</td>
98+
<td>{?=isset_or($value.subjective)?}</td>
99+
<td>{?=isset_or($value.objective)?}</td>
100+
<td>{?=isset_or($value.assessment)?}</td>
101+
<td>{?=isset_or($value.plan)?}</td>
102+
<td>{?=isset_or($value.aktif)?}</td>
103+
<td>
104+
<a href="{?=url([ADMIN, 'clinical_pathway', 'cppttemplate'])?}&id={$value.id}" class="btn btn-xs btn-primary">Edit</a>
105+
<a href="{?=url([ADMIN, 'clinical_pathway', 'deletecppttemplate', $value.id])?}" class="btn btn-xs btn-danger" onclick="return confirm('Hapus template CPPT ini?');">Hapus</a>
106+
</td>
107+
</tr>
108+
{/loop}
109+
</tbody>
110+
</table>
111+
</div>
112+
</div>
113+
</div>
114+
</div>
115+
</div>

0 commit comments

Comments
 (0)