feat: migrate OPD template flow to DB and apply related updates

This commit is contained in:
DevSoftTech Server 2026-03-20 18:11:17 +05:30
parent 145f9fba69
commit 300ec0e5cf
30 changed files with 1976 additions and 1068 deletions

View File

@ -452,6 +452,9 @@ $routes->get('Opd/opd_lettre_print/(:num)', 'Opd::opd_lettre_print/$1');
$routes->get('Opd/opd_lettre_pdf/(:num)', 'Opd::opd_lettre_pdf/$1');
$routes->get('Opd/print_template_builder', 'Opd::print_template_builder');
$routes->post('Opd/print_template_save', 'Opd::print_template_save');
$routes->post('Opd/print_template_rename', 'Opd::print_template_rename');
$routes->post('Opd/print_template_delete', 'Opd::print_template_delete');
$routes->match(['get', 'post'], 'Opd/paper_print_settings', 'Opd::paper_print_settings');
$routes->get('opd_print/invoice_print_pdf/(:num)', 'Opd::invoice_print_pdf/$1');
$routes->get('opd_print/opd_PDF_print/(:num)', 'Opd::opd_pdf_print/$1');

View File

@ -3357,135 +3357,6 @@ class Diagnosis extends BaseController
return '';
}
private function extractTextWithDocumentIntelligence(string $absolutePath, string $endpoint, string $key): string
{
try {
$client = service('curlrequest', $this->httpOptions());
$bytes = @file_get_contents($absolutePath);
if ($bytes === false) {
return '';
}
$analyzeUrl = $endpoint . '/documentintelligence/documentModels/prebuilt-read:analyze?api-version=2024-02-29-preview';
$submit = $client->post($analyzeUrl, [
'headers' => [
'Content-Type' => 'application/octet-stream',
'Ocp-Apim-Subscription-Key' => $key,
],
'body' => $bytes,
]);
if ($submit->getStatusCode() < 200 || $submit->getStatusCode() >= 300) {
return '';
}
$operationLocation = trim((string) $submit->getHeaderLine('Operation-Location'));
if ($operationLocation === '') {
return '';
}
for ($i = 0; $i < 12; $i++) {
usleep(800000);
$poll = $client->get($operationLocation, [
'headers' => [
'Ocp-Apim-Subscription-Key' => $key,
],
]);
if ($poll->getStatusCode() < 200 || $poll->getStatusCode() >= 300) {
continue;
}
$decoded = json_decode((string) $poll->getBody(), true);
if (! is_array($decoded)) {
continue;
}
$status = strtolower((string) ($decoded['status'] ?? ''));
if ($status === 'succeeded') {
$lines = [];
$pages = $decoded['analyzeResult']['pages'] ?? [];
foreach ($pages as $page) {
$pageLines = $page['lines'] ?? [];
foreach ($pageLines as $line) {
$content = trim((string) ($line['content'] ?? ''));
if ($content !== '') {
$lines[] = $content;
}
}
}
return trim(implode("\n", $lines));
}
if ($status === 'failed') {
return '';
}
}
} catch (\Throwable $e) {
return '';
}
return '';
}
private function extractTextWithAzureVisionLlm(string $absolutePath): string
{
$endpoint = rtrim($this->readSetting('AZURE_OPENAI_ENDPOINT'), '/');
$apiKey = trim($this->readSetting('AZURE_OPENAI_API_KEY'));
$deployment = trim($this->readSetting('AZURE_OPENAI_DEPLOYMENT'));
$apiVersion = trim($this->readSetting('AZURE_OPENAI_API_VERSION'));
if ($endpoint === '' || $apiKey === '' || $deployment === '') {
return '';
}
if ($apiVersion === '') {
$apiVersion = '2024-10-21';
}
$mime = $this->detectMimeType($absolutePath);
if (! str_starts_with($mime, 'image/')) {
return '';
}
$bytes = @file_get_contents($absolutePath);
if ($bytes === false) {
return '';
}
$dataUrl = 'data:' . $mime . ';base64,' . base64_encode($bytes);
$url = $endpoint . '/openai/deployments/' . rawurlencode($deployment) . '/chat/completions?api-version=' . rawurlencode($apiVersion);
try {
$client = service('curlrequest', $this->httpOptions());
$response = $client->post($url, [
'headers' => [
'api-key' => $apiKey,
'Content-Type' => 'application/json',
],
'json' => [
'messages' => [[
'role' => 'user',
'content' => [
['type' => 'text', 'text' => 'Read and return all text from this medical lab report image exactly as plain text.'],
['type' => 'image_url', 'image_url' => ['url' => $dataUrl]],
],
]],
'temperature' => 0,
'max_tokens' => 3000,
],
]);
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
return '';
}
$decoded = json_decode((string) $response->getBody(), true);
return trim((string) ($decoded['choices'][0]['message']['content'] ?? ''));
} catch (\Throwable $e) {
return '';
}
}
/**
* @return array<string, mixed>
@ -3724,99 +3595,6 @@ class Diagnosis extends BaseController
];
}
/**
* @return array<string, mixed>
*/
private function parseLabValuesWithAzureOpenAi(string $ocrText, string $panelName): array
{
$endpoint = rtrim($this->readSetting('AZURE_OPENAI_ENDPOINT'), '/');
$apiKey = trim($this->readSetting('AZURE_OPENAI_API_KEY'));
$deployment = trim($this->readSetting('AZURE_OPENAI_DEPLOYMENT'));
$apiVersion = trim($this->readSetting('AZURE_OPENAI_API_VERSION'));
if ($endpoint === '' || $apiKey === '' || $deployment === '') {
return [
'provider' => 'azure-openai',
'model' => '',
'values' => [],
'raw' => [],
'error' => 'Azure OpenAI settings are missing.',
];
}
if ($apiVersion === '') {
$apiVersion = '2024-10-21';
}
$schemaHint = '{"panel_name":"string","values":[{"test_name":"string","value":"string","unit":"string","reference_range":"string","abnormal_flag":"H|L|N|","raw_line":"string"}] }';
$prompt = "You are an expert lab report parser for Indian hospitals.\n" .
"Extract only measurable pathology values from OCR text.\n" .
"Return strict JSON only, no markdown, no comments, with schema: " . $schemaHint . "\n" .
"If uncertain, keep empty string. panel_name input: " . $panelName . "\n\n" .
"OCR_TEXT:\n" . mb_substr($ocrText, 0, 14000);
$url = $endpoint . '/openai/deployments/' . rawurlencode($deployment) . '/chat/completions?api-version=' . rawurlencode($apiVersion);
$attempts = $this->aiMaxRetries();
$lastError = 'Azure AI parsing failed.';
for ($attempt = 1; $attempt <= $attempts; $attempt++) {
try {
$client = service('curlrequest', $this->httpOptions());
$response = $client->post($url, [
'headers' => [
'api-key' => $apiKey,
'Content-Type' => 'application/json',
],
'json' => [
'messages' => [[
'role' => 'user',
'content' => $prompt,
]],
'temperature' => 0,
'response_format' => ['type' => 'json_object'],
'max_tokens' => 2200,
],
]);
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
$lastError = 'Azure OpenAI HTTP ' . $response->getStatusCode();
continue;
}
$decoded = json_decode((string) $response->getBody(), true);
$content = trim((string) ($decoded['choices'][0]['message']['content'] ?? ''));
if ($content === '') {
$lastError = 'Azure OpenAI returned empty content.';
continue;
}
$json = json_decode($content, true);
if (! is_array($json)) {
$lastError = 'Azure OpenAI returned invalid JSON content.';
continue;
}
return [
'provider' => 'azure-openai',
'model' => (string) ($decoded['model'] ?? $deployment),
'values' => is_array($json['values'] ?? null) ? $json['values'] : [],
'raw' => $decoded,
'error' => '',
];
} catch (\Throwable $e) {
$lastError = $e->getMessage() !== '' ? $e->getMessage() : 'Azure OpenAI request timeout/error.';
}
}
return [
'provider' => 'azure-openai',
'model' => '',
'values' => [],
'raw' => [],
'error' => $lastError,
];
}
/**
* @return array<string, mixed>
*/

File diff suppressed because it is too large Load Diff

View File

@ -483,8 +483,8 @@ class Opd_prescription extends BaseController
$draft = $this->generateClinicalTextWithAi($prompt);
if ($draft !== null && trim($draft) !== '') {
$provider = $this->lastAiProvider === 'azure' ? 'azure' : 'ai';
$providerLabel = $provider === 'azure' ? 'Azure OpenAI' : 'AI';
$provider = $this->lastAiProvider === 'ai-server' ? 'ai-server' : 'ai';
$providerLabel = $provider === 'ai-server' ? 'AI Server' : 'AI';
return $this->response->setJSON([
'update' => 1,
'draft_text' => trim($draft),
@ -579,8 +579,8 @@ class Opd_prescription extends BaseController
$aiMode = 'fallback';
$message = 'AI unavailable. Template draft prepared from entered data.';
} else {
$aiMode = $this->lastAiProvider === 'azure' ? 'azure' : 'ai';
$message = 'Full clinical draft prepared using ' . ($aiMode === 'azure' ? 'Azure OpenAI' : 'AI') . '.';
$aiMode = $this->lastAiProvider === 'ai-server' ? 'ai-server' : 'ai';
$message = 'Full clinical draft prepared using ' . ($aiMode === 'ai-server' ? 'AI Server' : 'AI') . '.';
}
return $this->response->setJSON([
@ -2597,37 +2597,12 @@ class Opd_prescription extends BaseController
$targetLanguageName = $supportedLanguages[$targetLang];
$prompt = "You are a clinical translation assistant for OPD patient advice in India.\n"
. "Translate the following English patient advice into {$targetLanguageName}.\n"
. "Keep meaning medically accurate, simple, and patient-friendly.\n"
. "Return ONLY valid JSON object: {\"translated_text\":\"...\"}.\n"
. "Do not include markdown, extra keys, or explanation.\n"
. "English Advice: {$sourceText}";
$this->lastAiProvider = 'none';
$aiText = $this->generateClinicalTextWithAi($prompt, 220);
if ($aiText === null || trim($aiText) === '') {
return $this->response->setJSON([
'update' => 0,
'error_text' => 'AI unavailable. Check AI settings.',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
$parsed = $this->parseJsonFromAiResponse($aiText);
$translatedText = trim((string) ($parsed['translated_text'] ?? ''));
if ($translatedText === '') {
$translatedText = trim(strip_tags((string) $aiText));
$translatedText = preg_replace('/```(?:json)?|```/i', '', $translatedText) ?? $translatedText;
$translatedText = trim($translatedText);
}
$translatedText = $this->translateAdviceWithAiServer($sourceText, $targetLang);
if ($translatedText === '') {
return $this->response->setJSON([
'update' => 0,
'error_text' => 'Translation failed. Try again.',
'error_text' => 'AI server unavailable. Check DIAGNOSIS_AI_SERVER_URL and token settings.',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
@ -2635,7 +2610,7 @@ class Opd_prescription extends BaseController
return $this->response->setJSON([
'update' => 1,
'provider' => $this->lastAiProvider,
'provider' => 'ai-server',
'target_lang' => $targetLang,
'target_language_name' => $targetLanguageName,
'translated_text' => $translatedText,
@ -3016,8 +2991,8 @@ class Opd_prescription extends BaseController
$this->lastAiProvider = 'none';
$aiDraft = $this->generateComplaintDraftWithAi($finalComplaints, $currentText);
if ($aiDraft !== null && trim($aiDraft) !== '') {
$provider = $this->lastAiProvider === 'azure' ? 'azure' : 'ai';
$providerLabel = $provider === 'azure' ? 'Azure OpenAI' : 'AI';
$provider = $this->lastAiProvider === 'ai-server' ? 'ai-server' : 'ai';
$providerLabel = $provider === 'ai-server' ? 'AI Server' : 'AI';
return $this->response->setJSON([
'update' => 1,
'draft_text' => trim($aiDraft),
@ -5374,59 +5349,10 @@ class Opd_prescription extends BaseController
}
/**
* @return array{endpoint:string,api_key:string,deployment:string,api_version:string}
*/
private function getAzureOpenAiConfig(): array
{
$endpoint = $this->readAiSettingValue(
['AZURE_OPENAI_ENDPOINT', 'AI_AZURE_OPENAI_ENDPOINT', 'APP_AZURE_OPENAI_ENDPOINT', 'H_AZURE_OPENAI_ENDPOINT'],
['AZURE_OPENAI_ENDPOINT']
);
$apiKey = $this->readAiSettingValue(
['AZURE_OPENAI_API_KEY', 'AI_AZURE_OPENAI_API_KEY', 'APP_AZURE_OPENAI_API_KEY', 'H_AZURE_OPENAI_API_KEY'],
['AZURE_OPENAI_API_KEY']
);
$deployment = $this->readAiSettingValue(
['AZURE_OPENAI_DEPLOYMENT', 'AI_AZURE_OPENAI_DEPLOYMENT', 'APP_AZURE_OPENAI_DEPLOYMENT', 'H_AZURE_OPENAI_DEPLOYMENT'],
['AZURE_OPENAI_DEPLOYMENT']
);
$apiVersion = $this->readAiSettingValue(
['AZURE_OPENAI_API_VERSION', 'AI_AZURE_OPENAI_API_VERSION', 'APP_AZURE_OPENAI_API_VERSION', 'H_AZURE_OPENAI_API_VERSION'],
['AZURE_OPENAI_API_VERSION']
);
if ($apiVersion === '') {
$apiVersion = '2024-10-21';
}
return [
'endpoint' => rtrim($endpoint, '/'),
'api_key' => $apiKey,
'deployment' => $deployment,
'api_version' => $apiVersion,
];
}
/**
* @param array<int, string> $constCandidates
* @param array<int, string> $settingCandidates
*/
private function readAiSettingValue(array $constCandidates, array $settingCandidates): string
private function readAiSettingValue(array $settingCandidates): string
{
foreach ($constCandidates as $constName) {
if (! defined($constName)) {
continue;
}
$value = trim((string) constant($constName));
if ($value !== '') {
return $value;
}
}
if ($this->db->tableExists('hospital_setting') && ! empty($settingCandidates)) {
$row = $this->db->table('hospital_setting')
->select('s_value')
@ -5462,61 +5388,6 @@ Output only one plain text sentence starting with 'Chief complaints:' and no mar
return $this->generateClinicalTextWithAi($prompt, 180);
}
private function generateClinicalTextWithAzureOpenAi(string $prompt, int $maxOutputTokens = 300): ?string
{
$cfg = $this->getAzureOpenAiConfig();
if ($cfg['endpoint'] === '' || $cfg['api_key'] === '' || $cfg['deployment'] === '' || trim($prompt) === '') {
return null;
}
try {
$httpOptions = [
'timeout' => 20,
'http_errors' => false,
];
if (defined('ENVIRONMENT') && in_array((string) ENVIRONMENT, ['development', 'testing'], true)) {
$httpOptions['verify'] = false;
}
$client = service('curlrequest', $httpOptions);
$url = $cfg['endpoint'] . '/openai/deployments/' . rawurlencode($cfg['deployment'])
. '/chat/completions?api-version=' . rawurlencode($cfg['api_version']);
$response = $client->post($url, [
'headers' => [
'api-key' => $cfg['api_key'],
'Content-Type' => 'application/json',
],
'json' => [
'messages' => [
['role' => 'system', 'content' => 'You are a clinical OPD assistant. Return plain text only, no markdown.'],
['role' => 'user', 'content' => $prompt],
],
'temperature' => 0.2,
'top_p' => 0.9,
'max_tokens' => max(64, $maxOutputTokens),
],
]);
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
return null;
}
$decoded = json_decode((string) $response->getBody(), true);
$text = trim((string) ($decoded['choices'][0]['message']['content'] ?? ''));
$text = trim($text);
if ($text === '') {
return null;
}
$this->lastAiProvider = 'azure';
return preg_replace('/\s+/', ' ', $text) ?: $text;
} catch (\Throwable $e) {
return null;
}
}
private function buildClinicalPrompt(string $mode, string $text): string
{
if ($mode === 'hinglish_to_english') {
@ -5639,7 +5510,84 @@ Input: " . $text;
private function generateClinicalTextWithAi(string $prompt, int $maxOutputTokens = 300): ?string
{
return $this->generateClinicalTextWithAzureOpenAi($prompt, $maxOutputTokens);
unset($prompt, $maxOutputTokens);
return null;
}
private function resolveAiServerBaseUrl(): string
{
$base = rtrim($this->readAiSettingValue(['DIAGNOSIS_AI_SERVER_URL']), '/');
if ($base !== '') {
return $base;
}
$candidates = [
trim($this->readAiSettingValue(['DIAGNOSIS_AI_PARSE_ENDPOINT'])),
trim($this->readAiSettingValue(['DIAGNOSIS_AI_OCR_ENDPOINT'])),
];
foreach ($candidates as $url) {
if ($url === '') {
continue;
}
$parts = @parse_url($url);
if (! is_array($parts) || empty($parts['scheme']) || empty($parts['host'])) {
continue;
}
$base = (string) $parts['scheme'] . '://' . (string) $parts['host'];
if (! empty($parts['port'])) {
$base .= ':' . (int) $parts['port'];
}
return rtrim($base, '/');
}
return '';
}
private function translateAdviceWithAiServer(string $sourceText, string $targetLang): string
{
$base = $this->resolveAiServerBaseUrl();
if ($base === '') {
return '';
}
$token = trim($this->readAiSettingValue(['DIAGNOSIS_AI_PARSE_TOKEN']));
try {
$options = [
'timeout' => 20,
'http_errors' => false,
];
if (defined('ENVIRONMENT') && in_array((string) ENVIRONMENT, ['development', 'testing'], true)) {
$options['verify'] = false;
}
$headers = ['Accept' => 'application/json'];
if ($token !== '') {
$headers['Authorization'] = 'Bearer ' . $token;
}
$client = service('curlrequest', $options);
$url = $base . '/translate?text=' . rawurlencode($sourceText) . '&target_lang=' . rawurlencode($targetLang);
$response = $client->get($url, ['headers' => $headers]);
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
return '';
}
$decoded = json_decode((string) $response->getBody(), true);
$translated = trim((string) ($decoded['translated_text'] ?? ''));
if ($translated === '') {
return '';
}
$this->lastAiProvider = 'ai-server';
return $translated;
} catch (\Throwable $e) {
return '';
}
}
/**

View File

@ -7,6 +7,8 @@ use App\Models\DoctorModel;
class Doctor extends BaseController
{
private const OPD_TEMPLATE_KEY_PREFIX = 'OPD_PRINT_TEMPLATE__';
public function index(): string
{
$doctorModel = new DoctorModel();
@ -23,6 +25,7 @@ class Doctor extends BaseController
return view('Setting/Doctor/Doctor_V', [
'errors' => session('errors'),
'template_options' => $templateContext['options'],
'opd_print_template_options' => $templateContext['opd_print_options'],
'template_fields' => $templateContext['fields'],
]);
}
@ -42,6 +45,7 @@ class Doctor extends BaseController
'errors' => $this->validator->getErrors(),
'formData' => $this->request->getPost(),
'template_options' => $templateContext['options'],
'opd_print_template_options' => $templateContext['opd_print_options'],
'template_fields' => $templateContext['fields'],
]);
}
@ -80,8 +84,7 @@ class Doctor extends BaseController
if (! in_array($dbField, $templateContext['fields'], true)) {
continue;
}
$templateName = strtolower(trim((string) $this->request->getPost($inputField)));
$templateName = preg_replace('/[^a-z0-9_\-]/', '', $templateName) ?? '';
$templateName = $this->normalizeTemplateId((string) $this->request->getPost($inputField));
$data[$dbField] = $templateName;
}
@ -99,6 +102,7 @@ class Doctor extends BaseController
'errors' => $errors,
'formData' => $this->request->getPost(),
'template_options' => $templateContext['options'],
'opd_print_template_options' => $templateContext['opd_print_options'],
'template_fields' => $templateContext['fields'],
]);
}
@ -123,6 +127,7 @@ class Doctor extends BaseController
'doc_ipd_fee_list' => $doctorModel->getDoctorIpdFees($id),
'doc_ipd_fee_type' => $doctorModel->getIpdFeeTypes(),
'template_options' => $templateContext['options'],
'opd_print_template_options' => $templateContext['opd_print_options'],
'template_fields' => $templateContext['fields'],
]);
}
@ -165,8 +170,7 @@ class Doctor extends BaseController
if (! in_array($dbField, $templateContext['fields'], true)) {
continue;
}
$templateName = strtolower(trim((string) $this->request->getPost($inputField)));
$templateName = preg_replace('/[^a-z0-9_\-]/', '', $templateName) ?? '';
$templateName = $this->normalizeTemplateId((string) $this->request->getPost($inputField));
$data[$dbField] = $templateName;
}
@ -573,59 +577,67 @@ class Doctor extends BaseController
];
if (! $this->db->tableExists('doctor_master')) {
return ['fields' => [], 'options' => []];
return ['fields' => [], 'options' => [], 'opd_print_options' => []];
}
$doctorFields = $this->db->getFieldNames('doctor_master') ?? [];
$availableFields = array_values(array_intersect($allTemplateFields, $doctorFields));
$options = [];
$templateDir = APPPATH . 'Views/billing/opd_templates';
if (is_dir($templateDir)) {
$htmlFiles = glob($templateDir . '/*.html') ?: [];
foreach ($htmlFiles as $file) {
$name = strtolower(trim((string) basename((string) $file, '.html')));
$name = preg_replace('/[^a-z0-9_\-]/', '', $name) ?? '';
if ($name !== '') {
$options[] = $name;
}
}
$phpFiles = glob($templateDir . '/*.php') ?: [];
foreach ($phpFiles as $file) {
$name = strtolower(trim((string) basename((string) $file, '.php')));
$name = preg_replace('/[^a-z0-9_\-]/', '', $name) ?? '';
if ($name !== '') {
$options[] = $name;
}
}
}
foreach ($availableFields as $field) {
$rows = $this->db->table('doctor_master')
->select($field . ' as template_name')
->where($field . ' IS NOT NULL', null, false)
->where($field . ' !=', '')
->distinct()
->get()
->getResultArray();
foreach ($rows as $row) {
$name = strtolower(trim((string) ($row['template_name'] ?? '')));
$name = preg_replace('/[^a-z0-9_\-]/', '', $name) ?? '';
if ($name !== '') {
$options[] = $name;
}
}
}
$options = $this->collectStoredTemplateOptions();
$opdPrintOptions = $options;
$options = array_values(array_unique($options));
sort($options);
$opdPrintOptions = array_values(array_unique($opdPrintOptions));
sort($opdPrintOptions);
return [
'fields' => $availableFields,
'options' => $options,
'opd_print_options' => $opdPrintOptions,
];
}
private function normalizeTemplateId(string $templateId): string
{
$templateId = strtolower(trim($templateId));
$templateId = str_replace([' ', '.'], '_', $templateId);
$templateId = preg_replace('/[^a-z0-9_\-]+/', '_', $templateId) ?? '';
$templateId = preg_replace('/_{2,}/', '_', $templateId) ?? $templateId;
return trim($templateId, '_-');
}
/**
* @return array<int, string>
*/
private function collectStoredTemplateOptions(): array
{
if (! $this->db->tableExists('hospital_setting')) {
return [];
}
$rows = $this->db->table('hospital_setting')
->select('s_name')
->like('s_name', self::OPD_TEMPLATE_KEY_PREFIX, 'after')
->get()
->getResultArray();
$options = [];
foreach ($rows as $row) {
$storageKey = (string) ($row['s_name'] ?? '');
if (! str_starts_with($storageKey, self::OPD_TEMPLATE_KEY_PREFIX)) {
continue;
}
$templateId = substr($storageKey, strlen(self::OPD_TEMPLATE_KEY_PREFIX));
$templateId = $this->normalizeTemplateId((string) $templateId);
if ($templateId !== '') {
$options[] = $templateId;
}
}
return $options;
}
}

View File

@ -41,7 +41,7 @@ class CreateLabAiExtractionTables extends Migration
'ai_provider' => [
'type' => 'VARCHAR',
'constraint' => 50,
'default' => 'azure-openai',
'default' => 'ai-server',
],
'model_name' => [
'type' => 'VARCHAR',

View File

@ -67,6 +67,7 @@
<?php
$templateFieldSet = array_flip($template_fields ?? []);
$templateOptions = $template_options ?? [];
$opdPrintTemplateOptions = $opd_print_template_options ?? [];
?>
<?php if (! empty($templateFieldSet)) : ?>
<div class="row g-3 mt-1">
@ -75,7 +76,7 @@
<label class="form-label">OPD Print Template</label>
<select class="form-select" name="tmpl_opd_print_format">
<option value="">-- Select Template --</option>
<?php foreach ($templateOptions as $tmpl) : ?>
<?php foreach ($opdPrintTemplateOptions as $tmpl) : ?>
<option value="<?= esc($tmpl) ?>" <?= combo_checked($tmpl, strtolower(trim((string) ($formData['tmpl_opd_print_format'] ?? '')))) ?>><?= esc($tmpl) ?></option>
<?php endforeach; ?>
</select>

View File

@ -89,6 +89,7 @@ if (!empty($data) && !empty($data[0])) {
<?php
$templateFieldSet = array_flip($template_fields ?? []);
$templateOptions = $template_options ?? [];
$opdPrintTemplateOptions = $opd_print_template_options ?? [];
?>
<?php if (! empty($templateFieldSet)) : ?>
<div class="row g-3 mt-1">
@ -97,7 +98,7 @@ if (!empty($data) && !empty($data[0])) {
<label class="form-label">OPD Print Template</label>
<select class="form-select" name="tmpl_opd_print_format">
<option value="">-- Select Template --</option>
<?php foreach ($templateOptions as $tmpl) : ?>
<?php foreach ($opdPrintTemplateOptions as $tmpl) : ?>
<option value="<?= esc($tmpl) ?>" <?= combo_checked($tmpl, strtolower(trim((string) ($data[0]->opd_print_format ?? '')))) ?>><?= esc($tmpl) ?></option>
<?php endforeach; ?>
</select>

View File

@ -227,7 +227,7 @@
$('#advice_hi').val((data.translated_text || '').toString());
var langName = (data.target_language_name || '').toString();
var provider = (data.provider || '').toString();
var providerLabel = provider === 'azure' ? 'Azure OpenAI' : 'AI';
var providerLabel = provider === 'ai-server' ? 'AI Server' : 'AI';
setMsg('ok', 'Translated to ' + (langName || 'selected language') + ' using ' + providerLabel + '.');
});
});

View File

@ -207,8 +207,9 @@ if ($user && method_exists($user, 'inGroup')) {
<div class="col-sm-6">
<a href="<?= base_url('opd_print/invoice_print_pdf') ?>/<?= esc($opd_master[0]->opd_id ?? 0) ?>" target="_blank" class="btn btn-outline-secondary">Print Invoice</a>
<a href="<?= base_url('opd_print/opd_PDF_print') ?>/<?= esc($opd_master[0]->opd_id ?? 0) ?>" target="_blank" class="btn btn-outline-secondary">Print OPD HEAD in Letter Head</a>
<a href="<?= base_url('opd_print/opd_Cont_print') ?>/<?= esc($opd_master[0]->opd_id ?? 0) ?>" target="_blank" class="btn btn-outline-secondary">Print Cont. Paper</a>
<a href="<?= base_url('opd_print/opd_blank_print') ?>/<?= esc($opd_master[0]->opd_id ?? 0) ?>" target="_blank" class="btn btn-outline-secondary">Print OPD HEAD in Blank Page</a>
<a href="<?= base_url('opd_print/opd_Cont_print') ?>/<?= esc($opd_master[0]->opd_id ?? 0) ?>" target="_blank" class="btn btn-outline-secondary">Print Cont. Paper</a>
</div>
<div class="col-sm-6">
<?php if (($refund_status ?? 0) == 1) : ?>

View File

@ -608,7 +608,7 @@
return;
}
if (isNewMedicine && (provider === 'azure' || provider === 'local-brand-fallback')) {
if (isNewMedicine && (provider === 'ai-server' || provider === 'local-brand-fallback')) {
if (window.confirm('Details prepared for a new medicine. Save this new master entry now?')) {
saveMedicine(buildMedicinePayload(), function() {
setMsg('ok', 'New medicine saved with suggested generic/salt details.');

View File

@ -0,0 +1,162 @@
<?php
$settings = $settings ?? [];
$notice = (string) ($notice ?? '');
$noticeType = (string) ($notice_type ?? 'success');
$pageSize = strtoupper((string) ($settings['page_size'] ?? 'A4'));
?>
<section class="content-header">
<div class="clearfix">
<div style="float:left;">
<h1>OPD Paper Print Settings</h1>
</div>
<div style="float:right; margin-top:8px;">
<a class="btn btn-sm btn-info" href="javascript:load_form_div('<?= base_url('Opd/print_template_builder') ?>?mode=list','maindiv','OPD Template List');">Template Builder</a>
</div>
</div>
</section>
<section class="content">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Dedicated OPD PDF page/header/footer settings</h3>
</div>
<div class="box-body">
<div id="opd_paper_notice">
<?php if ($notice !== ''): ?>
<div class="alert alert-<?= esc($noticeType) ?> py-2 mb-3"><?= esc($notice) ?></div>
<?php endif; ?>
</div>
<form method="post" action="<?= base_url('Opd/paper_print_settings') ?>" id="opd_paper_print_form">
<?= csrf_field() ?>
<div class="row">
<div class="col-md-3">
<label>Page Size</label>
<select class="form-control" name="page_size">
<option value="A4" <?= $pageSize === 'A4' ? 'selected' : '' ?>>A4</option>
<option value="A4-L" <?= $pageSize === 'A4-L' ? 'selected' : '' ?>>A4 Landscape</option>
<option value="A5" <?= $pageSize === 'A5' ? 'selected' : '' ?>>A5</option>
<option value="A6" <?= $pageSize === 'A6' ? 'selected' : '' ?>>A6</option>
<option value="LETTER" <?= $pageSize === 'LETTER' ? 'selected' : '' ?>>Letter</option>
<option value="LEGAL" <?= $pageSize === 'LEGAL' ? 'selected' : '' ?>>Legal</option>
</select>
</div>
</div>
<hr>
<h4 style="margin-top:0;">Page & Margin (cm)</h4>
<div class="row">
<div class="col-md-2">
<label>Top</label>
<input type="number" step="0.1" min="0" max="25" class="form-control" name="page_margin_top_cm" value="<?= esc((string) ($settings['page_margin_top_cm'] ?? '6.1')) ?>">
</div>
<div class="col-md-2">
<label>Bottom</label>
<input type="number" step="0.1" min="0" max="25" class="form-control" name="page_margin_bottom_cm" value="<?= esc((string) ($settings['page_margin_bottom_cm'] ?? '2.5')) ?>">
</div>
<div class="col-md-2">
<label>Left</label>
<input type="number" step="0.1" min="0" max="25" class="form-control" name="page_margin_left_cm" value="<?= esc((string) ($settings['page_margin_left_cm'] ?? '0.7')) ?>">
</div>
<div class="col-md-2">
<label>Right</label>
<input type="number" step="0.1" min="0" max="25" class="form-control" name="page_margin_right_cm" value="<?= esc((string) ($settings['page_margin_right_cm'] ?? '0.7')) ?>">
</div>
<div class="col-md-2">
<label>Header</label>
<input type="number" step="0.1" min="0" max="25" class="form-control" name="margin_header_cm" value="<?= esc((string) ($settings['margin_header_cm'] ?? '0.5')) ?>">
</div>
<div class="col-md-2">
<label>Footer</label>
<input type="number" step="0.1" min="0" max="25" class="form-control" name="margin_footer_cm" value="<?= esc((string) ($settings['margin_footer_cm'] ?? '1.5')) ?>">
</div>
</div>
<hr>
<h4 style="margin-top:0;">Header / Footer HTML</h4>
<div class="alert alert-info" style="padding:8px 10px; font-size:12px;">
Use variables with <b>{{name}}</b> or <b>{name}</b>.<br>
Hospital: <code>H_Name</code>, <code>H_address_1</code>, <code>H_address_2</code>, <code>H_phone_No</code>, <code>H_Email</code>, <code>H_logo</code>.<br>
Common: <code>print_time</code>, <code>current_date</code>, <code>doctor_name</code>.<br>
OPD: <code>pName</code>, <code>age_sex</code>, <code>phoneno</code>, <code>uhid_no</code>, <code>opd_no</code>, <code>opd_date</code>, <code>Complaint</code>, <code>diagnosis</code>, <code>medical</code>, <code>advice</code>.<br>
mPDF page tokens like <code>{PAGENO}</code> and <code>{nbpg}</code> are supported.
</div>
<div class="row">
<div class="col-md-12">
<label>Header HTML / mPDF tags</label>
<textarea class="form-control" name="header_html" rows="7" style="font-family:Consolas,Monaco,monospace;"><?= esc((string) ($settings['header_html'] ?? '')) ?></textarea>
</div>
<div class="col-md-12" style="margin-top:10px;">
<label>Footer HTML / mPDF tags</label>
<textarea class="form-control" name="footer_html" rows="5" style="font-family:Consolas,Monaco,monospace;"><?= esc((string) ($settings['footer_html'] ?? '')) ?></textarea>
</div>
</div>
<div style="margin-top:12px;">
<button type="submit" class="btn btn-primary btn-sm">Save OPD Print Settings</button>
</div>
</form>
</div>
</div>
</section>
<script>
(function () {
var form = document.getElementById('opd_paper_print_form');
var noticeBox = document.getElementById('opd_paper_notice');
function showNotice(type, message) {
if (!noticeBox) {
return;
}
var safeType = (type === 'success') ? 'success' : 'danger';
var safeText = String(message || '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
noticeBox.innerHTML = '<div class="alert alert-' + safeType + ' py-2 mb-3">' + safeText + '</div>';
}
if (!form) {
return;
}
form.addEventListener('submit', function (event) {
event.preventDefault();
var submitBtn = form.querySelector('button[type="submit"]');
if (submitBtn) {
submitBtn.disabled = true;
}
$.ajax({
url: form.action,
method: 'POST',
data: $(form).serialize(),
dataType: 'json',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.done(function (result) {
var status = (result && result.status) ? result.status : 'error';
showNotice(status === 'success' ? 'success' : 'danger', (result && result.notice) ? result.notice : 'Save failed.');
if (result && result.csrfName && result.csrfHash) {
var csrfInput = form.querySelector('input[name="' + result.csrfName + '"]');
if (csrfInput) {
csrfInput.value = result.csrfHash;
}
}
})
.fail(function () {
showNotice('danger', 'Save failed.');
})
.always(function () {
if (submitBtn) {
submitBtn.disabled = false;
}
});
});
})();
</script>

View File

@ -1,13 +0,0 @@
<div style="font-size:12px; line-height:1.45;">
{{painscale_img}}
{{vital_content}}
{{Complaint}}
{{diagnosis}}
{{Finding_Examinations}}
{{medical}}
{{investigation}}
{{Prescriber_Remarks}}
{{advice}}
{{next_visit}}
{{refer_to}}
</div>

View File

@ -1,23 +0,0 @@
<style>
@page {
margin-top: 3.2cm;
margin-bottom: 1.2cm;
margin-left: 0.5cm;
margin-right: 0.5cm;
margin-header: 0.5cm;
margin-footer: 0.5cm;
header: html_myHeader;
footer: html_myFooter;
}
body { font-family: freeserif, dejavusans, sans-serif; font-size: 10pt; color: #111; }
.part-divider { margin: 6px 0; border-top: 1px solid #666; }
</style>
<htmlpageheader name="myHeader">
{{hospital_section}}
</htmlpageheader>
<htmlpagefooter name="myFooter">
<p style="text-align:right; font-size:9px;">Print Time : {{print_time}}</p>
</htmlpagefooter>
{{patient_section}}
<div class="part-divider"></div>
{{content_section}}

View File

@ -1,23 +0,0 @@
<style>
@page {
margin-top: 3.2cm;
margin-bottom: 1.2cm;
margin-left: 0.5cm;
margin-right: 0.5cm;
margin-header: 0.5cm;
margin-footer: 0.5cm;
header: html_myHeader;
footer: html_myFooter;
}
body { font-family: freeserif, dejavusans, sans-serif; font-size: 10pt; color: #111; }
.part-divider { margin: 6px 0; border-top: 1px solid #666; }
</style>
<htmlpageheader name="myHeader">
{{hospital_section}}
</htmlpageheader>
<htmlpagefooter name="myFooter">
<p style="text-align:right; font-size:9px;">Print Time : {{print_time}}</p>
</htmlpagefooter>
{{patient_section}}
<div class="part-divider"></div>
{{content_section}}

View File

@ -1,10 +0,0 @@
<table>
<tr>
<td>
Patient Name :
<td>
<td>
{{pName}}
</td>
<tr>
</table>

View File

@ -1,18 +0,0 @@
<table width="100%" style="font-size:10pt; border:1px solid #777; border-collapse:collapse;" cellspacing="0" cellpadding="4">
<tr>
<td width="50%" valign="top" style="border:1px solid #777;">
<strong>{{pName}}</strong><br>
{{pRelative}}<br>
{{age_sex}}<br>
{{p_address}}<br>
Phone: {{phoneno}}
</td>
<td width="50%" valign="top" style="border:1px solid #777;">
UHID: {{uhid_no}}<br>
OPD: {{opd_no}}<br>
SR: {{opd_sr_no}}<br>
Date: {{opd_date}}<br>
{{exp_date}}
</td>
</tr>
</table>

View File

@ -1,26 +0,0 @@
<table width="100%" style="font-size:10pt; border:1px solid #777; border-collapse:collapse;" cellspacing="0" cellpadding="4">
<tr>
<td width="33.3%" valign="top" style="border:1px solid #777;">
Name : <strong>{{pName}}</strong><br>
{{pRelative}}<br>
Gender/Age : <b>{{age_sex}}</b><br>
Mob : {{phoneno}}<br>
Address : {{p_address}}
</td>
<td width="33.3%" valign="top" style="border:1px solid #777;">
UHID : {{uhid_no}}<br>
Sr No.: <b>{{opd_sr_no}}</b><br>
OPD No.: <b>{{opd_no}}</b><br>
<b>Date: {{opd_date}}</b><br>
{{exp_date}}
</td>
<td width="33.3%" valign="top" style="border:1px solid #777;">
<b>Department:</b><br>
{{SpecName}}<br>
{{opd_fee_desc}}<br>
<b>No. of Visit:</b> {{total_no_visit}}<br>
{{last_opdvisit_date}}<br>
Book Time : {{str_opd_book_date}}
</td>
</tr>
</table>

View File

@ -6,12 +6,20 @@
<div style="float:right; margin-top:8px;">
<div class="btn-group btn-group-sm" role="group" aria-label="Template Navigation">
<a class="btn btn-info" href="javascript:load_form_div('<?= base_url('Opd/print_template_builder') ?>?mode=list','maindiv','OPD Template List');">Template List</a>
<a class="btn btn-default" href="#opd-paper-settings">Paper Print Settings</a>
<button type="button" class="btn btn-primary" disabled>Edit Template</button>
</div>
</div>
</div>
</section>
<?php
$paperSettings = $paper_settings ?? [];
$paperPageSize = strtoupper((string) ($paperSettings['page_size'] ?? 'A4'));
$paperCustomWidth = (string) ($paperSettings['custom_width_mm'] ?? '210');
$paperCustomHeight = (string) ($paperSettings['custom_height_mm'] ?? '297');
?>
<section class="content">
<div class="row">
<div class="col-md-8">
@ -28,6 +36,7 @@
<div class="col-sm-6">
<label>Template Name</label>
<input type="text" id="tmpl_name" class="form-control" value="<?= esc($selected_name ?? 'default') ?>" placeholder="default">
<input type="hidden" id="tmpl_original_name" value="<?= esc($selected_name ?? '') ?>">
</div>
<div class="col-sm-6" style="padding-top:25px;">
<button type="button" class="btn btn-default" id="btn_new_part">New</button>
@ -35,24 +44,9 @@
<button type="button" class="btn btn-primary" id="btn_save_part">Save</button>
</div>
</div>
<div class="row" style="margin-top:8px;">
<div class="col-sm-8">
<label>Existing Templates</label>
<select id="tmpl_existing" class="form-control">
<option value="">-- Select existing template --</option>
<?php foreach (($template_names ?? []) as $nm) : ?>
<option value="<?= esc($nm) ?>" <?= ($selected_name ?? '') === $nm ? 'selected' : '' ?>><?= esc($nm) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-sm-4" style="padding-top:25px;">
<button type="button" class="btn btn-info" id="btn_use_existing">Use Selected</button>
</div>
</div>
<hr>
<div class="form-group">
<label>HTML Content</label>
<textarea id="tmpl_content" class="form-control" rows="20" style="font-family:Consolas,monospace;"><?= esc($template_content ?? '') ?></textarea>
<div class="form-group" style="margin-top:10px;">
<label>Template HTML Content (for template id save/load)</label>
<textarea id="tmpl_content" class="form-control" rows="10" style="font-family:Consolas,monospace;"><?= esc($template_content ?? '') ?></textarea>
</div>
<div id="tmpl_msg" class="text-muted"></div>
</div>
@ -76,6 +70,101 @@
</div>
</div>
</div>
<div class="row" style="margin-top:10px;">
<div class="col-md-12">
<div class="box box-success" id="opd-paper-settings">
<div class="box-header with-border">
<h3 class="box-title">OPD Paper Print Settings</h3>
</div>
<div class="box-body">
<div id="opd_paper_msg" class="text-muted" style="margin-bottom:8px;"></div>
<div class="form-group">
<label>Page Size</label>
<select id="opd_page_size" class="form-control input-sm">
<option value="A4" <?= $paperPageSize === 'A4' ? 'selected' : '' ?>>A4</option>
<option value="A4-L" <?= $paperPageSize === 'A4-L' ? 'selected' : '' ?>>A4 Landscape</option>
<option value="A5" <?= $paperPageSize === 'A5' ? 'selected' : '' ?>>A5</option>
<option value="A6" <?= $paperPageSize === 'A6' ? 'selected' : '' ?>>A6</option>
<option value="LETTER" <?= $paperPageSize === 'LETTER' ? 'selected' : '' ?>>Letter</option>
<option value="LEGAL" <?= $paperPageSize === 'LEGAL' ? 'selected' : '' ?>>Legal</option>
<option value="CUSTOM" <?= $paperPageSize === 'CUSTOM' ? 'selected' : '' ?>>Custom (mm)</option>
</select>
</div>
<div class="row" id="opd_custom_size_row" style="display:none; margin-top:-2px; margin-bottom:8px;">
<div class="col-xs-6">
<label>Custom Width (mm)</label>
<input type="number" step="1" min="20" max="600" id="opd_custom_width" class="form-control input-sm" value="<?= esc($paperCustomWidth) ?>">
</div>
<div class="col-xs-6">
<label>Custom Height (mm)</label>
<input type="number" step="1" min="20" max="1000" id="opd_custom_height" class="form-control input-sm" value="<?= esc($paperCustomHeight) ?>">
</div>
</div>
<div class="row" style="margin-top:6px;">
<div class="col-md-2 col-sm-4 col-xs-6" style="margin-bottom:8px;">
<label>Top (cm)</label>
<input type="number" step="0.1" min="0" max="25" id="opd_margin_top" class="form-control input-sm" value="<?= esc((string) ($paperSettings['page_margin_top_cm'] ?? '6.1')) ?>">
</div>
<div class="col-md-2 col-sm-4 col-xs-6" style="margin-bottom:8px;">
<label>Bottom (cm)</label>
<input type="number" step="0.1" min="0" max="25" id="opd_margin_bottom" class="form-control input-sm" value="<?= esc((string) ($paperSettings['page_margin_bottom_cm'] ?? '2.5')) ?>">
</div>
<div class="col-md-2 col-sm-4 col-xs-6" style="margin-bottom:8px;">
<label>Left (cm)</label>
<input type="number" step="0.1" min="0" max="25" id="opd_margin_left" class="form-control input-sm" value="<?= esc((string) ($paperSettings['page_margin_left_cm'] ?? '0.7')) ?>">
</div>
<div class="col-md-2 col-sm-4 col-xs-6" style="margin-bottom:8px;">
<label>Right (cm)</label>
<input type="number" step="0.1" min="0" max="25" id="opd_margin_right" class="form-control input-sm" value="<?= esc((string) ($paperSettings['page_margin_right_cm'] ?? '0.7')) ?>">
</div>
<div class="col-md-2 col-sm-4 col-xs-6" style="margin-bottom:8px;">
<label>Header (cm)</label>
<input type="number" step="0.1" min="0" max="25" id="opd_margin_header" class="form-control input-sm" value="<?= esc((string) ($paperSettings['margin_header_cm'] ?? '0.5')) ?>">
</div>
<div class="col-md-2 col-sm-4 col-xs-6" style="margin-bottom:8px;">
<label>Footer (cm)</label>
<input type="number" step="0.1" min="0" max="25" id="opd_margin_footer" class="form-control input-sm" value="<?= esc((string) ($paperSettings['margin_footer_cm'] ?? '1.5')) ?>">
</div>
</div>
<div class="form-group" style="margin-top:8px;">
<label>Header HTML</label>
<textarea id="opd_header_html" class="form-control input-sm" rows="4" style="font-family:Consolas,monospace;"><?= esc((string) ($paperSettings['header_html'] ?? '')) ?></textarea>
</div>
<div class="form-group" style="margin-top:8px;">
<label>Footer HTML</label>
<textarea id="opd_footer_html" class="form-control input-sm" rows="4" style="font-family:Consolas,monospace;"><?= esc((string) ($paperSettings['footer_html'] ?? '')) ?></textarea>
</div>
<div class="form-group" style="margin-top:8px;">
<label>HTML Content</label>
<textarea id="opd_paper_html_content" class="form-control input-sm" rows="8" style="font-family:Consolas,monospace;"><?= esc((string) ($paperSettings['paper_html_content'] ?? '')) ?></textarea>
</div>
<div class="small text-muted" style="margin-bottom:8px;">
<strong>How it works:</strong><br>
1. Margin values (cm) auto-populate the CSS @page block<br>
2. <strong>Header HTML:</strong> Enter HTML for top of page (logo, hospital name, address, doctor info using {{variables}})<br>
3. <strong>Footer HTML:</strong> Enter HTML for bottom of page (print time, page number, etc. using {{variables}})<br>
4. <strong>HTML Content:</strong> Enter main body content between header and footer (patient info, diagnosis, etc.)<br><br>
<strong>Available variables:</strong><br>
Hospital: {{H_Name}}, {{H_address_1}}, {{H_address_2}}, {{H_phone_No}}, {{H_Email}}, {{H_logo}}<br>
Patient/OPD: {{pName}}, {{age_sex}}, {{phoneno}}, {{uhid_no}}, {{opd_no}}, {{opd_date}}<br>
Medical: {{Complaint}}, {{diagnosis}}, {{medical}}, {{advice}}, {{doctor_name}}, {{doctor_sign_html}}<br>
Dates: {{print_time}}, {{current_date}}<br>
Margins (cm): {{MarginTop}}, {{MarginBottom}}, {{MarginLeft}}, {{MarginRight}}, {{MarginHeader}}, {{MarginFooter}}
</div>
<button type="button" class="btn btn-success btn-sm" id="btn_save_opd_paper">Save OPD Print Settings</button>
<button type="button" class="btn btn-default btn-sm" id="btn_reset_opd_paper">Reset</button>
</div>
</div>
</div>
</div>
</section>
<script>
@ -101,31 +190,107 @@
el.textContent = msg || '';
}
function setPaperMsg(msg, ok) {
var el = document.getElementById('opd_paper_msg');
if (!el) {
return;
}
el.className = ok ? 'text-success' : 'text-danger';
el.textContent = msg || '';
}
function normalizeTemplateId(value) {
value = (value || '').trim().toLowerCase().replace(/[ .]+/g, '_').replace(/[^a-z0-9_\-]+/g, '_').replace(/_+/g, '_');
return value.replace(/^[_-]+|[_-]+$/g, '');
}
document.getElementById('btn_load_part').addEventListener('click', function () {
var name = (document.getElementById('tmpl_name').value || 'default').trim();
var name = normalizeTemplateId(document.getElementById('tmpl_name').value || 'default');
if (!name) { name = 'default'; }
document.getElementById('tmpl_name').value = name;
load_form_div('<?= base_url('Opd/print_template_builder') ?>?mode=edit&name=' + encodeURIComponent(name), 'maindiv', 'OPD Template Edit');
});
document.getElementById('btn_new_part').addEventListener('click', function () {
document.getElementById('tmpl_name').value = '';
document.getElementById('tmpl_content').value = '';
document.getElementById('tmpl_original_name').value = '';
setMsg('Enter new template name, then write HTML and click Save.', true);
});
document.getElementById('btn_use_existing').addEventListener('click', function () {
var selected = document.getElementById('tmpl_existing').value || '';
if (!selected) {
setMsg('Please select an existing template.', false);
function toggleCustomSize() {
var pageSize = document.getElementById('opd_page_size');
var customRow = document.getElementById('opd_custom_size_row');
if (!pageSize || !customRow) {
return;
}
document.getElementById('tmpl_name').value = selected;
load_form_div('<?= base_url('Opd/print_template_builder') ?>?mode=edit&name=' + encodeURIComponent(selected), 'maindiv', 'OPD Template Edit');
});
customRow.style.display = (pageSize.value === 'CUSTOM') ? 'block' : 'none';
}
function buildTemplateFromPaperFields() {
var top = document.getElementById('opd_margin_top');
var bottom = document.getElementById('opd_margin_bottom');
var left = document.getElementById('opd_margin_left');
var right = document.getElementById('opd_margin_right');
var marginHeader = document.getElementById('opd_margin_header');
var marginFooter = document.getElementById('opd_margin_footer');
var header = document.getElementById('opd_header_html');
var footer = document.getElementById('opd_footer_html');
var body = document.getElementById('opd_paper_html_content');
var pageBlock = '<style>@page {\n'
+ 'margin-top: ' + ((top && top.value) || '6.1') + 'cm;\n'
+ 'margin-bottom: ' + ((bottom && bottom.value) || '2.5') + 'cm;\n'
+ 'margin-left: ' + ((left && left.value) || '0.7') + 'cm;\n'
+ 'margin-right: ' + ((right && right.value) || '0.7') + 'cm;\n'
+ 'margin-header: ' + ((marginHeader && marginHeader.value) || '0.5') + 'cm;\n'
+ 'margin-footer: ' + ((marginFooter && marginFooter.value) || '1.5') + 'cm;\n'
+ 'header: html_myHeader;\n'
+ 'footer: html_myFooter;\n'
+ '}\n</style>\n\n';
var headerBlock = '';
var headerText = (header && header.value) ? header.value.trim() : '';
if (headerText !== '') {
if (/<\s*htmlpageheader\b/i.test(headerText)) {
headerBlock = headerText + '\n\n';
} else {
headerBlock = '<htmlpageheader name="myHeader">\n' + headerText + '\n</htmlpageheader>\n\n';
}
}
var footerBlock = '';
var footerText = (footer && footer.value) ? footer.value.trim() : '';
if (footerText !== '') {
if (/<\s*htmlpagefooter\b/i.test(footerText)) {
footerBlock = footerText + '\n\n';
} else {
footerBlock = '<htmlpagefooter name="myFooter">\n' + footerText + '\n</htmlpagefooter>\n\n';
}
}
var bodyText = (body && body.value) ? body.value.trim() : '';
return pageBlock + headerBlock + footerBlock + bodyText;
}
var pageSizeSelect = document.getElementById('opd_page_size');
if (pageSizeSelect) {
pageSizeSelect.addEventListener('change', toggleCustomSize);
}
toggleCustomSize();
document.getElementById('btn_save_part').addEventListener('click', function () {
var name = (document.getElementById('tmpl_name').value || '').trim();
var name = normalizeTemplateId(document.getElementById('tmpl_name').value || '');
var originalName = normalizeTemplateId(document.getElementById('tmpl_original_name').value || '');
var content = document.getElementById('tmpl_content').value || '';
document.getElementById('tmpl_name').value = name;
if (!content.trim()) {
content = buildTemplateFromPaperFields();
var tmplContentEl = document.getElementById('tmpl_content');
if (tmplContentEl && content.trim()) {
tmplContentEl.value = content;
}
}
if (!name) {
setMsg('Template name required', false);
return;
@ -138,22 +303,191 @@
};
payload[csrf.name] = csrf.value;
$.post('<?= base_url('Opd/print_template_save') ?>', payload, function (res) {
if (res && res.csrfName && res.csrfHash) {
var tokenInput = document.querySelector('input[name="' + res.csrfName + '"]');
if (tokenInput) tokenInput.value = res.csrfHash;
}
if (!res || Number(res.update || 0) !== 1) {
setMsg((res && res.error_text) ? res.error_text : 'Unable to save template', false);
return;
}
setMsg('Saved: ' + (res.section || 'full') + '/' + (res.name || name), true);
setTimeout(function () {
load_form_div('<?= base_url('Opd/print_template_builder') ?>?mode=edit&name=' + encodeURIComponent(res.name || name), 'maindiv', 'OPD Template Edit');
}, 250);
}, 'json').fail(function () {
setMsg('Unable to save template', false);
});
var saveTemplate = function () {
$.post('<?= base_url('Opd/print_template_save') ?>', payload, function (res) {
if (res && res.csrfName && res.csrfHash) {
var tokenInput = document.querySelector('input[name="' + res.csrfName + '"]');
if (tokenInput) tokenInput.value = res.csrfHash;
}
if (!res || Number(res.update || 0) !== 1) {
setMsg((res && res.error_text) ? res.error_text : 'Unable to save template', false);
return;
}
document.getElementById('tmpl_original_name').value = (res.name || name);
setMsg('Saved: ' + (res.section || 'full') + '/' + (res.name || name), true);
setTimeout(function () {
load_form_div('<?= base_url('Opd/print_template_builder') ?>?mode=edit&name=' + encodeURIComponent(res.name || name), 'maindiv', 'OPD Template Edit');
}, 250);
}, 'json').fail(function () {
setMsg('Unable to save template', false);
});
};
if (originalName && originalName !== name) {
var renamePayload = {
old_name: originalName,
new_name: name
};
var renameCsrf = getCsrfPair();
renamePayload[renameCsrf.name] = renameCsrf.value;
$.post('<?= base_url('Opd/print_template_rename') ?>', renamePayload, function (renameRes) {
if (renameRes && renameRes.csrfName && renameRes.csrfHash) {
var renameTokenInput = document.querySelector('input[name="' + renameRes.csrfName + '"]');
if (renameTokenInput) renameTokenInput.value = renameRes.csrfHash;
}
if (!renameRes || Number(renameRes.update || 0) !== 1) {
setMsg((renameRes && renameRes.error_text) ? renameRes.error_text : 'Unable to rename template', false);
return;
}
document.getElementById('tmpl_original_name').value = name;
saveTemplate();
}, 'json').fail(function () {
setMsg('Unable to rename template', false);
});
return;
}
saveTemplate();
});
var savePaperBtn = document.getElementById('btn_save_opd_paper');
if (savePaperBtn) {
savePaperBtn.addEventListener('click', function () {
var csrf = getCsrfPair();
var currentTemplateName = normalizeTemplateId(document.getElementById('tmpl_name').value || '');
var payload = {
template_name: currentTemplateName,
page_size: (document.getElementById('opd_page_size').value || 'A4'),
custom_width_mm: document.getElementById('opd_custom_width').value || '210',
custom_height_mm: document.getElementById('opd_custom_height').value || '297',
page_margin_top_cm: document.getElementById('opd_margin_top').value || '6.1',
page_margin_bottom_cm: document.getElementById('opd_margin_bottom').value || '2.5',
page_margin_left_cm: document.getElementById('opd_margin_left').value || '0.7',
page_margin_right_cm: document.getElementById('opd_margin_right').value || '0.7',
margin_header_cm: document.getElementById('opd_margin_header').value || '0.5',
margin_footer_cm: document.getElementById('opd_margin_footer').value || '1.5',
header_html: document.getElementById('opd_header_html').value || '',
footer_html: document.getElementById('opd_footer_html').value || '',
paper_html_content: document.getElementById('opd_paper_html_content').value || ''
};
payload[csrf.name] = csrf.value;
savePaperBtn.disabled = true;
$.ajax({
url: '<?= base_url('Opd/paper_print_settings') ?>',
method: 'POST',
data: payload,
dataType: 'json',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}).done(function (res) {
if (res && res.csrfName && res.csrfHash) {
var tokenInput = document.querySelector('input[name="' + res.csrfName + '"]');
if (tokenInput) tokenInput.value = res.csrfHash;
}
if (!res || res.status !== 'success') {
setPaperMsg((res && res.notice) ? res.notice : 'Unable to save OPD print settings', false);
return;
}
// Keep template library in sync when user saves paper settings from edit page.
if (currentTemplateName) {
var tmplCsrf = getCsrfPair();
var templatePayload = {
section: 'full',
name: currentTemplateName,
content: buildTemplateFromPaperFields()
};
templatePayload[tmplCsrf.name] = tmplCsrf.value;
$.post('<?= base_url('Opd/print_template_save') ?>', templatePayload, function (tmplRes) {
if (tmplRes && tmplRes.csrfName && tmplRes.csrfHash) {
var tmplTokenInput = document.querySelector('input[name="' + tmplRes.csrfName + '"]');
if (tmplTokenInput) tmplTokenInput.value = tmplRes.csrfHash;
}
if (!tmplRes || Number(tmplRes.update || 0) !== 1) {
setPaperMsg((res.notice || 'OPD print settings saved.') + ' But template id save failed.', false);
setMsg((tmplRes && tmplRes.error_text) ? tmplRes.error_text : 'Unable to save template id', false);
return;
}
setMsg('Saved: full/' + (tmplRes.name || currentTemplateName), true);
setPaperMsg((res.notice || 'OPD print settings saved.') + ' Template id synced: ' + (tmplRes.name || currentTemplateName), true);
}, 'json').fail(function () {
setPaperMsg((res.notice || 'OPD print settings saved.') + ' But template id save failed.', false);
setMsg('Unable to save template id', false);
});
} else {
setPaperMsg((res.notice || 'OPD print settings saved.'), true);
}
}).fail(function () {
setPaperMsg('Unable to save OPD print settings', false);
}).always(function () {
savePaperBtn.disabled = false;
});
});
}
var resetPaperBtn = document.getElementById('btn_reset_opd_paper');
if (resetPaperBtn) {
resetPaperBtn.addEventListener('click', function () {
var csrf = getCsrfPair();
var currentTemplateName = normalizeTemplateId(document.getElementById('tmpl_name').value || '');
var payload = {
reset: 1,
template_name: currentTemplateName
};
payload[csrf.name] = csrf.value;
resetPaperBtn.disabled = true;
$.ajax({
url: '<?= base_url('Opd/paper_print_settings') ?>',
method: 'POST',
data: payload,
dataType: 'json',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}).done(function (res) {
if (res && res.csrfName && res.csrfHash) {
var tokenInput = document.querySelector('input[name="' + res.csrfName + '"]');
if (tokenInput) tokenInput.value = res.csrfHash;
}
if (!res || res.status !== 'success') {
setPaperMsg((res && res.notice) ? res.notice : 'Unable to reset OPD print settings', false);
return;
}
var settings = res.settings || {};
document.getElementById('opd_page_size').value = settings.page_size || 'A4';
document.getElementById('opd_custom_width').value = settings.custom_width_mm || '210';
document.getElementById('opd_custom_height').value = settings.custom_height_mm || '297';
document.getElementById('opd_margin_top').value = settings.page_margin_top_cm || '6.1';
document.getElementById('opd_margin_bottom').value = settings.page_margin_bottom_cm || '2.5';
document.getElementById('opd_margin_left').value = settings.page_margin_left_cm || '0.7';
document.getElementById('opd_margin_right').value = settings.page_margin_right_cm || '0.7';
document.getElementById('opd_margin_header').value = settings.margin_header_cm || '0.5';
document.getElementById('opd_margin_footer').value = settings.margin_footer_cm || '1.5';
document.getElementById('opd_header_html').value = settings.header_html || '';
document.getElementById('opd_footer_html').value = settings.footer_html || '';
document.getElementById('opd_paper_html_content').value = settings.paper_html_content || '';
toggleCustomSize();
setPaperMsg((res.notice || 'OPD print settings reset.'), true);
}).fail(function () {
setPaperMsg('Unable to reset OPD print settings', false);
}).always(function () {
resetPaperBtn.disabled = false;
});
});
}
})();
</script>

View File

@ -26,6 +26,7 @@
</div>
</div>
<div class="box-body">
<?= csrf_field() ?>
<div id="tmpl_list_msg" class="text-muted" style="margin-bottom:8px;"></div>
<div class="table-responsive">
<table class="table table-bordered table-striped">
@ -33,7 +34,7 @@
<tr>
<th style="width: 70px;">#</th>
<th>Template Name</th>
<th style="width: 140px;">Action</th>
<th style="width: 220px;">Action</th>
</tr>
</thead>
<tbody>
@ -48,6 +49,8 @@
<td><?= esc($nm) ?></td>
<td>
<a class="btn btn-primary btn-xs" href="javascript:load_form_div('<?= base_url('Opd/print_template_builder') ?>?mode=edit&name=<?= esc($nm) ?>','maindiv','OPD Template Edit');">Edit</a>
<button type="button" class="btn btn-default btn-xs btn_rename_template" data-name="<?= esc($nm) ?>">Rename</button>
<button type="button" class="btn btn-danger btn-xs btn_delete_template" data-name="<?= esc($nm) ?>">Delete</button>
</td>
</tr>
<?php endforeach; ?>
@ -61,6 +64,28 @@
<script>
(function () {
function normalizeTemplateId(value) {
value = (value || '').trim().toLowerCase().replace(/[ .]+/g, '_').replace(/[^a-z0-9_\-]+/g, '_').replace(/_+/g, '_');
return value.replace(/^[_-]+|[_-]+$/g, '');
}
function getCsrfPair() {
var input = document.querySelector('input[name^="csrf_"]');
if (!input) {
return { name: 'csrf_test_name', value: '' };
}
return { name: input.getAttribute('name'), value: input.value };
}
function setListMsg(msg, ok) {
var el = document.getElementById('tmpl_list_msg');
if (!el) {
return;
}
el.className = ok ? 'text-success' : 'text-danger';
el.textContent = msg || '';
}
var btn = document.getElementById('btn_new_template');
if (!btn) {
return;
@ -71,17 +96,102 @@
if (name === null) {
return;
}
name = (name || '').trim().toLowerCase().replace(/[^a-z0-9_\-]/g, '');
name = normalizeTemplateId(name);
if (!name) {
var msg = document.getElementById('tmpl_list_msg');
if (msg) {
msg.className = 'text-danger';
msg.textContent = 'Valid template name required.';
}
setListMsg('Valid template name required.', false);
return;
}
load_form_div('<?= base_url('Opd/print_template_builder') ?>?mode=edit&name=' + encodeURIComponent(name), 'maindiv', 'OPD Template Edit');
});
var renameButtons = document.querySelectorAll('.btn_rename_template');
renameButtons.forEach(function (renameBtn) {
renameBtn.addEventListener('click', function () {
var oldName = (renameBtn.getAttribute('data-name') || '').trim();
if (!oldName) {
return;
}
var newName = window.prompt('Enter new template name (letters, numbers, _ or -):', oldName);
if (newName === null) {
return;
}
newName = normalizeTemplateId(newName);
if (!newName) {
setListMsg('Valid new template name required.', false);
return;
}
if (newName === oldName) {
setListMsg('New template name is same as current name.', false);
return;
}
var csrf = getCsrfPair();
var payload = {
old_name: oldName,
new_name: newName
};
payload[csrf.name] = csrf.value;
$.post('<?= base_url('Opd/print_template_rename') ?>', payload, function (res) {
if (res && res.csrfName && res.csrfHash) {
var tokenInput = document.querySelector('input[name="' + res.csrfName + '"]');
if (tokenInput) {
tokenInput.value = res.csrfHash;
}
}
if (!res || Number(res.update || 0) !== 1) {
setListMsg((res && res.error_text) ? res.error_text : 'Unable to rename template', false);
return;
}
setListMsg('Template renamed: ' + oldName + ' -> ' + (res.new_name || newName), true);
load_form_div('<?= base_url('Opd/print_template_builder') ?>?mode=list', 'maindiv', 'OPD Template List');
}, 'json').fail(function () {
setListMsg('Unable to rename template', false);
});
});
});
var deleteButtons = document.querySelectorAll('.btn_delete_template');
deleteButtons.forEach(function (deleteBtn) {
deleteBtn.addEventListener('click', function () {
var templateName = (deleteBtn.getAttribute('data-name') || '').trim();
if (!templateName) {
return;
}
if (!confirm('Are you sure you want to delete template "' + templateName + '"? This action cannot be undone.')) {
return;
}
var csrf = getCsrfPair();
var payload = {
template_name: templateName
};
payload[csrf.name] = csrf.value;
$.post('<?= base_url('Opd/print_template_delete') ?>', payload, function (res) {
if (res && res.csrfName && res.csrfHash) {
var tokenInput = document.querySelector('input[name="' + res.csrfName + '"]');
if (tokenInput) {
tokenInput.value = res.csrfHash;
}
}
if (!res || Number(res.update || 0) !== 1) {
setListMsg((res && res.error_text) ? res.error_text : 'Unable to delete template', false);
return;
}
setListMsg('Template deleted: ' + templateName, true);
load_form_div('<?= base_url('Opd/print_template_builder') ?>?mode=list', 'maindiv', 'OPD Template List');
}, 'json').fail(function () {
setListMsg('Unable to delete template', false);
});
});
});
})();
</script>

View File

@ -1 +0,0 @@
<H1>Hello World</h1>

View File

@ -1 +0,0 @@
<H1>Hello World</h1>

View File

@ -1 +0,0 @@
<h1>Test</h1>

View File

@ -1,74 +0,0 @@
<?php
$opd = $opd_master[0] ?? null;
$patient = $patient_master[0] ?? null;
$doctor = $doctor_master[0] ?? null;
$pName = strtoupper(trim((string) (($patient->title ?? '') . ' ' . ($patient->p_fname ?? ''))));
$pRelative = trim((string) (($patient->p_relative ?? '') . ' ' . ($patient->p_rname ?? '')));
$ageSex = trim((string) (($patient->xgender ?? '') . ' / ' . ($patient->age ?? '')));
$phone = (string) ($patient->mphone1 ?? '');
$pAddress = trim((string) (($patient->add1 ?? '') . ', ' . ($patient->add2 ?? '') . ', ' . ($patient->city ?? '')), ' ,');
$uhidNo = (string) ($patient->p_code ?? '');
$opdSrNo = (string) ($opd->opd_no ?? '');
$opdNo = (string) ($opd->opd_code ?? '');
$opdDate = (string) ($opd->str_apointment_date ?? '');
$expDate = (string) ($opd->opd_Exp_Date ?? '');
$specName = trim((string) ($doctor->SpecName ?? $opd->doc_spec ?? ''));
$opdFeeDesc = trim((string) ($opd->opd_fee_desc ?? ''));
$totalNoVisit = (string) ($opd->no_visit ?? '');
$lastVisitDate = trim((string) (($old_opd[0]->str_apointment_date ?? '') ?: ''));
$bookTime = (string) ($opd->apointment_date ?? '');
$lastVisitLine = $lastVisitDate !== '' ? ('<br>Last Visit : ' . esc($lastVisitDate)) : '';
$expDateLine = $expDate !== '' ? ('<b>Valid Upto : </b>' . esc($expDate)) : '';
?>
<style>
@page {
margin-top: 5.5cm;
margin-bottom: 1.2cm;
margin-left: 0.5cm;
margin-right: 0.5cm;
margin-header: 0.5cm;
margin-footer: 0.5cm;
header: html_myHeader;
footer: html_myFooter;
}
body { font-family: freeserif, dejavusans, sans-serif; font-size: 10pt; }
</style>
<htmlpageheader name="myHeader">
<p align="center"></p>
</htmlpageheader>
<htmlpagefooter name="myFooter">
<p align="center">Print Time : <?= date('d-m-Y H:i:s') ?></p>
</htmlpagefooter>
<table width="100%" border="0" style="font-size:10pt;">
<tr>
<td width="33.3%" valign="top">
Name : <strong><?= esc($pName) ?></strong><br>
<?= esc($pRelative) ?><br>
Gender/Age : <b><?= esc($ageSex) ?></b><br>
Mob : <?= esc($phone) ?><br>
Address : <?= esc($pAddress) ?>
</td>
<td width="33.3%" valign="top">
UHID : <?= esc($uhidNo) ?><br>
Sr No.: <b><?= esc($opdSrNo) ?></b><br>
OPD No.: <b><?= esc($opdNo) ?></b><br>
<br><b>Date: <?= esc($opdDate) ?></b><br>
<?= $expDateLine ?>
</td>
<td width="33.3%" valign="top">
<b>DEPARTMENT :</b><br>
<?= esc($specName) ?><br>
<?= esc($opdFeeDesc) ?><br>
<b>No. of Visit</b> : <?= esc($totalNoVisit) ?>
<?= $lastVisitLine ?><br>
Book Time : <?= esc($bookTime) ?>
</td>
</tr>
</table>

View File

@ -0,0 +1,84 @@
<style>@page {
margin-top: 4cm;
margin-bottom: 2.5cm;
margin-left: 0.7cm;
margin-right: 0.7cm;
margin-header: 0.5cm;
margin-footer: 1.5cm;
header: html_myHeader;
footer: html_myFooter;
}
</style>
<htmlpageheader name="myHeader">
<table cellspacing="0" style="font-size: 10px;width:100%;border-style: inset;">
<tr>
<td style="width: 120px;vertical-align: top;">
<img style="width: 100px;vertical-align: top;" src="{{H_logo_abs}}" />
</td>
<td style="width: 400px;vertical-align: top;text-align: left;">
<p align="center" style="font-size: 20px;">{{H_Name}}</p>
<p align="center" style="font-size: 12px">{{H_address_1}}, {{H_address_2}}<br>Phone: {{H_phone_No}}</p>
</td>
<td style="width: 300px;vertical-align: top;text-align: right;">
<p align="right" style="font-size: 22px;">Dr. {{doctor_name}}</p>
{{doctor_sign_html}}
</td>
</tr>
</table>
<hr/>
</htmlpageheader>
<htmlpagefooter name="myFooter">
<p align="right" style="font-size: 10px;">Prescription Generated: {{print_time}}</p>
<p style="font-size: 9px;">Please ensure doctor registration number and signature/stamp are present for regulatory compliance.</p>
</htmlpagefooter>
<table width="100%" style="font-size: 11px; border-collapse: collapse;">
<tr>
<td width="50%">
<strong>Name:</strong> {{pName}}<br>
<strong>S/o:</strong> [Father Name]<br>
<strong>Gender/Age:</strong> {{age_sex}}<br>
<strong>Mob:</strong> {{phoneno}}<br>
<strong>Address:</strong> [Address]
</td>
<td width="50%">
<strong>UHID:</strong> {{uhid_no}}<br>
<strong>Sr No.:</strong> [Sr No]<br>
<strong>OPD No.:</strong> {{opd_no}}<br>
<strong>Date & Time:</strong> {{opd_date}} 12:00 AM<br>
<strong>Valid Upto:</strong> [Valid Date]
</td>
</tr>
</table>
<table width="100%" style="font-size: 11px; border-collapse: collapse; margin-top: 10px;">
<tr>
<td width="33%">
<strong>DEPARTMENT:</strong><br>{{Complaint}}
</td>
<td width="33%">
<strong>DIAGNOSIS:</strong><br>{{diagnosis}}
</td>
<td width="33%">
<strong>Last Visit:</strong> [Date]<br>
<strong>No. of Visit:</strong> [Count]
</td>
</tr>
</table>
<h3 style="margin: 15px 0 10px 0;">Rx :</h3>
{{medical}}
<p style="margin-top: 20px; font-size: 10px;">
<strong>Investigation:</strong> {{investigation}}<br>
<strong>Advice:</strong> {{advice}}
</p>
<div style="margin-top: 40px; text-align: right;">
<p><strong>Dr. {{doctor_name}}</strong></p>
{{doctor_sign_html}}
<p style="font-size: 10px; margin-top: 5px;"><strong>Reg. No.: 80982</strong></p>
</div>

View File

@ -0,0 +1,86 @@
<style>@page {
margin-top: 4cm;
margin-bottom: 2.5cm;
margin-left: 0.7cm;
margin-right: 0.7cm;
margin-header: 0.5cm;
margin-footer: 1.5cm;
header: html_myHeader;
footer: html_myFooter;
}
</style>
<htmlpageheader name="myHeader">
<table cellspacing="0" style="font-size: 10px;width:100%;border-style: inset;">
<tr>
<td style="width: 120px;vertical-align: top;">
<img style="width: 100px;vertical-align: top;" src="assets/images/{{H_logo}}" />
</td>
<td style="width: 400px;vertical-align: top;text-align: left;">
<p align="center" style="font-size: 20px;">{{H_Name}}</p>
<p align="center" style="font-size: 12px">{{H_address_1}}, {{H_address_2}}<br>
Phone: {{H_phone_No}}
</p>
</td>
<td style="width: 300px;vertical-align: top;text-align: right;">
<p align="right" style="font-size: 22px;">Dr. {{doctor_name}}</p>
{{doctor_sign_html}}
</td>
</tr>
</table>
<hr/>
</htmlpageheader>
<htmlpagefooter name="myFooter">
<p align="right" style="font-size: 10px;">Prescription Generated: {{print_time}}</p>
<p style="font-size: 9px;">Please ensure doctor registration number and signature/stamp are present for regulatory compliance.</p>
</htmlpagefooter>
<table width="100%" style="font-size: 11px; border-collapse: collapse;">
<tr>
<td width="50%">
<strong>Name:</strong> {{pName}}<br>
<strong>S/o:</strong> [Father Name]<br>
<strong>Gender/Age:</strong> {{age_sex}}<br>
<strong>Mob:</strong> {{phoneno}}<br>
<strong>Address:</strong> [Address]
</td>
<td width="50%">
<strong>UHID:</strong> {{uhid_no}}<br>
<strong>Sr No.:</strong> [Sr No]<br>
<strong>OPD No.:</strong> {{opd_no}}<br>
<strong>Date & Time:</strong> {{opd_date}} 12:00 AM<br>
<strong>Valid Upto:</strong> [Valid Date]
</td>
</tr>
</table>
<table width="100%" style="font-size: 11px; border-collapse: collapse; margin-top: 10px;">
<tr>
<td width="33%">
<strong>DEPARTMENT:</strong><br>{{Complaint}}
</td>
<td width="33%">
<strong>DIAGNOSIS:</strong><br>{{diagnosis}}
</td>
<td width="33%">
<strong>Last Visit:</strong> [Date]<br>
<strong>No. of Visit:</strong> [Count]
</td>
</tr>
</table>
<h3 style="margin: 15px 0 10px 0;">Rx :</h3>
{{medical}}
<p style="margin-top: 20px; font-size: 10px;">
<strong>Investigation:</strong> {{investigation}}<br>
<strong>Advice:</strong> {{advice}}
</p>
<div style="margin-top: 40px; text-align: right;">
<p><strong>Dr. {{doctor_name}}</strong></p>
{{doctor_sign_html}}
<p style="font-size: 10px; margin-top: 5px;"><strong>Reg. No.: 80982</strong></p>
</div>

View File

@ -1 +0,0 @@
<h1>Test again</h1>

View File

@ -5,7 +5,7 @@ $studyName = trim((string) ($study_name ?? ''));
$findingsHtml = (string) ($findings_html ?? '');
$impressionHtml = (string) ($impression_html ?? '');
$summaryText = trim((string) ($summary_text ?? ''));
$provider = trim((string) ($provider ?? 'azure-openai'));
$provider = trim((string) ($provider ?? 'ai-server'));
$model = trim((string) ($model ?? ''));
$findingsInputId = 'ai_findings_' . $batchId;
$impressionInputId = 'ai_impression_' . $batchId;
@ -18,8 +18,8 @@ if ($providerKey === 'local-xray-fallback') {
} elseif ($providerKey === 'regex-fallback') {
$providerLabel = 'Regex Fallback';
$providerBadgeClass = 'bg-warning text-dark';
} elseif ($providerKey === 'azure-openai') {
$providerLabel = 'Azure OpenAI';
} elseif ($providerKey === 'ai-server') {
$providerLabel = 'AI Server';
$providerBadgeClass = 'bg-info text-dark';
}
$isFallback = in_array($providerKey, ['local-xray-fallback', 'regex-fallback'], true);

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB