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
This commit is contained in:
DevSoftTech Server 2026-04-10 12:30:15 +05:30
parent a81162f954
commit 21ddeefe40
4 changed files with 181 additions and 58 deletions

View File

@ -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,
]);

View File

@ -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);

View File

@ -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,

View File

@ -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;
}
</style>
<htmlpageheader name="myHeader">