diff --git a/app/Controllers/Billing/Charges.php b/app/Controllers/Billing/Charges.php index 9578e66..5a5fdb8 100644 --- a/app/Controllers/Billing/Charges.php +++ b/app/Controllers/Billing/Charges.php @@ -841,6 +841,43 @@ class Charges extends BaseController ]); } + if ($mode === 5) { + // Cash return — patient overpaid (negative pending amount) + $returnAmount = $paidAmount - $netAmount; + if ($returnAmount <= 0) { + return $this->response->setJSON([ + 'update' => 0, + 'error_text' => 'No overpayment to return.', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + + $paydata = [ + 'payment_mode' => (int) ($invoice['payment_mode'] ?? 1), + 'payof_type' => '2', + 'payof_id' => $invId, + 'payof_code' => $invoice['invoice_code'] ?? '', + 'credit_debit' => '1', + 'amount' => $returnAmount, + 'payment_date' => date('Y-m-d H:i:s'), + 'remark' => 'Cash Return - Overpayment after item removal', + 'update_by' => $userNameInfo . ' [' . $userId . ']', + 'update_by_id' => $userId, + 'insert_code' => $spid, + ]; + + $paymentModel->insertPayment($paydata); + $invoiceModel->updateInvoiceFinal($invId); + + return $this->response->setJSON([ + 'update' => 1, + 'showcontent' => 'Cash Return', + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + return $this->response->setJSON([ 'update' => 0, 'error_text' => 'Invalid payment request', diff --git a/app/Database/Migrations/2026-04-01-000030_CreateInvoiceItemUpdateTable.php b/app/Database/Migrations/2026-04-01-000030_CreateInvoiceItemUpdateTable.php new file mode 100644 index 0000000..9702258 --- /dev/null +++ b/app/Database/Migrations/2026-04-01-000030_CreateInvoiceItemUpdateTable.php @@ -0,0 +1,113 @@ +forge->addField([ + 'audit_id' => [ + 'type' => 'INT', + 'constraint' => 11, + 'unsigned' => true, + 'auto_increment' => true, + ], + 'id' => [ + 'type' => 'INT', + 'constraint' => 11, + 'null' => true, + ], + 'inv_master_id' => [ + 'type' => 'INT', + 'constraint' => 11, + 'null' => true, + 'default' => 0, + ], + 'item_type' => [ + 'type' => 'INT', + 'constraint' => 11, + 'null' => true, + 'default' => 0, + ], + 'item_id' => [ + 'type' => 'INT', + 'constraint' => 11, + 'null' => true, + 'default' => 0, + ], + 'item_name' => [ + 'type' => 'VARCHAR', + 'constraint' => 50, + 'null' => true, + 'default' => '0', + ], + 'item_rate' => [ + 'type' => 'DECIMAL', + 'constraint' => '10,2', + 'null' => true, + 'default' => '0.00', + ], + 'ins_id' => [ + 'type' => 'INT', + 'constraint' => 11, + 'null' => true, + 'default' => 0, + ], + 'org_code' => [ + 'type' => 'VARCHAR', + 'constraint' => 50, + 'null' => true, + ], + 'item_added_date' => [ + 'type' => 'DATETIME', + 'null' => true, + ], + 'item_qty' => [ + 'type' => 'DECIMAL', + 'constraint' => '10,2', + 'null' => true, + 'default' => '0.00', + ], + 'item_amount' => [ + 'type' => 'DECIMAL', + 'constraint' => '10,2', + 'null' => true, + 'default' => '0.00', + ], + 'item_amount1' => [ + 'type' => 'DECIMAL', + 'constraint' => '10,2', + 'null' => true, + 'default' => '0.00', + ], + 'update_by_id' => [ + 'type' => 'INT', + 'constraint' => 11, + 'null' => true, + ], + 'update_by' => [ + 'type' => 'VARCHAR', + 'constraint' => 100, + 'null' => true, + ], + 'update_action' => [ + 'type' => 'VARCHAR', + 'constraint' => 20, + 'null' => true, + ], + ]); + + $this->forge->addKey('audit_id', true); + $this->forge->addKey('id'); + $this->forge->addKey('inv_master_id'); + $this->forge->createTable('invoice_item_update', true); + } + + public function down(): void + { + $this->forge->dropTable('invoice_item_update', true); + } +} \ No newline at end of file diff --git a/app/Models/InvoiceModel.php b/app/Models/InvoiceModel.php index 93941c2..835d5ed 100644 --- a/app/Models/InvoiceModel.php +++ b/app/Models/InvoiceModel.php @@ -60,11 +60,22 @@ class InvoiceModel $row['update_by'] = $userName; $row['update_action'] = '1'; - if (! $this->db->table('invoice_item_update')->insert($row)) { - return false; + if ($this->db->tableExists('invoice_item_update')) { + $auditTableFields = $this->db->getFieldNames('invoice_item_update'); + if (! empty($auditTableFields)) { + $row = array_intersect_key($row, array_flip($auditTableFields)); + } + + if (! $this->db->table('invoice_item_update')->insert($row)) { + return false; + } } $this->db->table('invoice_item')->where('id', $id)->delete(); + if ($this->db->affectedRows() < 1) { + return false; + } + $this->recalculateInvoice((int) $row['inv_master_id']); return true; @@ -135,10 +146,19 @@ class InvoiceModel ->getRowArray(); $paidAmount = (float) ($paidRow['paid_amount'] ?? 0); + $correctionCrdr = (int) ($invoice['correction_crdr'] ?? 1); $netAmount = $grossTotal - $discountAmount; - $netAmount += $correctionAmount; + if ($correctionAmount > 0) { + if ($correctionCrdr === 0) { + $netAmount += $correctionAmount; // Add-type correction increases net + } else { + $netAmount -= $correctionAmount; // Return-type correction reduces net + } + } $balanceAmount = $netAmount - $paidAmount; $paymentPart = ($balanceAmount > 0 && $paidAmount > 0) ? 1 : 0; + $paymentMode = (int) ($invoice['payment_mode'] ?? 0); + $paymentStatus = ($paymentMode === 3 || $paymentMode === 4) ? 1 : ($balanceAmount <= 0 ? 1 : 0); $this->db->table('invoice_master') ->where('id', $invoiceId) @@ -148,6 +168,7 @@ class InvoiceModel 'payment_part_received' => $paidAmount, 'payment_part_balance' => $balanceAmount, 'payment_part' => $paymentPart, + 'payment_status' => $paymentStatus, ]); if ($insuranceCaseId > 0) { diff --git a/app/Views/billing/charges/charges_invoice_show.php b/app/Views/billing/charges/charges_invoice_show.php index 5ab2b04..f1672ab 100644 --- a/app/Views/billing/charges/charges_invoice_show.php +++ b/app/Views/billing/charges/charges_invoice_show.php @@ -32,92 +32,92 @@
-
-
- To -
- p_fname ?? '') ?>
- p_relative ?? '') ?> : p_rname ?? '') ?>
- Gender : xgender ?? '') ?>
- Age : age ?? '') ?>
- Phone No : mphone1 ?? '') ?> -
-
-
- Invoice #invoice_code ?? '') ?>
- insurance_id ?? 0) > 1) : ?> - Ins. Comp. :ins_company_name ?? '') ?>
- insurance_case_id ?? 0) > 0 && count($case_master) > 0) : ?> - Org.Case No. :case_id_code ?? '') ?>
- - - Date : inv_date ?? '') ?>
- Patient ID : p_code ?? '') ?>
- Refer By : refer_by_other ?? '') ?>
- -
-
+
+
+ To +
+ p_fname ?? '') ?>
+ p_relative ?? '') ?> : p_rname ?? '') ?>
+ Gender : xgender ?? '') ?>
+ Age : age ?? '') ?>
+ Phone No : mphone1 ?? '') ?> +
+
+
+ Invoice #invoice_code ?? '') ?>
+ insurance_id ?? 0) > 1) : ?> + Ins. Comp. :ins_company_name ?? '') ?>
+ insurance_case_id ?? 0) > 0 && count($case_master) > 0) : ?> + Org.Case No. :case_id_code ?? '') ?>
+ + + Date : inv_date ?? '') ?>
+ Patient ID : p_code ?? '') ?>
+ Refer By : refer_by_other ?? '') ?>
+ +
+
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - payment_status ?? 0) == 0) : ?> - - - - - - - - - - - - - - - - - - - - - - - - -
#Charges GroupCharge NameRateQtyAmount
group_desc ?? '') ?>item_name ?? '') ?>item_rate ?? '') ?>item_qty ?? '') ?>item_amount ?? '') ?>
#Gross Totaltotal_amount ?? '') ?>
#Deduction
#Deductiondiscount_desc ?? '') ?>discount_amount ?? '') ?>
#Net Amountnet_amount ?? '') ?>
-
-
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + payment_status ?? 0) == 0) : ?> + + + + + + + + + + + + + + + + + + + + + + + + +
#Charges GroupCharge NameRateQtyAmount
group_desc ?? '') ?>item_name ?? '') ?>item_rate ?? '') ?>item_qty ?? '') ?>item_amount ?? '') ?>
#Gross Totaltotal_amount ?? '') ?>
#Deduction
#Deductiondiscount_desc ?? '') ?>discount_amount ?? '') ?>
#Net Amountnet_amount ?? '') ?>
+
+
@@ -139,7 +139,7 @@ -payment_status ?? 0) == 0 && $canChargePay) : ?> + 0 || (($pending_amount ?? 0) == 0 && ((int) ($invoice_master[0]->payment_status ?? 0) === 0))) ) : ?>
@@ -316,26 +316,34 @@ # Deduction - + - + - -
- - -
-
- - -
- - +
+ + payment_mode ?? 0), [1, 2], true)) : ?> +
+
+ Overpayment: + Amount Received: Rs.  |  + Net Amount: Rs. net_amount ?? 0), 2) ?>  |  + Return Amount Due: Rs. +
+
+
+ +
+
+
+
@@ -532,5 +540,26 @@ $(document).ready(function(){ } }, 'json'); }); + $('#btn_return_cash').click(function() { + var csrf = getCsrfPair(); + var spid = $('#spid').val(); + if (confirm('Are you sure you want to process a cash return to the patient?')) { + $('#btn_return_cash').prop('disabled', true); + $.post('', { + "mode": "5", + "inv_id": $('#invoice_id').val(), + "spid": spid, + [csrf.name]: csrf.value + }, function(data) { + updateCsrf(data); + if (data.update == 0) { + $('div.jsReturnError').html(data.error_text); + $('#btn_return_cash').prop('disabled', false); + } else { + load_form('/' + $('#invoice_id').val()); + } + }, 'json'); + } + }); })(); diff --git a/app/Views/billing/charges/invoice_pdf_template.php b/app/Views/billing/charges/invoice_pdf_template.php index 7da7c6b..06ecb4d 100644 --- a/app/Views/billing/charges/invoice_pdf_template.php +++ b/app/Views/billing/charges/invoice_pdf_template.php @@ -24,7 +24,7 @@ if ($hospitalNameLen > 42) { $invoiceCode = (string) ($invoice->invoice_code ?? ''); $invoiceDate = (string) ($invoice->inv_date ?? ''); $printedOn = date('d-m-Y h:i:s A'); -$invoiceStatus = ((int) ($invoice->invoice_status ?? 0) === 1) ? 'Paid' : (((int) ($invoice->invoice_status ?? 0) === 2) ? 'Cancelled' : 'Pending'); +$invoiceStatus = ((int) ($invoice->invoice_status ?? 0) === 2) ? 'Cancelled' : (((int) ($invoice->payment_status ?? 0) === 1) ? 'Paid' : 'Pending'); $currencyPrefix = 'Rs.'; $grossAmount = (float) ($invoice->total_amount ?? 0); $discount = (float) ($invoice->discount_amount ?? 0); @@ -58,6 +58,9 @@ $qrParts = [ $qrContent = implode(' | ', array_filter($qrParts, static fn ($v) => trim((string) $v) !== '')); $qrPackageAvailable = class_exists('\\Mpdf\\QrCode\\QrCode'); +$paidAmountPrint = (float) ($invoice->payment_part_received ?? 0); +$balanceAmountPrint = (float) ($invoice->payment_part_balance ?? ($netAmount - $paidAmountPrint)); + /** * Convert numbers to words (basic implementation for Indian system) * Wrapped with function_exists to prevent redeclaration when rendering multiple times @@ -250,7 +253,11 @@ if (!function_exists('convertNumberToWords')) {
-
Amount received: | Balance Amount: | Net Amount:
+ +
Amount received: | Return Amount: | Net Amount:
+ +
Amount received: | Balance Amount: | Net Amount:
+
Amount in Words: