From 21ddeefe40f590fe27a8c33e71e64ac2d89b10a6 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Fri, 10 Apr 2026 12:30:15 +0530 Subject: [PATCH] fix: Running OPD zero-value validity, update_doc_field HTTP 500, PDF Unicode font (Devanagari) - Opd.php showfee(): exclude zero-amount OPD from validity window query - Opd.php confirm_opd(): anchor last_opdvisit_date to most recent paid OPD for zero-value running - Opd.php buildOpdLetterPrintData(): use paid OPD start date for expiry on zero-value entries - OpdModel.php insertOpd(): exclude fee_type=3/zero-amount from running_opd_id lookup; anchor visit count to paid OPD start when isZeroValueRunning - DoctorDocument.php update_doc_field(): wrap in try/catch, schema-safe payload via filterPayloadByExistingColumns, structured JSON errors - DoctorDocument.php create_final(): configure mPDF with FreeSans/FreeSerif (GNU FreeFont), useOTL=0xFF, autoScriptToLang=true for Devanagari/Hindi rendering - doc_letterhead_print.php: add body/p/td/th font-family freesans for PDF Unicode output --- app/Controllers/DoctorDocument.php | 150 +++++++++++++----- app/Controllers/Opd.php | 59 +++++-- app/Models/OpdModel.php | 27 +++- .../doctor_document/doc_letterhead_print.php | 3 + 4 files changed, 181 insertions(+), 58 deletions(-) diff --git a/app/Controllers/DoctorDocument.php b/app/Controllers/DoctorDocument.php index 560ad31..3510e29 100644 --- a/app/Controllers/DoctorDocument.php +++ b/app/Controllers/DoctorDocument.php @@ -828,52 +828,98 @@ class DoctorDocument extends BaseController if ($resp = $this->ensureAccess()) { return $resp; } + try { + if (! $this->db->tableExists('patient_doc') || ! $this->db->tableExists('patient_doc_raw')) { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'Document tables not available', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } - $pending = $this->db->table('patient_doc_raw') - ->where('p_doc_id', $patientDocId) - ->where('update_data', 0) - ->countAllResults(); + $pending = 0; + if ($this->hasColumn('patient_doc_raw', 'update_data')) { + $pending = $this->db->table('patient_doc_raw') + ->where('p_doc_id', $patientDocId) + ->where('update_data', 0) + ->countAllResults(); + } + + if ($pending > 0) { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'Some field pending', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + + $patientDoc = $this->db->table('patient_doc')->where('id', $patientDocId)->get(1)->getRowArray(); + if (! is_array($patientDoc)) { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'Document not found', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + + $report = (string) ($patientDoc['raw_data'] ?? ''); + + $values = $this->db->table('patient_doc_raw r') + ->select('r.p_doc_raw_value,s.input_code') + ->join('doc_format_sub s', 'r.p_doc_sub_id=s.id', 'left') + ->where('r.p_doc_id', $patientDocId) + ->get() + ->getResultArray(); + + foreach ($values as $row) { + $code = trim((string) ($row['input_code'] ?? '')); + if ($code === '') { + continue; + } + $val = nl2br((string) ($row['p_doc_raw_value'] ?? '')); + $report = str_replace('{' . $code . '}', $val, $report); + } + + $updatePayload = [ + 'raw_data' => $report, + 'update_pre_value' => 1, + 'updated_at' => date('Y-m-d H:i:s'), + ]; + $updatePayload = $this->filterPayloadByExistingColumns('patient_doc', $updatePayload); + + if (empty($updatePayload)) { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'Unable to compile due to schema mismatch', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + + $this->db->table('patient_doc')->where('id', $patientDocId)->update($updatePayload); - if ($pending > 0) { return $this->response->setJSON([ + 'update' => 1, + 'error_text' => 'Compile done', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } catch (\Throwable $e) { + log_message('error', 'DoctorDocument update_doc_field failed for doc_id {doc_id}: {message}', [ + 'doc_id' => $patientDocId, + 'message' => $e->getMessage(), + ]); + + return $this->response->setStatusCode(500)->setJSON([ 'update' => 0, - 'error_text' => 'Some field pending', + 'error_text' => 'Compile failed due to server error', 'csrfName' => csrf_token(), 'csrfHash' => csrf_hash(), ]); } - - $patientDoc = $this->db->table('patient_doc')->where('id', $patientDocId)->get(1)->getRowArray(); - $report = (string) ($patientDoc['raw_data'] ?? ''); - - $values = $this->db->table('patient_doc_raw r') - ->select('r.p_doc_raw_value,s.input_code') - ->join('doc_format_sub s', 'r.p_doc_sub_id=s.id', 'left') - ->where('r.p_doc_id', $patientDocId) - ->get() - ->getResultArray(); - - foreach ($values as $row) { - $code = trim((string) ($row['input_code'] ?? '')); - if ($code === '') { - continue; - } - $val = nl2br((string) ($row['p_doc_raw_value'] ?? '')); - $report = str_replace('{' . $code . '}', $val, $report); - } - - $this->db->table('patient_doc')->where('id', $patientDocId)->update([ - 'raw_data' => $report, - 'update_pre_value' => 1, - 'updated_at' => date('Y-m-d H:i:s'), - ]); - - return $this->response->setJSON([ - 'update' => 1, - 'error_text' => 'Compile done', - 'csrfName' => csrf_token(), - 'csrfHash' => csrf_hash(), - ]); } public function load_doc(int $patientDocId) @@ -1063,6 +1109,10 @@ class DoctorDocument extends BaseController mkdir($mpdfTempDir, 0755, true); } + // FreeSans (GNU FreeFont) ships with mPDF and covers Devanagari (Hindi) Unicode glyphs. + $mpdfFontDir = realpath(__DIR__ . '/../../vendor/mpdf/mpdf/ttfonts'); + $mpdfFontDirs = $mpdfFontDir !== false ? [$mpdfFontDir] : []; + try { $mpdf = new Mpdf([ 'format' => $pageSize, @@ -1073,8 +1123,28 @@ class DoctorDocument extends BaseController 'margin_header' => $printHeaderMargin * 10, 'margin_footer' => $printFooterMargin * 10, 'tempDir' => $mpdfTempDir, - 'autoScriptToLang' => false, - 'autoLanguageDetection' => false, + 'default_font' => 'freesans', + 'fontDir' => $mpdfFontDirs, + 'fontdata' => [ + 'freesans' => [ + 'R' => 'FreeSans.ttf', + 'B' => 'FreeSansBold.ttf', + 'I' => 'FreeSansOblique.ttf', + 'BI' => 'FreeSansBoldOblique.ttf', + 'useOTL' => 0xFF, + 'useKashida' => 75, + ], + 'freeserif' => [ + 'R' => 'FreeSerif.ttf', + 'B' => 'FreeSerifBold.ttf', + 'I' => 'FreeSerifItalic.ttf', + 'BI' => 'FreeSerifBoldItalic.ttf', + 'useOTL' => 0xFF, + 'useKashida' => 75, + ], + ], + 'autoScriptToLang' => true, + 'autoLanguageDetection' => true, 'autoArabic' => false, 'autoVietnamese' => false, ]); diff --git a/app/Controllers/Opd.php b/app/Controllers/Opd.php index fa85c66..0ea7603 100644 --- a/app/Controllers/Opd.php +++ b/app/Controllers/Opd.php @@ -1378,7 +1378,8 @@ class Opd extends BaseController $docInfo = $query->getResult(); $sql = "select * from opd_master where p_id=" . $pId . " and doc_id=" . $docId . - " and apointment_date >= date_add(curdate(),interval -" . $noOpdDays . " day) and opd_fee_type<>3"; + " and apointment_date >= date_add(curdate(),interval -" . $noOpdDays . " day)" + . " and opd_fee_type<>3 and coalesce(opd_fee_amount,0) > 0"; $query = $this->db->query($sql); $opdRunning = $query->getResult(); @@ -1544,7 +1545,25 @@ class Opd extends BaseController 'prepared_by' => $userNameInfo, ]; - if (count($opdMaster) > 0) { + $isZeroValueRunning = ((int) ($docFee[0]->doc_fee_type ?? 0) === 3) + || ((float) ($docFee[0]->amount ?? 0) <= 0); + + if ($isZeroValueRunning) { + $sql = "select apointment_date from opd_master" + . " where p_id=" . $pid + . " and doc_id=" . $docId + . " and opd_fee_type<>3" + . " and coalesce(opd_fee_amount,0) > 0" + . " order by opd_id desc limit 1"; + $query = $this->db->query($sql); + $previousPaid = $query->getResult(); + + if (!empty($previousPaid[0]->apointment_date ?? null)) { + $insert['last_opdvisit_date'] = $previousPaid[0]->apointment_date; + } elseif (count($opdMaster) > 0) { + $insert['last_opdvisit_date'] = $opdMaster[0]->apointment_date; + } + } elseif (count($opdMaster) > 0) { $insert['last_opdvisit_date'] = $opdMaster[0]->apointment_date; } @@ -3488,10 +3507,16 @@ class Opd extends BaseController } } - $sql = "select *,date_format(apointment_date,'%d-%m-%Y') as str_apointment_date, - date_format(date_add(apointment_date,interval " . $noOpdDays . " day),'%d-%m-%Y') as opd_Exp_Date, + $sql = "select o.*,date_format(o.apointment_date,'%d-%m-%Y') as str_apointment_date, + date_format(date_add( + case + when (coalesce(o.running_opd,0)=1 or coalesce(o.opd_fee_amount,0)<=0) and coalesce(o.running_opd_id,0)>0 + then coalesce((select apointment_date from opd_master where opd_id=o.running_opd_id), o.apointment_date) + else o.apointment_date + end, + interval " . $noOpdDays . " day),'%d-%m-%Y') as opd_Exp_Date, (case payment_status when 1 then 'Cash' when 2 then 'Bank Card' when 3 then 'Org. Credit' else 'Pending' end) as Payment_type_str - from opd_master where opd_id=" . (int) $opdId; + from opd_master o where o.opd_id=" . (int) $opdId; $query = $this->db->query($sql); $data['opd_master'] = $query->getResult(); @@ -3511,11 +3536,25 @@ class Opd extends BaseController $pRow->age = get_age_1($pRow->dob ?? null, $pRow->age ?? '', $pRow->age_in_month ?? '', $pRow->estimate_dob ?? '', $opdRow->apointment_date ?? null); } - $sql = "select *,date_format(apointment_date,'%d-%m-%Y') as str_apointment_date, - date_format(date_add(apointment_date,interval " . $noOpdDays . " day),'%d-%m-%Y') as opd_Exp_Date - from opd_master where p_id=" . (int) $opdRow->p_id . " and opd_id < " . (int) $opdId . " order by opd_id desc limit 1"; - $query = $this->db->query($sql); - $data['old_opd'] = $query->getResult(); + $data['old_opd'] = []; + if (!empty($opdRow->last_opdvisit_date)) { + $sql = "select *,date_format(apointment_date,'%d-%m-%Y') as str_apointment_date, + date_format(date_add(apointment_date,interval " . $noOpdDays . " day),'%d-%m-%Y') as opd_Exp_Date + from opd_master where p_id=" . (int) $opdRow->p_id + . " and doc_id=" . (int) ($opdRow->doc_id ?? 0) + . " and apointment_date=" . $this->db->escape((string) $opdRow->last_opdvisit_date) + . " order by opd_id desc limit 1"; + $query = $this->db->query($sql); + $data['old_opd'] = $query->getResult(); + } + + if (empty($data['old_opd'])) { + $sql = "select *,date_format(apointment_date,'%d-%m-%Y') as str_apointment_date, + date_format(date_add(apointment_date,interval " . $noOpdDays . " day),'%d-%m-%Y') as opd_Exp_Date + from opd_master where p_id=" . (int) $opdRow->p_id . " and opd_id < " . (int) $opdId . " order by opd_id desc limit 1"; + $query = $this->db->query($sql); + $data['old_opd'] = $query->getResult(); + } $sql = "select * from hc_insurance where id=" . (int) ($opdRow->insurance_id ?? 0); $query = $this->db->query($sql); diff --git a/app/Models/OpdModel.php b/app/Models/OpdModel.php index 3006eb9..9851064 100644 --- a/app/Models/OpdModel.php +++ b/app/Models/OpdModel.php @@ -42,14 +42,6 @@ class OpdModel return 0; } - $opdTimesRow = $this->db->table('opd_master') - ->select('count(*) as xtimes') - ->where('opd_id <=', $insertId) - ->where('p_id', $pId) - ->get() - ->getRow(); - $opdTimes = (int) ($opdTimesRow->xtimes ?? 0); - $opdNoRow = $this->db->table('opd_master') ->select('count(*) as no_max') ->where('apointment_date', date('Y-m-d')) @@ -66,6 +58,8 @@ class OpdModel $runningOpd = 0; $lastOpdId = 0; + $isZeroValueRunning = ((int) ($data['opd_fee_type'] ?? 0) === 3) + || ((float) ($data['opd_fee_amount'] ?? 0) <= 0); if ((int) ($data['opd_fee_type'] ?? 0) === 3) { $runningOpd = 1; @@ -75,6 +69,8 @@ class OpdModel ->where('apointment_date >=', date('Y-m-d', strtotime('-4 days'))) ->where('apointment_date <=', date('Y-m-d')) ->where('running_opd', 0) + ->where('opd_fee_type <>', 3) + ->where('coalesce(opd_fee_amount,0) >', 0, false) ->where('doc_id', $docId) ->where('p_id', $pId) ->get() @@ -83,6 +79,21 @@ class OpdModel $lastOpdId = (int) ($lastOpdRow->last_opd_id ?? 0); } + $opdTimesBuilder = $this->db->table('opd_master') + ->select('count(*) as xtimes') + ->where('opd_id <=', $insertId) + ->where('p_id', $pId); + + if ($isZeroValueRunning && $lastOpdId > 0) { + // For zero-value running OPD, keep visit count within the same paid OPD validity cycle. + $opdTimesBuilder + ->where('opd_id >=', $lastOpdId) + ->where('doc_id', $docId); + } + + $opdTimesRow = $opdTimesBuilder->get()->getRow(); + $opdTimes = (int) ($opdTimesRow->xtimes ?? 0); + $update = [ 'opd_code' => $opdCode, 'no_visit' => $opdTimes, diff --git a/app/Views/doctor_document/doc_letterhead_print.php b/app/Views/doctor_document/doc_letterhead_print.php index 213a4ce..313d62f 100644 --- a/app/Views/doctor_document/doc_letterhead_print.php +++ b/app/Views/doctor_document/doc_letterhead_print.php @@ -21,6 +21,9 @@ $hasSelectedPrintTemplate = ! empty($has_selected_print_template); header: html_myHeader; footer: html_myFooter; } +body, p, td, th, div, span, li { + font-family: 'freesans', 'freeserif', sans-serif; +}