finance/storestock: implement cash submission audits, bank settlements, and opd payout tracking
This commit is contained in:
parent
7e4903bd68
commit
d02244444e
|
|
@ -2,6 +2,38 @@
|
|||
|
||||
Date: 2026-02-08
|
||||
|
||||
---
|
||||
|
||||
## Storestock Module — 2026-04-19
|
||||
|
||||
Ported Hospital Stock Management (Storestock) module from CI3 to CI4.
|
||||
|
||||
### Files created
|
||||
- `app/Controllers/Storestock.php` — full port: index, indent_list, getIndentTable, new_indent, indent_create, indent_show, indent_items_list, add_item (type 1/2/3), get_drug, final_invoice, print_single_indent, store_stock, store_stock_result, get_product_stock, report_2, main_store
|
||||
- `app/Views/storestock/dashboard.php`
|
||||
- `app/Views/storestock/indent_list.php` — jQuery DataTable server-side, CI4 CSRF
|
||||
- `app/Views/storestock/new_indent.php` — location + employee indent creation
|
||||
- `app/Views/storestock/indent_edit.php` — add/update/delete items via AJAX, jQuery autocomplete for drug search
|
||||
- `app/Views/storestock/indent_item_list.php` — AJAX partial; qty edit, remove buttons
|
||||
- `app/Views/storestock/final_invoice.php` — read-only summary + print link
|
||||
- `app/Views/storestock/store_stock_search.php` — search form with schedule filter
|
||||
- `app/Views/storestock/stock_search_result.php` — result table + modal for batch detail
|
||||
- `app/Views/storestock/stock_item_history.php` — batch-wise purchase detail table
|
||||
- `app/Views/storestock/day_report.php` — date range stub
|
||||
- `app/Views/storestock/main_store_dashboard.php` — main store nav panel
|
||||
- `app/Views/storestock/store_print.php` — mPDF print template
|
||||
|
||||
### Routes modified
|
||||
- `app/Config/Routes.php` — 17 Storestock routes added after `service('auth')->routes()`
|
||||
|
||||
### Key patterns
|
||||
- Auth via `ensureStoreAccess()` — checks `hospital_stock.access` permission + group membership
|
||||
- `getIndentTable()` uses parameterised queries (no raw user-input interpolation)
|
||||
- `add_item()` calls stored procs `p_stock_update_purchase_id_store` + `p_update_med_GST_store`
|
||||
- indent_code generated as `S{ym}{padded_id}` after insert
|
||||
- Delete archives row to `inv_stock_item_delete` before deleting from `inv_stock_item`
|
||||
- `print_single_indent` uses mPDF (Mpdf\Mpdf), no debug toolbar
|
||||
|
||||
## Summary
|
||||
Ported OPD Charges, IPD Charges, and Package modules from CI3 to CI4 with consistent NiceAdmin UI, AJAX modal flows, Simple-DataTables, insurance rate handling, and print/Excel exports.
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,14 @@ class AuthGroups extends ShieldAuthGroups
|
|||
'title' => 'Storekeeper',
|
||||
'description' => 'Handles item issuance and stock receiving operations.',
|
||||
],
|
||||
'billing_cashier' => [
|
||||
'title' => 'Billing Cashier',
|
||||
'description' => 'Submits billing cash statements to accounts.',
|
||||
],
|
||||
'accounts_officer' => [
|
||||
'title' => 'Accounts Officer',
|
||||
'description' => 'Accepts/verifies billing statements and audits bank records.',
|
||||
],
|
||||
'user' => [
|
||||
'title' => 'User',
|
||||
'description' => 'General users of the site. Often customers.',
|
||||
|
|
@ -124,15 +132,13 @@ class AuthGroups extends ShieldAuthGroups
|
|||
'reports.collection.view' => 'Can view collection reports',
|
||||
'reports.insurance_credit.view' => 'Can view insurance credit reports',
|
||||
'reports.nabh_audit.view' => 'Can view NABH audit report',
|
||||
'finance.access' => 'Can access finance module',
|
||||
'finance.vendor.manage' => 'Can manage finance vendors',
|
||||
'finance.po.manage' => 'Can manage purchase orders',
|
||||
'finance.grn.manage' => 'Can manage GRN entries',
|
||||
'finance.invoice.manage' => 'Can manage vendor invoices',
|
||||
'finance.cash.manage' => 'Can manage cash collection and disbursement',
|
||||
'finance.doctor_payout.manage' => 'Can manage doctor payout workflow',
|
||||
'finance.bank_deposit.manage' => 'Can manage bank deposit register',
|
||||
'finance.compliance.view' => 'Can view finance compliance report',
|
||||
'finance.workflow.view' => 'Can access Accounts and Finance workflow dashboard',
|
||||
'finance.cash.billing.submit' => 'Can create billing cash entries and submit cash statements',
|
||||
'finance.cash.accounts.accept' => 'Can accept submitted billing cash statements',
|
||||
'finance.cash.accounts.verify' => 'Can verify accepted billing cash statements',
|
||||
'finance.bank.deposit.create' => 'Can create bank deposit register entries',
|
||||
'finance.bank.audit' => 'Can mark bank transaction audit status',
|
||||
'finance.bank.statement.update' => 'Can mark updated in bank statement status',
|
||||
'diagnosis.access' => 'Can access diagnosis module',
|
||||
'diagnosis.report.view' => 'Can view diagnosis reports',
|
||||
'doctor_work.access' => 'Can access doctor work module',
|
||||
|
|
@ -269,6 +275,18 @@ class AuthGroups extends ShieldAuthGroups
|
|||
'hospital_stock.purchase.manage',
|
||||
'hospital_stock.report.view',
|
||||
],
|
||||
'billing_cashier' => [
|
||||
'finance.workflow.view',
|
||||
'finance.cash.billing.submit',
|
||||
'finance.bank.deposit.create',
|
||||
],
|
||||
'accounts_officer' => [
|
||||
'finance.workflow.view',
|
||||
'finance.cash.accounts.accept',
|
||||
'finance.cash.accounts.verify',
|
||||
'finance.bank.audit',
|
||||
'finance.bank.statement.update',
|
||||
],
|
||||
'user' => [],
|
||||
'beta' => [
|
||||
'beta.access',
|
||||
|
|
|
|||
|
|
@ -262,6 +262,12 @@ $routes->get('Finance/scroll_items_table', 'Finance::scrollItemsTable');
|
|||
$routes->get('Finance/bank_deposits', 'Finance::bankDeposits');
|
||||
$routes->get('Finance/bank_deposits_table', 'Finance::bankDepositsTable');
|
||||
$routes->get('Finance/bank_audit', 'Finance::bankAudit');
|
||||
$routes->get('Finance/payout/opd-consult', 'Finance::payoutOpdConsult');
|
||||
$routes->get('Finance/payout/opd-consult-summary', 'Finance::payoutOpdConsultSummary');
|
||||
$routes->get('Finance/payout/opd-consult-drafts-table', 'Finance::payoutOpdConsultDraftsTable');
|
||||
$routes->post('Finance/payout/opd-consult-draft-create', 'Finance::payoutOpdConsultDraftCreate');
|
||||
$routes->post('Finance/payout/opd-consult-draft-update', 'Finance::payoutOpdConsultDraftUpdate');
|
||||
$routes->post('Finance/payout/opd-consult-draft-delete', 'Finance::payoutOpdConsultDraftDelete');
|
||||
$routes->get('Finance/bank_audit_direct_payments_table', 'Finance::bankAuditDirectPaymentsTable');
|
||||
$routes->get('Finance/bank_statement_entries_table', 'Finance::bankStatementEntriesTable');
|
||||
$routes->get('Finance/bank_settlement_entries_table', 'Finance::bankSettlementEntriesTable');
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\DoctorModel;
|
||||
use App\Models\FinanceGrnModel;
|
||||
use App\Models\FinanceCashTransactionModel;
|
||||
use App\Models\FinanceBankDepositModel;
|
||||
|
|
@ -1800,6 +1801,384 @@ class Finance extends BaseController
|
|||
return view('finance/index');
|
||||
}
|
||||
|
||||
public function payoutOpdConsult()
|
||||
{
|
||||
if (! $this->canFinanceAny([
|
||||
'finance.workflow.view',
|
||||
'finance.cash.billing.submit',
|
||||
'finance.cash.accounts.accept',
|
||||
'finance.cash.accounts.verify',
|
||||
'finance.bank.deposit.create',
|
||||
'finance.bank.audit',
|
||||
'finance.bank.statement.update',
|
||||
])) {
|
||||
return $this->response->setStatusCode(403)->setBody('Access denied');
|
||||
}
|
||||
|
||||
$doctorModel = new DoctorModel();
|
||||
$doctorRows = $doctorModel->getDoctors();
|
||||
usort($doctorRows, static function ($left, $right): int {
|
||||
$leftName = trim((string) (($left->p_title ?? '') . ' ' . ($left->p_fname ?? '')));
|
||||
$rightName = trim((string) (($right->p_title ?? '') . ' ' . ($right->p_fname ?? '')));
|
||||
|
||||
return strcasecmp($leftName, $rightName);
|
||||
});
|
||||
|
||||
$doctorOptions = [];
|
||||
foreach ($doctorRows as $row) {
|
||||
$isActive = property_exists($row, 'active') ? (int) ($row->active ?? 0) : 1;
|
||||
if ($isActive !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doctorOptions[] = [
|
||||
'id' => (int) ($row->id ?? 0),
|
||||
'name' => trim((string) (($row->p_title ?? '') . ' ' . ($row->p_fname ?? ''))),
|
||||
];
|
||||
}
|
||||
|
||||
return view('finance/payout_opd_consult', [
|
||||
'doctor_options' => $doctorOptions,
|
||||
'state_unit_options' => $this->fetchOpdStateUnitOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function payoutOpdConsultSummary()
|
||||
{
|
||||
if (! $this->canFinanceAny([
|
||||
'finance.workflow.view',
|
||||
'finance.cash.billing.submit',
|
||||
'finance.cash.accounts.accept',
|
||||
'finance.cash.accounts.verify',
|
||||
'finance.bank.deposit.create',
|
||||
'finance.bank.audit',
|
||||
'finance.bank.statement.update',
|
||||
])) {
|
||||
return $this->response->setStatusCode(403)->setBody('Access denied');
|
||||
}
|
||||
|
||||
$fromDate = $this->normalizeDateInput((string) ($this->request->getGet('from_date') ?? ''));
|
||||
$toDate = $this->normalizeDateInput((string) ($this->request->getGet('to_date') ?? ''));
|
||||
$doctorId = (int) ($this->request->getGet('doctor_id') ?? 0);
|
||||
$stateUnit = trim((string) ($this->request->getGet('state_unit') ?? ''));
|
||||
|
||||
if ($fromDate === '') {
|
||||
$fromDate = date('Y-m-01');
|
||||
}
|
||||
if ($toDate === '') {
|
||||
$toDate = date('Y-m-d');
|
||||
}
|
||||
if ($fromDate > $toDate) {
|
||||
[$fromDate, $toDate] = [$toDate, $fromDate];
|
||||
}
|
||||
|
||||
return view('finance/partials/payout_opd_consult_summary', $this->buildOpdConsultPayoutSummaryData($fromDate, $toDate, $doctorId, $stateUnit));
|
||||
}
|
||||
|
||||
public function payoutOpdConsultDraftsTable()
|
||||
{
|
||||
if (! $this->canFinanceAny([
|
||||
'finance.workflow.view',
|
||||
'finance.cash.billing.submit',
|
||||
'finance.cash.accounts.accept',
|
||||
'finance.cash.accounts.verify',
|
||||
'finance.bank.deposit.create',
|
||||
'finance.bank.audit',
|
||||
'finance.bank.statement.update',
|
||||
'finance.doctor_payout.manage',
|
||||
])) {
|
||||
return $this->response->setStatusCode(403)->setBody('Access denied');
|
||||
}
|
||||
|
||||
$fromDate = $this->normalizeDateInput((string) ($this->request->getGet('from_date') ?? ''));
|
||||
$toDate = $this->normalizeDateInput((string) ($this->request->getGet('to_date') ?? ''));
|
||||
$doctorId = (int) ($this->request->getGet('doctor_id') ?? 0);
|
||||
$stateUnit = trim((string) ($this->request->getGet('state_unit') ?? ''));
|
||||
|
||||
if ($fromDate === '') {
|
||||
$fromDate = date('Y-m-01');
|
||||
}
|
||||
if ($toDate === '') {
|
||||
$toDate = date('Y-m-d');
|
||||
}
|
||||
if ($fromDate > $toDate) {
|
||||
[$fromDate, $toDate] = [$toDate, $fromDate];
|
||||
}
|
||||
|
||||
$builder = $this->doctorPayoutModel
|
||||
->select('finance_doctor_payouts.*, finance_doctor_agreements.doctor_code, finance_doctor_agreements.doctor_name')
|
||||
->join('finance_doctor_agreements', 'finance_doctor_agreements.id = finance_doctor_payouts.doctor_id', 'left')
|
||||
->where('finance_doctor_payouts.payout_type', 'consultation')
|
||||
->where('finance_doctor_payouts.payout_date >=', $fromDate)
|
||||
->where('finance_doctor_payouts.payout_date <=', $toDate)
|
||||
->orderBy('finance_doctor_payouts.id', 'DESC');
|
||||
|
||||
if ($doctorId > 0) {
|
||||
$agreementIds = $this->findDoctorAgreementIdsFromMaster($doctorId);
|
||||
if (! empty($agreementIds)) {
|
||||
$builder->whereIn('finance_doctor_payouts.doctor_id', $agreementIds);
|
||||
} else {
|
||||
$builder->where('1 = 0', null, false);
|
||||
}
|
||||
}
|
||||
|
||||
if ($stateUnit !== '') {
|
||||
$builder->like('finance_doctor_payouts.case_reference', '-U' . $this->normalizeStateUnitToken($stateUnit), 'both');
|
||||
}
|
||||
|
||||
return view('finance/partials/payout_opd_consult_drafts_table', [
|
||||
'rows' => $builder->findAll(30),
|
||||
]);
|
||||
}
|
||||
|
||||
public function payoutOpdConsultDraftCreate()
|
||||
{
|
||||
if (! $this->canFinanceAny([
|
||||
'finance.workflow.view',
|
||||
'finance.cash.billing.submit',
|
||||
'finance.cash.accounts.accept',
|
||||
'finance.cash.accounts.verify',
|
||||
'finance.bank.deposit.create',
|
||||
'finance.bank.audit',
|
||||
'finance.bank.statement.update',
|
||||
'finance.doctor_payout.manage',
|
||||
])) {
|
||||
return $this->response->setStatusCode(403)->setJSON(['status' => 0, 'message' => 'Access denied']);
|
||||
}
|
||||
|
||||
if (! method_exists($this->db, 'tableExists') || ! $this->db->tableExists('finance_doctor_payouts')) {
|
||||
return $this->response->setStatusCode(500)->setJSON(['status' => 0, 'message' => 'Payout table not available.']);
|
||||
}
|
||||
|
||||
$doctorId = (int) ($this->request->getPost('doctor_id') ?? 0);
|
||||
$fromDate = $this->normalizeDateInput((string) ($this->request->getPost('from_date') ?? ''));
|
||||
$toDate = $this->normalizeDateInput((string) ($this->request->getPost('to_date') ?? ''));
|
||||
$stateUnit = trim((string) ($this->request->getPost('state_unit') ?? ''));
|
||||
$baseAmount = (float) ($this->request->getPost('base_amount') ?? 0);
|
||||
$doctorShare = (float) ($this->request->getPost('doctor_share') ?? 75);
|
||||
$hospitalShare = (float) ($this->request->getPost('hospital_share') ?? 25);
|
||||
$deductions = (float) ($this->request->getPost('deductions') ?? 0);
|
||||
$adjustments = (float) ($this->request->getPost('adjustments') ?? 0);
|
||||
|
||||
if ($doctorId <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Please select a doctor before creating payout draft.']);
|
||||
}
|
||||
|
||||
if ($fromDate === '') {
|
||||
$fromDate = date('Y-m-01');
|
||||
}
|
||||
if ($toDate === '') {
|
||||
$toDate = date('Y-m-d');
|
||||
}
|
||||
if ($fromDate > $toDate) {
|
||||
[$fromDate, $toDate] = [$toDate, $fromDate];
|
||||
}
|
||||
|
||||
$summaryData = $this->buildOpdConsultPayoutSummaryData($fromDate, $toDate, $doctorId, $stateUnit);
|
||||
$summary = (array) ($summaryData['summary'] ?? []);
|
||||
$eligibleOpdIds = $this->fetchEligibleOpdIdsForPayout($fromDate, $toDate, $doctorId, $stateUnit);
|
||||
$completedOpd = count($eligibleOpdIds);
|
||||
if ($completedOpd <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'No completed OPD found for selected filters.']);
|
||||
}
|
||||
|
||||
if ($baseAmount <= 0) {
|
||||
$baseAmount = (float) ($summary['total_received'] ?? 0);
|
||||
}
|
||||
|
||||
$doctorGross = round(($baseAmount * $doctorShare) / 100, 2);
|
||||
$hospitalGross = round(($baseAmount * $hospitalShare) / 100, 2);
|
||||
$netPayable = round($doctorGross - $deductions + $adjustments, 2);
|
||||
if ($netPayable < 0) {
|
||||
$netPayable = 0.0;
|
||||
}
|
||||
|
||||
$agreementId = $this->resolveOrCreateDoctorAgreementIdFromMaster($doctorId);
|
||||
if ($agreementId <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Unable to map selected doctor to payout agreement.']);
|
||||
}
|
||||
|
||||
$rate = round($netPayable / max(1, $completedOpd), 2);
|
||||
$caseReference = 'OPD-' . str_replace('-', '', $fromDate) . '-' . str_replace('-', '', $toDate) . '-D' . $doctorId;
|
||||
if ($stateUnit !== '') {
|
||||
$caseReference .= '-U' . $this->normalizeStateUnitToken($stateUnit);
|
||||
}
|
||||
|
||||
$remarks = 'OPD payout draft from OPD Consult Payout | Completed=' . $completedOpd
|
||||
. ', Base=' . number_format($baseAmount, 2, '.', '')
|
||||
. ', Doc%=' . number_format($doctorShare, 2, '.', '')
|
||||
. ', Hosp%=' . number_format($hospitalShare, 2, '.', '')
|
||||
. ', Deductions=' . number_format($deductions, 2, '.', '')
|
||||
. ', Adjustments=' . number_format($adjustments, 2, '.', '')
|
||||
. ', DoctorGross=' . number_format($doctorGross, 2, '.', '')
|
||||
. ', HospitalShare=' . number_format($hospitalGross, 2, '.', '')
|
||||
. ', Net=' . number_format($netPayable, 2, '.', '');
|
||||
|
||||
$this->db->transStart();
|
||||
|
||||
$this->doctorPayoutModel->insert([
|
||||
'payout_date' => $toDate,
|
||||
'doctor_id' => $agreementId,
|
||||
'case_reference' => $caseReference,
|
||||
'payout_type' => 'consultation',
|
||||
'units' => $completedOpd,
|
||||
'rate' => $rate,
|
||||
'calculated_amount' => $netPayable,
|
||||
'approved_amount' => $netPayable,
|
||||
'status' => 'draft',
|
||||
'remarks' => $remarks,
|
||||
'hr_submitted_by' => $this->currentUserName(),
|
||||
]);
|
||||
|
||||
$insertId = (int) ($this->doctorPayoutModel->getInsertID() ?? 0);
|
||||
|
||||
if ($insertId > 0 && ! empty($eligibleOpdIds) && method_exists($this->db, 'tableExists') && $this->db->tableExists('opd_master')) {
|
||||
$opdUpdate = [
|
||||
'payout_draft_id' => $insertId,
|
||||
'payout_calculated_at' => date('Y-m-d H:i:s'),
|
||||
'payout_calculated_by' => $this->currentUserName(),
|
||||
];
|
||||
if ($this->tableHasField('opd_master', 'updated_at')) {
|
||||
$opdUpdate['updated_at'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$this->db->table('opd_master')
|
||||
->whereIn('opd_id', $eligibleOpdIds)
|
||||
->update($opdUpdate);
|
||||
}
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
if (! $this->db->transStatus() || $insertId <= 0) {
|
||||
return $this->response->setStatusCode(500)->setJSON(['status' => 0, 'message' => 'Failed to create payout draft.']);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'message' => 'Payout draft created successfully.',
|
||||
'payout_id' => $insertId,
|
||||
'case_reference' => $caseReference,
|
||||
'net_payable' => $netPayable,
|
||||
]);
|
||||
}
|
||||
|
||||
public function payoutOpdConsultDraftUpdate()
|
||||
{
|
||||
if (! $this->canFinanceAny([
|
||||
'finance.workflow.view',
|
||||
'finance.cash.billing.submit',
|
||||
'finance.cash.accounts.accept',
|
||||
'finance.cash.accounts.verify',
|
||||
'finance.bank.deposit.create',
|
||||
'finance.bank.audit',
|
||||
'finance.bank.statement.update',
|
||||
'finance.doctor_payout.manage',
|
||||
])) {
|
||||
return $this->response->setStatusCode(403)->setJSON(['status' => 0, 'message' => 'Access denied']);
|
||||
}
|
||||
|
||||
$payoutId = (int) ($this->request->getPost('payout_id') ?? 0);
|
||||
$payoutDate = $this->normalizeDateInput((string) ($this->request->getPost('payout_date') ?? ''));
|
||||
$approvedAmount = (float) ($this->request->getPost('approved_amount') ?? 0);
|
||||
$remarks = trim((string) ($this->request->getPost('remarks') ?? ''));
|
||||
|
||||
if ($payoutId <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Invalid payout id.']);
|
||||
}
|
||||
|
||||
$row = $this->doctorPayoutModel->find($payoutId);
|
||||
if (! is_array($row)) {
|
||||
return $this->response->setStatusCode(404)->setJSON(['status' => 0, 'message' => 'Payout draft not found.']);
|
||||
}
|
||||
|
||||
if ((string) ($row['status'] ?? 'draft') !== 'draft') {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Only draft entries can be edited.']);
|
||||
}
|
||||
|
||||
if ($payoutDate === '') {
|
||||
$payoutDate = (string) ($row['payout_date'] ?? date('Y-m-d'));
|
||||
}
|
||||
if ($approvedAmount < 0) {
|
||||
$approvedAmount = 0;
|
||||
}
|
||||
|
||||
$units = max(1, (int) ($row['units'] ?? 1));
|
||||
$rate = round($approvedAmount / $units, 2);
|
||||
|
||||
$this->doctorPayoutModel->update($payoutId, [
|
||||
'payout_date' => $payoutDate,
|
||||
'approved_amount' => round($approvedAmount, 2),
|
||||
'rate' => $rate,
|
||||
'remarks' => $remarks !== '' ? $remarks : null,
|
||||
]);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'message' => 'Payout draft updated successfully.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function payoutOpdConsultDraftDelete()
|
||||
{
|
||||
if (! $this->canFinanceAny([
|
||||
'finance.workflow.view',
|
||||
'finance.cash.billing.submit',
|
||||
'finance.cash.accounts.accept',
|
||||
'finance.cash.accounts.verify',
|
||||
'finance.bank.deposit.create',
|
||||
'finance.bank.audit',
|
||||
'finance.bank.statement.update',
|
||||
'finance.doctor_payout.manage',
|
||||
])) {
|
||||
return $this->response->setStatusCode(403)->setJSON(['status' => 0, 'message' => 'Access denied']);
|
||||
}
|
||||
|
||||
$payoutId = (int) ($this->request->getPost('payout_id') ?? 0);
|
||||
if ($payoutId <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Invalid payout id.']);
|
||||
}
|
||||
|
||||
$row = $this->doctorPayoutModel->find($payoutId);
|
||||
if (! is_array($row)) {
|
||||
return $this->response->setStatusCode(404)->setJSON(['status' => 0, 'message' => 'Payout draft not found.']);
|
||||
}
|
||||
|
||||
if ((string) ($row['status'] ?? 'draft') !== 'draft') {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Only draft entries can be deleted.']);
|
||||
}
|
||||
|
||||
$this->db->transStart();
|
||||
|
||||
if (method_exists($this->db, 'tableExists') && $this->db->tableExists('opd_master') && $this->tableHasField('opd_master', 'payout_draft_id')) {
|
||||
$clearData = [
|
||||
'payout_draft_id' => null,
|
||||
];
|
||||
if ($this->tableHasField('opd_master', 'payout_calculated_at')) {
|
||||
$clearData['payout_calculated_at'] = null;
|
||||
}
|
||||
if ($this->tableHasField('opd_master', 'payout_calculated_by')) {
|
||||
$clearData['payout_calculated_by'] = null;
|
||||
}
|
||||
|
||||
$this->db->table('opd_master')
|
||||
->where('payout_draft_id', $payoutId)
|
||||
->update($clearData);
|
||||
}
|
||||
|
||||
$this->doctorPayoutModel->delete($payoutId);
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
if (! $this->db->transStatus()) {
|
||||
return $this->response->setStatusCode(500)->setJSON(['status' => 0, 'message' => 'Failed to delete payout draft.']);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'message' => 'Payout draft deleted and linked OPD records unlocked for recalculation.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function phase2()
|
||||
{
|
||||
if (! $this->canFinance('finance.access')) {
|
||||
|
|
@ -3307,6 +3686,369 @@ class Finance extends BaseController
|
|||
return $value > 0 ? $value : $default;
|
||||
}
|
||||
|
||||
private function buildOpdConsultPayoutSummaryData(string $fromDate, string $toDate, int $doctorId = 0, string $stateUnit = ''): array
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
$stateUnitColumn = $this->resolveOpdStateUnitColumn();
|
||||
|
||||
$runningExpr = "(COALESCE(o.running_opd,0)=1 OR o.opd_fee_type=3 OR UPPER(COALESCE(o.opd_fee_desc,'')) LIKE '%RUNNING%')";
|
||||
$newExpr = "(o.opd_fee_type=1 OR UPPER(COALESCE(o.opd_fee_desc,'')) LIKE '%NEW%')";
|
||||
$emergencyExpr = "(UPPER(COALESCE(o.opd_fee_desc,'')) LIKE '%EMERG%')";
|
||||
$routineExpr = "(NOT {$emergencyExpr})";
|
||||
$orgCreditExpr = "(COALESCE(o.insurance_case_id,0) > 0 OR COALESCE(o.insurance_id,0) > 0 OR COALESCE(o.payment_status,0) = 3)";
|
||||
|
||||
$doctorName = 'All Doctors';
|
||||
if ($doctorId > 0) {
|
||||
$doctorRow = $db->table('doctor_master')->select('p_title, p_fname')->where('id', $doctorId)->get()->getRow();
|
||||
if ($doctorRow) {
|
||||
$doctorName = trim((string) (($doctorRow->p_title ?? '') . ' ' . ($doctorRow->p_fname ?? '')));
|
||||
}
|
||||
}
|
||||
|
||||
$opdBuilder = $db->table('opd_master o')
|
||||
->select('o.doc_id')
|
||||
->select("COALESCE(NULLIF(TRIM(o.doc_name), ''), CONCAT('Doctor #', o.doc_id)) AS doctor_name", false)
|
||||
->select('SUM(CASE WHEN o.opd_status IN (1,2) THEN 1 ELSE 0 END) AS completed_opd', false)
|
||||
->select("SUM(CASE WHEN o.opd_status IN (1,2) AND {$routineExpr} THEN 1 ELSE 0 END) AS routine_opd", false)
|
||||
->select("SUM(CASE WHEN o.opd_status IN (1,2) AND {$emergencyExpr} THEN 1 ELSE 0 END) AS emergency_opd", false)
|
||||
->select("SUM(CASE WHEN o.opd_status IN (1,2) AND {$runningExpr} THEN 1 ELSE 0 END) AS running_opd", false)
|
||||
->select("SUM(CASE WHEN o.opd_status IN (1,2) AND {$newExpr} THEN 1 ELSE 0 END) AS new_opd", false)
|
||||
->select('SUM(CASE WHEN o.opd_status IN (1,2) THEN COALESCE(o.opd_fee_amount,0) ELSE 0 END) AS gross_amount', false)
|
||||
->select("SUM(CASE WHEN o.opd_status IN (1,2) AND {$orgCreditExpr} THEN COALESCE(o.opd_fee_amount,0) ELSE 0 END) AS org_credit_amount", false)
|
||||
->where('o.apointment_date >=', $fromDate)
|
||||
->where('o.apointment_date <=', $toDate)
|
||||
->whereIn('o.opd_status', [1, 2]);
|
||||
|
||||
if ($this->tableHasField('opd_master', 'payout_draft_id')) {
|
||||
$opdBuilder->groupStart()
|
||||
->where('o.payout_draft_id IS NULL', null, false)
|
||||
->orWhere('o.payout_draft_id', 0)
|
||||
->groupEnd();
|
||||
}
|
||||
|
||||
if ($doctorId > 0) {
|
||||
$opdBuilder->where('o.doc_id', $doctorId);
|
||||
}
|
||||
if ($stateUnit !== '' && $stateUnitColumn !== null) {
|
||||
$opdBuilder->where('o.' . $stateUnitColumn, $stateUnit);
|
||||
}
|
||||
|
||||
$doctorBreakdown = $opdBuilder
|
||||
->groupBy('o.doc_id, o.doc_name')
|
||||
->orderBy('doctor_name', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$summary = [
|
||||
'completed_opd' => 0,
|
||||
'routine_opd' => 0,
|
||||
'emergency_opd' => 0,
|
||||
'running_opd' => 0,
|
||||
'new_opd' => 0,
|
||||
'calculated_opd' => 0,
|
||||
'gross_amount' => 0.0,
|
||||
'org_credit_amount' => 0.0,
|
||||
'doctor_count' => 0,
|
||||
'approved_consents' => 0,
|
||||
'payout_count' => 0,
|
||||
'payout_amount' => 0.0,
|
||||
'cash_received' => 0.0,
|
||||
'bank_received' => 0.0,
|
||||
'total_received' => 0.0,
|
||||
'credit_amount' => 0.0,
|
||||
];
|
||||
|
||||
foreach ($doctorBreakdown as &$row) {
|
||||
$row['completed_opd'] = (int) ($row['completed_opd'] ?? 0);
|
||||
$row['routine_opd'] = (int) ($row['routine_opd'] ?? 0);
|
||||
$row['emergency_opd'] = (int) ($row['emergency_opd'] ?? 0);
|
||||
$row['running_opd'] = (int) ($row['running_opd'] ?? 0);
|
||||
$row['new_opd'] = (int) ($row['new_opd'] ?? 0);
|
||||
$row['gross_amount'] = (float) ($row['gross_amount'] ?? 0);
|
||||
$row['org_credit_amount'] = (float) ($row['org_credit_amount'] ?? 0);
|
||||
$row['cash_received'] = 0.0;
|
||||
$row['bank_received'] = 0.0;
|
||||
$row['credit_amount'] = 0.0;
|
||||
$row['payout_amount'] = 0.0;
|
||||
|
||||
$summary['completed_opd'] += $row['completed_opd'];
|
||||
$summary['routine_opd'] += $row['routine_opd'];
|
||||
$summary['emergency_opd'] += $row['emergency_opd'];
|
||||
$summary['running_opd'] += $row['running_opd'];
|
||||
$summary['new_opd'] += $row['new_opd'];
|
||||
$summary['gross_amount'] += $row['gross_amount'];
|
||||
$summary['org_credit_amount'] += $row['org_credit_amount'];
|
||||
}
|
||||
unset($row);
|
||||
|
||||
$summary['doctor_count'] = count($doctorBreakdown);
|
||||
|
||||
if ($this->tableHasField('opd_master', 'payout_draft_id')) {
|
||||
$calcBuilder = $db->table('opd_master o')
|
||||
->select('COUNT(*) AS calculated_opd', false)
|
||||
->where('o.apointment_date >=', $fromDate)
|
||||
->where('o.apointment_date <=', $toDate)
|
||||
->whereIn('o.opd_status', [1, 2])
|
||||
->where('o.payout_draft_id >', 0);
|
||||
|
||||
if ($doctorId > 0) {
|
||||
$calcBuilder->where('o.doc_id', $doctorId);
|
||||
}
|
||||
if ($stateUnit !== '' && $stateUnitColumn !== null) {
|
||||
$calcBuilder->where('o.' . $stateUnitColumn, $stateUnit);
|
||||
}
|
||||
|
||||
$calcRow = $calcBuilder->get()->getRowArray();
|
||||
$summary['calculated_opd'] = (int) ($calcRow['calculated_opd'] ?? 0);
|
||||
}
|
||||
|
||||
$collectionBuilder = $db->table('payment_history p')
|
||||
->select('o.doc_id')
|
||||
->select('SUM(CASE WHEN p.credit_debit = 0 THEN COALESCE(p.amount,0) ELSE 0 END) AS total_received', false)
|
||||
->select('SUM(CASE WHEN p.credit_debit = 0 AND p.payment_mode = 1 THEN COALESCE(p.amount,0) ELSE 0 END) AS cash_received', false)
|
||||
->select('SUM(CASE WHEN p.credit_debit = 0 AND p.payment_mode = 2 THEN COALESCE(p.amount,0) ELSE 0 END) AS bank_received', false)
|
||||
->join('opd_master o', 'p.payof_type = 1 AND o.opd_id = p.payof_id', 'inner')
|
||||
->where('DATE(p.payment_date) >=', $fromDate)
|
||||
->where('DATE(p.payment_date) <=', $toDate);
|
||||
|
||||
if ($doctorId > 0) {
|
||||
$collectionBuilder->where('o.doc_id', $doctorId);
|
||||
}
|
||||
if ($stateUnit !== '' && $stateUnitColumn !== null) {
|
||||
$collectionBuilder->where('o.' . $stateUnitColumn, $stateUnit);
|
||||
}
|
||||
|
||||
$collectionRows = $collectionBuilder->groupBy('o.doc_id')->get()->getResultArray();
|
||||
$collectionMap = [];
|
||||
foreach ($collectionRows as $row) {
|
||||
$docKey = (int) ($row['doc_id'] ?? 0);
|
||||
$collectionMap[$docKey] = [
|
||||
'total_received' => (float) ($row['total_received'] ?? 0),
|
||||
'cash_received' => (float) ($row['cash_received'] ?? 0),
|
||||
'bank_received' => (float) ($row['bank_received'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($doctorBreakdown as &$row) {
|
||||
$docKey = (int) ($row['doc_id'] ?? 0);
|
||||
$received = $collectionMap[$docKey] ?? ['total_received' => 0.0, 'cash_received' => 0.0, 'bank_received' => 0.0];
|
||||
$row['cash_received'] = $received['cash_received'];
|
||||
$row['bank_received'] = $received['bank_received'];
|
||||
$row['total_received'] = $received['total_received'];
|
||||
$row['credit_amount'] = max(0, round($row['gross_amount'] - $row['total_received'], 2));
|
||||
|
||||
$summary['cash_received'] += $row['cash_received'];
|
||||
$summary['bank_received'] += $row['bank_received'];
|
||||
}
|
||||
unset($row);
|
||||
|
||||
$summary['total_received'] = $summary['cash_received'] + $summary['bank_received'];
|
||||
$summary['credit_amount'] = max(0, round($summary['gross_amount'] - $summary['total_received'], 2));
|
||||
|
||||
if ($db->tableExists('abdm_consent_records')) {
|
||||
$consentBuilder = $db->table('abdm_consent_records acr')
|
||||
->select('COUNT(DISTINCT acr.id) AS approved_consents', false)
|
||||
->join('opd_master o', 'o.p_id = acr.patient_id', 'inner')
|
||||
->where('o.apointment_date >=', $fromDate)
|
||||
->where('o.apointment_date <=', $toDate)
|
||||
->whereIn('o.opd_status', [1, 2])
|
||||
->where('acr.consent_status', 'approved');
|
||||
|
||||
if ($doctorId > 0) {
|
||||
$consentBuilder->where('o.doc_id', $doctorId);
|
||||
}
|
||||
if ($stateUnit !== '' && $stateUnitColumn !== null) {
|
||||
$consentBuilder->where('o.' . $stateUnitColumn, $stateUnit);
|
||||
}
|
||||
|
||||
$consentRow = $consentBuilder->get()->getRowArray();
|
||||
$summary['approved_consents'] = (int) ($consentRow['approved_consents'] ?? 0);
|
||||
}
|
||||
|
||||
if ($db->tableExists('finance_doctor_payouts')) {
|
||||
$payoutBuilder = $db->table('finance_doctor_payouts p')
|
||||
->select('COUNT(*) AS payout_count, SUM(COALESCE(p.approved_amount, p.calculated_amount, 0)) AS payout_amount', false)
|
||||
->where('p.payout_date >=', $fromDate)
|
||||
->where('p.payout_date <=', $toDate);
|
||||
|
||||
if ($doctorId > 0) {
|
||||
$agreementIds = $this->findDoctorAgreementIdsFromMaster($doctorId);
|
||||
if (! empty($agreementIds)) {
|
||||
$payoutBuilder->whereIn('p.doctor_id', $agreementIds);
|
||||
} else {
|
||||
$payoutBuilder->where('1 = 0', null, false);
|
||||
}
|
||||
}
|
||||
|
||||
$payoutRow = $payoutBuilder->get()->getRowArray();
|
||||
$summary['payout_count'] = (int) ($payoutRow['payout_count'] ?? 0);
|
||||
$summary['payout_amount'] = (float) ($payoutRow['payout_amount'] ?? 0);
|
||||
}
|
||||
|
||||
foreach ($doctorBreakdown as &$row) {
|
||||
$row['payout_amount'] = $summary['doctor_count'] === 1 ? $summary['payout_amount'] : 0.0;
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return [
|
||||
'summary' => $summary,
|
||||
'doctor_breakdown' => $doctorBreakdown,
|
||||
'from_date' => $fromDate,
|
||||
'to_date' => $toDate,
|
||||
'doctor_name' => $doctorName,
|
||||
'state_unit' => $stateUnit,
|
||||
'state_unit_label' => $stateUnitColumn !== null ? ucwords(str_replace('_', ' ', $stateUnitColumn)) : 'State/Unit',
|
||||
];
|
||||
}
|
||||
|
||||
private function fetchOpdStateUnitOptions(): array
|
||||
{
|
||||
$column = $this->resolveOpdStateUnitColumn();
|
||||
if ($column === null || ! method_exists($this->db, 'tableExists') || ! $this->db->tableExists('opd_master')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$rows = $this->db->table('opd_master o')
|
||||
->select('TRIM(o.' . $column . ') AS value', false)
|
||||
->where('o.' . $column . ' IS NOT NULL', null, false)
|
||||
->where("TRIM(o." . $column . ") != ''", null, false)
|
||||
->groupBy('o.' . $column)
|
||||
->orderBy('value', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
return array_values(array_filter(array_map(static function (array $row): string {
|
||||
return trim((string) ($row['value'] ?? ''));
|
||||
}, $rows), static fn(string $value): bool => $value !== ''));
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveOpdStateUnitColumn(): ?string
|
||||
{
|
||||
$candidates = ['unit_name', 'unit', 'state_name', 'state', 'branch_name', 'branch', 'center_name', 'location', 'city'];
|
||||
foreach ($candidates as $column) {
|
||||
if ($this->tableHasField('opd_master', $column)) {
|
||||
return $column;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function normalizeStateUnitToken(string $value): string
|
||||
{
|
||||
return preg_replace('/[^A-Za-z0-9]/', '', strtoupper(trim($value)));
|
||||
}
|
||||
|
||||
private function findDoctorAgreementIdsFromMaster(int $doctorId): array
|
||||
{
|
||||
if ($doctorId <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
$code = 'DM-' . $doctorId;
|
||||
$row = $this->doctorAgreementModel->select('id')->where('doctor_code', $code)->first();
|
||||
if (is_array($row) && (int) ($row['id'] ?? 0) > 0) {
|
||||
$ids[] = (int) ($row['id'] ?? 0);
|
||||
}
|
||||
|
||||
if (empty($ids) && method_exists($this->db, 'tableExists') && $this->db->tableExists('doctor_master')) {
|
||||
$doctor = $this->db->table('doctor_master')->select('p_title, p_fname')->where('id', $doctorId)->get(1)->getRowArray();
|
||||
if (is_array($doctor)) {
|
||||
$doctorName = trim((string) (($doctor['p_title'] ?? '') . ' ' . ($doctor['p_fname'] ?? '')));
|
||||
if ($doctorName !== '') {
|
||||
$nameRows = $this->doctorAgreementModel->select('id')->like('doctor_name', $doctorName, 'both')->findAll();
|
||||
foreach ($nameRows as $item) {
|
||||
$id = (int) ($item['id'] ?? 0);
|
||||
if ($id > 0) {
|
||||
$ids[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter($ids, static fn(int $id): bool => $id > 0)));
|
||||
}
|
||||
|
||||
private function fetchEligibleOpdIdsForPayout(string $fromDate, string $toDate, int $doctorId, string $stateUnit = ''): array
|
||||
{
|
||||
if (! method_exists($this->db, 'tableExists') || ! $this->db->tableExists('opd_master')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$builder = $this->db->table('opd_master o')
|
||||
->select('o.opd_id')
|
||||
->where('o.apointment_date >=', $fromDate)
|
||||
->where('o.apointment_date <=', $toDate)
|
||||
->whereIn('o.opd_status', [1, 2]);
|
||||
|
||||
if ($doctorId > 0) {
|
||||
$builder->where('o.doc_id', $doctorId);
|
||||
}
|
||||
|
||||
$stateUnitColumn = $this->resolveOpdStateUnitColumn();
|
||||
if ($stateUnit !== '' && $stateUnitColumn !== null) {
|
||||
$builder->where('o.' . $stateUnitColumn, $stateUnit);
|
||||
}
|
||||
|
||||
if ($this->tableHasField('opd_master', 'payout_draft_id')) {
|
||||
$builder->groupStart()
|
||||
->where('o.payout_draft_id IS NULL', null, false)
|
||||
->orWhere('o.payout_draft_id', 0)
|
||||
->groupEnd();
|
||||
}
|
||||
|
||||
$rows = $builder->get()->getResultArray();
|
||||
|
||||
return array_values(array_filter(array_map(static fn(array $row): int => (int) ($row['opd_id'] ?? 0), $rows), static fn(int $id): bool => $id > 0));
|
||||
}
|
||||
|
||||
private function resolveOrCreateDoctorAgreementIdFromMaster(int $doctorId): int
|
||||
{
|
||||
if ($doctorId <= 0 || ! method_exists($this->db, 'tableExists') || ! $this->db->tableExists('doctor_master')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$doctorRow = $this->db->table('doctor_master')
|
||||
->select('id, p_title, p_fname')
|
||||
->where('id', $doctorId)
|
||||
->get(1)
|
||||
->getRowArray();
|
||||
if (! is_array($doctorRow)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$doctorName = trim((string) (($doctorRow['p_title'] ?? '') . ' ' . ($doctorRow['p_fname'] ?? '')));
|
||||
if ($doctorName === '') {
|
||||
$doctorName = 'Doctor #' . $doctorId;
|
||||
}
|
||||
$doctorCode = 'DM-' . $doctorId;
|
||||
|
||||
$existing = $this->doctorAgreementModel->where('doctor_code', $doctorCode)->first();
|
||||
if (is_array($existing) && (int) ($existing['id'] ?? 0) > 0) {
|
||||
return (int) $existing['id'];
|
||||
}
|
||||
|
||||
$this->doctorAgreementModel->insert([
|
||||
'doctor_code' => $doctorCode,
|
||||
'doctor_name' => $doctorName,
|
||||
'specialization' => null,
|
||||
'consultation_rate' => 0,
|
||||
'surgery_rate' => 0,
|
||||
'agreement_start_date' => null,
|
||||
'agreement_end_date' => null,
|
||||
'status' => 1,
|
||||
'created_by' => $this->currentUserName(),
|
||||
]);
|
||||
|
||||
return (int) ($this->doctorAgreementModel->getInsertID() ?? 0);
|
||||
}
|
||||
|
||||
private function normalizeDateInput(string $date): string
|
||||
{
|
||||
$date = trim($date);
|
||||
|
|
|
|||
|
|
@ -731,8 +731,14 @@ class Patient extends BaseController
|
|||
return $this->response->setStatusCode(404)->setBody('Patient not found');
|
||||
}
|
||||
|
||||
$opdFields = $this->db->getFieldNames('opd_master') ?? [];
|
||||
$opdSelect = ['opd_id', 'opd_code', 'doc_name', 'apointment_date'];
|
||||
if (in_array('queue_no', $opdFields, true)) {
|
||||
$opdSelect[] = 'queue_no';
|
||||
}
|
||||
|
||||
$opdList = $this->db->table('opd_master')
|
||||
->select('opd_id, opd_code, doc_name, apointment_date, queue_no')
|
||||
->select(implode(', ', $opdSelect))
|
||||
->where('p_id', $pno)
|
||||
->orderBy('opd_id', 'DESC')
|
||||
->get()
|
||||
|
|
@ -741,6 +747,7 @@ class Patient extends BaseController
|
|||
$opdIds = array_column($opdList, 'opd_id');
|
||||
$filesByOpd = [];
|
||||
$rxByOpd = [];
|
||||
$medicinesBySession = [];
|
||||
|
||||
if ($opdIds && $this->db->tableExists('file_upload_data')) {
|
||||
$fields = $this->db->getFieldNames('file_upload_data') ?? [];
|
||||
|
|
@ -790,7 +797,7 @@ class Patient extends BaseController
|
|||
if (in_array('opd_id', $rxFields, true)) {
|
||||
$selectParts[] = 'opd_id';
|
||||
}
|
||||
foreach (['date_opd_visit', 'queue_no', 'bp', 'diastolic', 'pulse', 'temp', 'spo2', 'complaints', 'diagnosis', 'investigation', 'advice'] as $col) {
|
||||
foreach (['date_opd_visit', 'queue_no', 'bp', 'diastolic', 'pulse', 'temp', 'spo2', 'complaints', 'diagnosis', 'investigation', 'advice', 'next_visit', 'refer_to'] as $col) {
|
||||
if (in_array($col, $rxFields, true)) {
|
||||
$selectParts[] = $col;
|
||||
}
|
||||
|
|
@ -814,6 +821,55 @@ class Patient extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
$medicineTable = $this->findExistingTable(['opd_prescrption_prescribed', 'opd_prescription_prescribed']);
|
||||
$rxSessionIds = array_values(array_filter(array_map(static fn(array $row): int => (int) ($row['id'] ?? 0), $rxByOpd), static fn(int $id): bool => $id > 0));
|
||||
if ($medicineTable !== null && $this->db->tableExists($medicineTable) && $rxSessionIds !== []) {
|
||||
$medicineBuilder = $this->db->table($medicineTable . ' pt')
|
||||
->select('pt.id, pt.opd_pre_id, pt.med_type, pt.med_name, pt.dosage, pt.dosage_when, pt.dosage_freq, pt.dosage_where, pt.no_of_days, pt.qty, pt.remark');
|
||||
|
||||
if ($this->db->tableExists('opd_dose_shed')) {
|
||||
$medicineBuilder->select('d.dose_show_sign AS dose_shed', false)
|
||||
->join('opd_dose_shed d', 'pt.dosage = d.dose_shed_id', 'left');
|
||||
}
|
||||
if ($this->db->tableExists('opd_dose_when')) {
|
||||
$medicineBuilder->select('dw.dose_sign_desc AS dose_when_label', false)
|
||||
->join('opd_dose_when dw', 'pt.dosage_when = dw.dose_when_id', 'left');
|
||||
}
|
||||
if ($this->db->tableExists('opd_dose_frequency')) {
|
||||
$medicineBuilder->select('df.dose_sign_desc AS dose_frequency_label', false)
|
||||
->join('opd_dose_frequency df', 'pt.dosage_freq = df.dose_freq_id', 'left');
|
||||
}
|
||||
if ($this->db->tableExists('opd_dose_where')) {
|
||||
$medicineBuilder->select('d_on.dose_sign_desc AS dose_where_label', false)
|
||||
->join('opd_dose_where d_on', 'pt.dosage_where = d_on.dose_where_id', 'left');
|
||||
}
|
||||
|
||||
$medicineRows = $medicineBuilder
|
||||
->whereIn('pt.opd_pre_id', $rxSessionIds)
|
||||
->orderBy('pt.id', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
foreach ($medicineRows as $medicineRow) {
|
||||
$sessionId = (int) ($medicineRow['opd_pre_id'] ?? 0);
|
||||
if ($sessionId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$medicinesBySession[$sessionId][] = [
|
||||
'med_type' => trim((string) ($medicineRow['med_type'] ?? '')),
|
||||
'med_name' => trim((string) ($medicineRow['med_name'] ?? '')),
|
||||
'dose' => trim((string) ($medicineRow['dose_shed'] ?? '')),
|
||||
'timing' => trim((string) ($medicineRow['dose_when_label'] ?? '')),
|
||||
'frequency' => trim((string) ($medicineRow['dose_frequency_label'] ?? '')),
|
||||
'where' => trim((string) ($medicineRow['dose_where_label'] ?? '')),
|
||||
'days' => trim((string) ($medicineRow['no_of_days'] ?? '')),
|
||||
'qty' => trim((string) ($medicineRow['qty'] ?? '')),
|
||||
'remark' => trim((string) ($medicineRow['remark'] ?? '')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$opdGroups = [];
|
||||
foreach ($opdList as $row) {
|
||||
$opdId = (int) $row['opd_id'];
|
||||
|
|
@ -838,6 +894,9 @@ class Patient extends BaseController
|
|||
'diagnosis' => (string) ($rx['diagnosis'] ?? ''),
|
||||
'investigation' => (string) ($rx['investigation'] ?? ''),
|
||||
'advice' => (string) ($rx['advice'] ?? ''),
|
||||
'next_visit' => (string) ($rx['next_visit'] ?? ''),
|
||||
'refer_to' => (string) ($rx['refer_to'] ?? ''),
|
||||
'medicines' => $medicinesBySession[$rxSessionId] ?? [],
|
||||
'files' => $filesByOpd[$opdId] ?? [],
|
||||
];
|
||||
}
|
||||
|
|
@ -848,6 +907,17 @@ class Patient extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
private function findExistingTable(array $candidates): ?string
|
||||
{
|
||||
foreach ($candidates as $table) {
|
||||
if ($this->db->tableExists($table)) {
|
||||
return $table;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function save_profile_image(int $pid)
|
||||
{
|
||||
$patient = $this->db->table('patient_master')->where('id', $pid)->get()->getRow();
|
||||
|
|
|
|||
950
app/Controllers/Storestock.php
Normal file
950
app/Controllers/Storestock.php
Normal file
|
|
@ -0,0 +1,950 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use Mpdf\Mpdf;
|
||||
|
||||
class Storestock extends BaseController
|
||||
{
|
||||
protected $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = \Config\Database::connect();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Auth helper
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private function ensureStoreAccess()
|
||||
{
|
||||
$user = service('auth')->user();
|
||||
|
||||
$allowed = false;
|
||||
if ($user && method_exists($user, 'can')) {
|
||||
$allowed = $user->can('hospital_stock.access') || $user->can('pharmacy.access');
|
||||
}
|
||||
|
||||
if (! $allowed && $user && method_exists($user, 'inGroup')) {
|
||||
$allowed = $user->inGroup(
|
||||
'superadmin', 'admin', 'developer',
|
||||
'stock_manager', 'stock_requester', 'stock_issuer', 'storekeeper'
|
||||
);
|
||||
}
|
||||
|
||||
if ($allowed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->response
|
||||
->setStatusCode(403)
|
||||
->setBody('<div class="alert alert-danger m-3">Access denied for Hospital Stock module.</div>');
|
||||
}
|
||||
|
||||
private function actorLabel(): string
|
||||
{
|
||||
$user = service('auth')->user();
|
||||
if (! $user) {
|
||||
return 'System[' . date('Y-m-d H:i:s') . ']';
|
||||
}
|
||||
$name = trim(($user->first_name ?? '') . ' ' . ($user->last_name ?? ''));
|
||||
if ($name === '' || $name === ' ') {
|
||||
$name = (string) ($user->username ?? ($user->email ?? 'User'));
|
||||
}
|
||||
$userId = (int) ($user->id ?? 0);
|
||||
return $name . '[' . $userId . '][' . date('d-m-Y H:i:s') . ']';
|
||||
}
|
||||
|
||||
private function safeScalar(string $sql, array $bindings = [], $default = 0)
|
||||
{
|
||||
try {
|
||||
$row = $this->db->query($sql, $bindings)->getRowArray();
|
||||
if (! is_array($row) || empty($row)) {
|
||||
return $default;
|
||||
}
|
||||
$val = reset($row);
|
||||
return $val !== null ? $val : $default;
|
||||
} catch (\Throwable $e) {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
private function safeRows(string $sql, array $bindings = []): array
|
||||
{
|
||||
try {
|
||||
return $this->db->query($sql, $bindings)->getResultArray();
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private function buildDashboardInsights(): array
|
||||
{
|
||||
$todayIndents = (int) $this->safeScalar(
|
||||
"SELECT COUNT(*) AS c FROM invoice_stock_master WHERE DATE(indent_date) = CURDATE()",
|
||||
[],
|
||||
0
|
||||
);
|
||||
|
||||
$monthIndents = (int) $this->safeScalar(
|
||||
"SELECT COUNT(*) AS c FROM invoice_stock_master WHERE DATE_FORMAT(indent_date, '%Y-%m') = DATE_FORMAT(CURDATE(), '%Y-%m')",
|
||||
[],
|
||||
0
|
||||
);
|
||||
|
||||
$pendingPurchase = (int) $this->safeScalar(
|
||||
"SELECT COUNT(*) AS c FROM purchase_invoice_stock WHERE IFNULL(inv_status, 0) = 0",
|
||||
[],
|
||||
0
|
||||
);
|
||||
|
||||
$lowStockCount = (int) $this->safeScalar(
|
||||
"SELECT COUNT(*) AS c FROM (
|
||||
SELECT p.id,
|
||||
COALESCE(SUM(s.total_unit - s.total_sale_unit - s.total_lost_unit - s.total_return_unit), 0) AS cur_units,
|
||||
COALESCE(MAX(p.re_order_qty), 0) AS re_order_qty,
|
||||
COALESCE(MAX(NULLIF(s.packing, 0)), 1) AS packing
|
||||
FROM med_store_product_master p
|
||||
LEFT JOIN purchase_invoice_item_stock s
|
||||
ON p.id = s.item_code
|
||||
AND s.remove_item = 0
|
||||
AND s.item_return = 0
|
||||
WHERE IFNULL(p.is_continue, 1) = 1
|
||||
GROUP BY p.id
|
||||
HAVING cur_units <= (re_order_qty * packing)
|
||||
) x",
|
||||
[],
|
||||
0
|
||||
);
|
||||
|
||||
$nearExpiryCount = (int) $this->safeScalar(
|
||||
"SELECT COUNT(*) AS c
|
||||
FROM purchase_invoice_item_stock s
|
||||
WHERE s.remove_item = 0
|
||||
AND s.item_return = 0
|
||||
AND (s.total_unit - s.total_sale_unit - s.total_lost_unit - s.total_return_unit) > 0
|
||||
AND s.expiry_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 60 DAY)",
|
||||
[],
|
||||
0
|
||||
);
|
||||
|
||||
$stockValue = (float) $this->safeScalar(
|
||||
"SELECT ROUND(SUM(
|
||||
(s.total_unit - s.total_sale_unit - s.total_lost_unit - s.total_return_unit)
|
||||
* IFNULL(s.purchase_unit_rate, s.purchase_price)
|
||||
), 2) AS v
|
||||
FROM purchase_invoice_item_stock s
|
||||
WHERE s.remove_item = 0
|
||||
AND s.item_return = 0
|
||||
AND (s.total_unit - s.total_sale_unit - s.total_lost_unit - s.total_return_unit) > 0",
|
||||
[],
|
||||
0
|
||||
);
|
||||
|
||||
$lowStockItems = $this->safeRows(
|
||||
"SELECT p.item_name,
|
||||
COALESCE(MAX(p.re_order_qty), 0) AS re_order_qty,
|
||||
COALESCE(MAX(NULLIF(s.packing, 0)), 1) AS packing,
|
||||
ROUND(COALESCE(SUM(s.total_unit - s.total_sale_unit - s.total_lost_unit - s.total_return_unit), 0), 2) AS cur_units
|
||||
FROM med_store_product_master p
|
||||
LEFT JOIN purchase_invoice_item_stock s
|
||||
ON p.id = s.item_code
|
||||
AND s.remove_item = 0
|
||||
AND s.item_return = 0
|
||||
WHERE IFNULL(p.is_continue, 1) = 1
|
||||
GROUP BY p.id, p.item_name
|
||||
HAVING cur_units <= (re_order_qty * packing)
|
||||
ORDER BY cur_units ASC
|
||||
LIMIT 8"
|
||||
);
|
||||
|
||||
$nearExpiryItems = $this->safeRows(
|
||||
"SELECT s.item_name,
|
||||
s.batch_no,
|
||||
DATE_FORMAT(s.expiry_date, '%d-%m-%Y') AS expiry_str,
|
||||
DATEDIFF(s.expiry_date, CURDATE()) AS days_left,
|
||||
ROUND((s.total_unit - s.total_sale_unit - s.total_lost_unit - s.total_return_unit) / NULLIF(s.packing, 0), 2) AS cur_packs
|
||||
FROM purchase_invoice_item_stock s
|
||||
WHERE s.remove_item = 0
|
||||
AND s.item_return = 0
|
||||
AND (s.total_unit - s.total_sale_unit - s.total_lost_unit - s.total_return_unit) > 0
|
||||
AND s.expiry_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 60 DAY)
|
||||
ORDER BY s.expiry_date ASC
|
||||
LIMIT 8"
|
||||
);
|
||||
|
||||
return [
|
||||
'stats' => [
|
||||
'today_indents' => $todayIndents,
|
||||
'month_indents' => $monthIndents,
|
||||
'pending_purchase' => $pendingPurchase,
|
||||
'low_stock_count' => $lowStockCount,
|
||||
'near_expiry_count' => $nearExpiryCount,
|
||||
'stock_value' => $stockValue,
|
||||
],
|
||||
'lists' => [
|
||||
'low_stock_items' => $lowStockItems,
|
||||
'near_expiry_items' => $nearExpiryItems,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Dashboard
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function index()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$data = $this->buildDashboardInsights();
|
||||
return view('storestock/dashboard', $data);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Indent List
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function indent_list()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
return view('storestock/indent_list');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Indent DataTable (server-side)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function getIndentTable()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$columns = [
|
||||
0 => 'indent_code',
|
||||
1 => 'issued_name',
|
||||
2 => 'indent_date_str',
|
||||
3 => 'id',
|
||||
];
|
||||
|
||||
$draw = (int) ($this->request->getPost('draw') ?? $this->request->getGet('draw') ?? 1);
|
||||
$start = (int) ($this->request->getPost('start') ?? $this->request->getGet('start') ?? 0);
|
||||
$length = (int) ($this->request->getPost('length') ?? $this->request->getGet('length') ?? 10);
|
||||
|
||||
$orderColIdx = (int) ($this->request->getPost('order[0][column]') ?? 0);
|
||||
$orderDir = $this->request->getPost('order[0][dir]') === 'asc' ? 'asc' : 'desc';
|
||||
$orderCol = $columns[$orderColIdx] ?? 'id';
|
||||
|
||||
// Column search values
|
||||
$searchIndentCode = (string) ($this->request->getPost('columns[0][search][value]') ?? '');
|
||||
$searchIssuedName = (string) ($this->request->getPost('columns[1][search][value]') ?? '');
|
||||
$searchIndentDate = (string) ($this->request->getPost('columns[2][search][value]') ?? '');
|
||||
|
||||
// Total records
|
||||
$totalData = (int) $this->db->query("SELECT COUNT(*) AS no_rec FROM invoice_stock_master")->getRow()->no_rec;
|
||||
$totalFiltered = $totalData;
|
||||
|
||||
// Build parameterised WHERE
|
||||
$whereSQL = ' WHERE 1=1';
|
||||
$whereParams = [];
|
||||
|
||||
if ($searchIndentCode !== '') {
|
||||
$esc = $this->db->escapeLikeString($searchIndentCode);
|
||||
$whereSQL .= " AND indent_code LIKE '%{$esc}%'";
|
||||
}
|
||||
|
||||
if ($searchIssuedName !== '') {
|
||||
$esc = $this->db->escapeLikeString($searchIssuedName);
|
||||
$whereSQL .= " AND issued_name LIKE '%{$esc}%'";
|
||||
}
|
||||
|
||||
if ($searchIndentDate !== '') {
|
||||
// Date column — use parameterised bind
|
||||
$whereSQL .= ' AND DATE(indent_date) = ?';
|
||||
$whereParams[] = $searchIndentDate;
|
||||
}
|
||||
|
||||
if ($searchIndentCode !== '' || $searchIssuedName !== '' || $searchIndentDate !== '') {
|
||||
$countSql = "SELECT COUNT(*) AS no_rec FROM invoice_stock_master{$whereSQL}";
|
||||
$totalFiltered = (int) $this->db->query($countSql, $whereParams)->getRow()->no_rec;
|
||||
}
|
||||
|
||||
$orderClause = " ORDER BY {$orderCol} {$orderDir}, id desc";
|
||||
$limitClause = ' LIMIT ' . $start . ', ' . $length;
|
||||
|
||||
$selectSQL = "SELECT m.id, m.indent_code, m.issued_name, m.indent_date,
|
||||
DATE_FORMAT(m.indent_date,'%d-%m-%Y') AS indent_date_str
|
||||
FROM invoice_stock_master m{$whereSQL}{$orderClause}{$limitClause}";
|
||||
|
||||
$rdata = $this->db->query($selectSQL, $whereParams)->getResultArray();
|
||||
|
||||
$rows = [];
|
||||
foreach ($rdata as $aRow) {
|
||||
$rows[] = [
|
||||
$aRow['indent_code'],
|
||||
$aRow['issued_name'],
|
||||
$aRow['indent_date_str'],
|
||||
$aRow['id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'draw' => $draw,
|
||||
'recordsTotal' => $totalData,
|
||||
'recordsFiltered' => $totalFiltered,
|
||||
'data' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// New Indent form
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function new_indent()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$data['location_master'] = $this->db->query("SELECT * FROM location_master ORDER BY loc_name")->getResult();
|
||||
$data['employee_master'] = $this->db->query("SELECT * FROM employee_master ORDER BY emp_name")->getResult();
|
||||
|
||||
return view('storestock/new_indent', $data);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Create Indent
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function indent_create(int $location_type)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$date_indent = $this->request->getPost('date_indent');
|
||||
$loc_id = (int) $this->request->getPost('loc_id');
|
||||
|
||||
// Convert dd/mm/yyyy → yyyy-mm-dd
|
||||
$mysql_date = date('Y-m-d');
|
||||
if ($date_indent) {
|
||||
$parts = explode('/', trim($date_indent));
|
||||
if (count($parts) === 3) {
|
||||
$mysql_date = $parts[2] . '-' . $parts[1] . '-' . $parts[0];
|
||||
} else {
|
||||
$mysql_date = date('Y-m-d', strtotime($date_indent));
|
||||
}
|
||||
}
|
||||
|
||||
$issued_name = '';
|
||||
|
||||
if ($location_type == 1) {
|
||||
$row = $this->db->query("SELECT * FROM location_master WHERE l_id = ?", [$loc_id])->getRow();
|
||||
if ($row) {
|
||||
$issued_name = $row->loc_name;
|
||||
}
|
||||
} elseif ($location_type == 2) {
|
||||
$row = $this->db->query("SELECT * FROM employee_master WHERE emp_id = ?", [$loc_id])->getRow();
|
||||
if ($row) {
|
||||
$issued_name = $row->emp_name . ' [' . $row->emp_code . ']';
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->table('invoice_stock_master')->insert([
|
||||
'indent_date' => $mysql_date,
|
||||
'location_type' => $location_type,
|
||||
'location_id' => $loc_id,
|
||||
'issued_name' => $issued_name,
|
||||
]);
|
||||
|
||||
$insert_id = $this->db->insertID();
|
||||
|
||||
// Generate indent_code like S{ym}{padded_id}
|
||||
$ym = date('ym');
|
||||
$indent_code = 'S' . $ym . str_pad($insert_id, 5, '0', STR_PAD_LEFT);
|
||||
$this->db->table('invoice_stock_master')->where('id', $insert_id)->update(['indent_code' => $indent_code]);
|
||||
|
||||
return redirect()->to(site_url('Storestock/Indent_show/' . $insert_id));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Show / Edit Indent
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function indent_show(int $indent_id)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$data['invoice_stock_master'] = $this->db->query(
|
||||
"SELECT * FROM invoice_stock_master WHERE id = ?",
|
||||
[$indent_id]
|
||||
)->getResult();
|
||||
|
||||
$data['inv_stock_item'] = $this->db->query(
|
||||
"SELECT i.*, IFNULL(DATEDIFF(expiry, CURDATE()), 1000) AS no_day,
|
||||
SUM(p.total_unit - p.total_sale_unit - p.total_return_unit - p.total_lost_unit) / p.packing AS cur_qty
|
||||
FROM inv_stock_item i
|
||||
JOIN purchase_invoice_item_stock p ON i.item_code = p.item_code AND p.remove_item = 0
|
||||
WHERE i.indent_id = ?
|
||||
GROUP BY i.id",
|
||||
[$indent_id]
|
||||
)->getResult();
|
||||
|
||||
$data['invoiceGtotal'] = $this->db->query(
|
||||
"SELECT SUM(amount) AS Gtotal, SUM(tamount) AS tamt, SUM(CGST) AS TCGST, SUM(SGST) AS TSGST
|
||||
FROM inv_stock_item WHERE indent_id = ?",
|
||||
[$indent_id]
|
||||
)->getResult();
|
||||
|
||||
$data['content'] = view('storestock/indent_item_list', $data);
|
||||
|
||||
return view('storestock/indent_edit', $data);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Indent Items List (AJAX partial refresh)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function indent_items_list(int $indent_id)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$data['inv_stock_item'] = $this->db->query(
|
||||
"SELECT * FROM inv_stock_item WHERE indent_id = ? ORDER BY id",
|
||||
[$indent_id]
|
||||
)->getResult();
|
||||
|
||||
$data['invoiceGtotal'] = $this->db->query(
|
||||
"SELECT SUM(amount) AS Gtotal, SUM(tamount) AS tamt, SUM(CGST) AS TCGST, SUM(SGST) AS TSGST
|
||||
FROM inv_stock_item WHERE indent_id = ?",
|
||||
[$indent_id]
|
||||
)->getResult();
|
||||
|
||||
$html = view('storestock/indent_item_list', $data);
|
||||
return $this->response->setBody($html);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Add / Update / Delete item from indent
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function add_item(int $type)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$insert_id = 1;
|
||||
$error_show = '';
|
||||
$exist = 0;
|
||||
$input_id = 0;
|
||||
$inv_id = 0;
|
||||
|
||||
$user = service('auth')->user();
|
||||
$user_id = $user ? (int) ($user->id ?? 0) : 0;
|
||||
$user_name_info = $this->actorLabel();
|
||||
|
||||
$store_stock_id = (int) $this->request->getPost('l_ssno');
|
||||
|
||||
if ($type == 1) {
|
||||
// ---- Add new item ----
|
||||
$inv_id = (int) $this->request->getPost('inv_id');
|
||||
$qty = (float) $this->request->getPost('qty');
|
||||
$disc = (float) $this->request->getPost('disc');
|
||||
$product_unit_rate = (float) $this->request->getPost('product_unit_rate');
|
||||
|
||||
$item = $this->db->query(
|
||||
"SELECT p.item_name, t.batch_no, t.expiry_date,
|
||||
(t.total_unit - t.total_sale_unit) AS S_qty,
|
||||
t.mrp, t.selling_unit_rate, p.id AS master_product_id,
|
||||
t.id AS purchase_item_id, p.formulation,
|
||||
t.HSNCODE, t.CGST_per, t.SGST_per, t.packing
|
||||
FROM med_store_product_master p
|
||||
JOIN purchase_invoice_item_stock t ON p.id = t.item_code
|
||||
WHERE t.id = ?",
|
||||
[$store_stock_id]
|
||||
)->getRow();
|
||||
|
||||
if (! $item) {
|
||||
$insert_id = 0;
|
||||
$error_show = 'Item not found in stock';
|
||||
} else {
|
||||
$item_exists = $this->db->query(
|
||||
"SELECT * FROM inv_stock_item WHERE indent_id = ? AND store_stock_id = ?",
|
||||
[$inv_id, $store_stock_id]
|
||||
)->getResult();
|
||||
|
||||
$stock_qty = (float) $item->S_qty;
|
||||
$amount_value = $qty * $product_unit_rate;
|
||||
$disc_amount = $amount_value * $disc / 100;
|
||||
$tamount_value = $amount_value - $disc_amount;
|
||||
|
||||
if ($qty > $stock_qty) {
|
||||
$insert_id = 0;
|
||||
$error_show = 'Stock Qty. is less than Required Qty : Current Qty :' . $stock_qty;
|
||||
} elseif ($stock_qty == 0) {
|
||||
$insert_id = 0;
|
||||
$error_show = 'Stock is Empty';
|
||||
}
|
||||
|
||||
if ($insert_id > 0) {
|
||||
if (count($item_exists) > 0) {
|
||||
$exist = count($item_exists);
|
||||
$input_id = 0;
|
||||
$error_show = 'Please Check Also Same Item List';
|
||||
}
|
||||
|
||||
$this->db->table('inv_stock_item')->insert([
|
||||
'indent_id' => $inv_id,
|
||||
'item_code' => $item->master_product_id,
|
||||
'item_Name' => $item->item_name,
|
||||
'formulation' => $item->formulation,
|
||||
'qty' => $qty,
|
||||
'batch_no' => $item->batch_no,
|
||||
'expiry' => $item->expiry_date,
|
||||
'price' => $product_unit_rate,
|
||||
'price2' => $product_unit_rate,
|
||||
'mrp' => $item->mrp,
|
||||
'disc_per' => $disc,
|
||||
'disc_amount' => $disc_amount,
|
||||
'amount' => $amount_value,
|
||||
'tamount' => $tamount_value,
|
||||
'CGST_per' => $item->CGST_per,
|
||||
'SGST_per' => $item->SGST_per,
|
||||
'HSNCODE' => $item->HSNCODE,
|
||||
'store_stock_id' => $item->purchase_item_id,
|
||||
'update_by_id' => $user_id,
|
||||
'update_by_remark' => $user_name_info,
|
||||
'packing' => $item->packing,
|
||||
]);
|
||||
|
||||
$insert_id = $this->db->insertID();
|
||||
}
|
||||
}
|
||||
|
||||
} elseif ($type == 2) {
|
||||
// ---- Update qty ----
|
||||
$update_qty = (float) $this->request->getPost('u_qty');
|
||||
$item_id = (int) $this->request->getPost('itemid');
|
||||
|
||||
$inv_item = $this->db->query(
|
||||
"SELECT * FROM inv_stock_item WHERE id = ?",
|
||||
[$item_id]
|
||||
)->getRow();
|
||||
|
||||
if (! $inv_item) {
|
||||
$insert_id = 0;
|
||||
$error_show = 'Item not found';
|
||||
} else {
|
||||
$inv_id = (int) $inv_item->indent_id;
|
||||
$store_stock_id = (int) $inv_item->store_stock_id;
|
||||
$disc = (float) $inv_item->disc_per;
|
||||
$old_qty = (float) $inv_item->qty;
|
||||
$diff_qty = $update_qty - $old_qty;
|
||||
|
||||
if ($diff_qty > 0) {
|
||||
$stock_item = $this->db->query(
|
||||
"SELECT (t.total_unit - t.total_sale_unit) AS S_qty
|
||||
FROM purchase_invoice_item_stock t WHERE t.id = ?",
|
||||
[$store_stock_id]
|
||||
)->getRow();
|
||||
|
||||
$stock_qty = $stock_item ? (float) $stock_item->S_qty : 0;
|
||||
|
||||
if ($diff_qty > $stock_qty) {
|
||||
$insert_id = 0;
|
||||
$error_show = 'Stock Qty. is less than Required Qty : Current Qty :' . $stock_qty;
|
||||
}
|
||||
}
|
||||
|
||||
if ($insert_id > 0) {
|
||||
$item_rate = (float) $inv_item->price;
|
||||
$amount_value = $update_qty * $item_rate;
|
||||
$disc_amount = $amount_value * $disc / 100;
|
||||
$tamount = $amount_value - $disc_amount;
|
||||
|
||||
$this->db->table('inv_stock_item')->where('id', $item_id)->update([
|
||||
'qty' => $update_qty,
|
||||
'disc_amount' => $disc_amount,
|
||||
'amount' => $amount_value,
|
||||
'tamount' => $tamount,
|
||||
'store_stock_id'=> $store_stock_id,
|
||||
'packing' => $inv_item->packing,
|
||||
'indent_id' => $inv_id,
|
||||
]);
|
||||
|
||||
$insert_id = $item_id;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// ---- Delete item ----
|
||||
$item_id = (int) $this->request->getPost('itemid');
|
||||
$inv_item = $this->db->query(
|
||||
"SELECT * FROM inv_stock_item WHERE id = ?",
|
||||
[$item_id]
|
||||
)->getRowArray();
|
||||
|
||||
if ($inv_item) {
|
||||
$inv_id = (int) $inv_item['indent_id'];
|
||||
|
||||
// Archive to delete table
|
||||
$del_data = $inv_item;
|
||||
$del_data['del_by'] = $user_name_info;
|
||||
unset($del_data['id']);
|
||||
$this->db->table('inv_stock_item_delete')->insert($del_data);
|
||||
|
||||
$this->db->table('inv_stock_item')->where('id', $item_id)->delete();
|
||||
$insert_id = 1;
|
||||
} else {
|
||||
$insert_id = 0;
|
||||
$error_show = 'Item not found';
|
||||
}
|
||||
}
|
||||
|
||||
$content = '';
|
||||
|
||||
if ($insert_id > 0) {
|
||||
// Call stored procs
|
||||
$this->db->query("CALL p_stock_update_purchase_id_store({$inv_id})");
|
||||
$this->db->query("CALL p_update_med_GST_store({$inv_id})");
|
||||
|
||||
$data['inv_stock_item'] = $this->db->query(
|
||||
"SELECT i.*, IFNULL(DATEDIFF(expiry, CURDATE()), 1000) AS no_day,
|
||||
SUM(p.total_unit - p.total_sale_unit - p.total_return_unit - p.total_lost_unit) / p.packing AS cur_qty
|
||||
FROM inv_stock_item i
|
||||
JOIN purchase_invoice_item_stock p ON i.item_code = p.item_code AND p.remove_item = 0
|
||||
WHERE i.indent_id = ?
|
||||
GROUP BY i.id",
|
||||
[$inv_id]
|
||||
)->getResult();
|
||||
|
||||
$data['invoiceGtotal'] = $this->db->query(
|
||||
"SELECT SUM(amount) AS Gtotal, SUM(tamount) AS tamt,
|
||||
SUM(CGST) AS TCGST, SUM(SGST) AS TSGST, SUM(disc_amount) AS t_dis_amt
|
||||
FROM inv_stock_item WHERE indent_id = ?",
|
||||
[$inv_id]
|
||||
)->getResult();
|
||||
|
||||
$content = view('storestock/indent_item_list', $data);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'exist' => $exist,
|
||||
'insertid' => $insert_id,
|
||||
'content' => $content,
|
||||
'error' => $error_show,
|
||||
'input_id' => $input_id,
|
||||
'csrf_token_value' => csrf_hash(),
|
||||
]);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Drug autocomplete
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function get_drug()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$term = trim((string) $this->request->getGet('term'));
|
||||
if ($term === '') {
|
||||
return $this->response->setJSON([]);
|
||||
}
|
||||
|
||||
$q = strtolower($term);
|
||||
$esc = $this->db->escapeLikeString($q);
|
||||
|
||||
$sql = "SELECT p.item_name, p.formulation, t.batch_no,
|
||||
t.expiry_date AS expiry_date_str, t.expiry_date,
|
||||
(t.total_unit - t.total_sale_unit - t.total_lost_unit - t.total_return_unit) AS c_qty,
|
||||
t.id, t.mrp, t.selling_unit_rate, p.id AS item_code, t.packing
|
||||
FROM med_store_product_master p
|
||||
JOIN purchase_invoice_item_stock t ON p.id = t.item_code
|
||||
AND (t.total_unit - t.total_sale_unit - t.total_lost_unit - t.total_return_unit) > 0
|
||||
AND (expiry_date > DATE_ADD(SYSDATE(), INTERVAL 1 DAY) OR p.exp_date_applicable = 0)
|
||||
AND t.remove_item = 0 AND t.item_return = 0
|
||||
WHERE p.item_name LIKE '{$esc}%'
|
||||
ORDER BY p.item_name, t.id
|
||||
LIMIT 100";
|
||||
|
||||
$rows = $this->db->query($sql)->getResultArray();
|
||||
$row_set = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$row_set[] = [
|
||||
'label' => htmlentities($row['item_name'] . ' ' . $row['formulation'])
|
||||
. ' |B:' . htmlentities($row['batch_no'])
|
||||
. ' |Pak:' . htmlentities($row['packing'])
|
||||
. ' |Rs.' . htmlentities($row['mrp'])
|
||||
. ' |Qty:' . htmlentities($row['c_qty']),
|
||||
'value' => htmlentities($row['item_name']),
|
||||
'l_item_code' => htmlentities($row['item_code']),
|
||||
'l_ss_no' => htmlentities($row['id']),
|
||||
'l_Batch' => htmlentities($row['batch_no']),
|
||||
'l_Expiry' => htmlentities($row['expiry_date_str']),
|
||||
'l_mrp' => htmlentities($row['mrp']),
|
||||
'l_unit_rate' => htmlentities($row['selling_unit_rate']),
|
||||
'l_c_qty' => htmlentities($row['c_qty']),
|
||||
'l_packing' => htmlentities($row['packing']),
|
||||
];
|
||||
}
|
||||
|
||||
return $this->response->setJSON($row_set);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Final Invoice
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function final_invoice(int $inv_id)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
// Call stored procedure to recalculate GST
|
||||
$this->db->query("CALL p_update_med_GST_store({$inv_id})");
|
||||
|
||||
$data['invoice_stock_master'] = $this->db->query(
|
||||
"SELECT *, (discount_amount + item_discount_amount) AS inv_disc_total,
|
||||
(CGST_Tamount + SGST_Tamount) AS TGST
|
||||
FROM invoice_stock_master WHERE id = ?",
|
||||
[$inv_id]
|
||||
)->getResult();
|
||||
|
||||
$data['inv_items'] = $this->db->query(
|
||||
"SELECT * FROM inv_stock_item WHERE indent_id = ?",
|
||||
[$inv_id]
|
||||
)->getResult();
|
||||
|
||||
$data['invoiceGtotal'] = $this->db->query(
|
||||
"SELECT SUM(amount) AS Gtotal, SUM(tamount) AS tamt,
|
||||
SUM(CGST) AS TCGST, SUM(SGST) AS TSGST, SUM(disc_amount) AS t_dis_amt
|
||||
FROM inv_stock_item WHERE indent_id = ?",
|
||||
[$inv_id]
|
||||
)->getResult();
|
||||
|
||||
return view('storestock/final_invoice', $data);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Print single indent (mPDF)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function print_single_indent(int $inv_id)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$this->db->query("CALL p_update_med_GST_store({$inv_id})");
|
||||
|
||||
$data['inv_items'] = $this->db->query(
|
||||
"SELECT i.indent_id, i.id, i.item_Name, i.formulation,
|
||||
i.batch_no, DATE_FORMAT(i.expiry, '%m-%y') AS expiry,
|
||||
i.price, i.qty, (i.amount) AS amount,
|
||||
i.HSNCODE, m.indent_code, m.id AS m_id,
|
||||
DATE_FORMAT(m.indent_date, '%d-%m-%Y') AS str_inv_date,
|
||||
(i.disc_amount) AS d_amt,
|
||||
(i.CGST + i.SGST) AS gst,
|
||||
(i.CGST_per + i.SGST_per) AS gst_per, i.sale_return
|
||||
FROM inv_stock_item i
|
||||
JOIN invoice_stock_master m ON i.indent_id = m.id
|
||||
WHERE m.id = ?
|
||||
ORDER BY i.sale_return, i.id",
|
||||
[$inv_id]
|
||||
)->getResult();
|
||||
|
||||
$data['invoice_stock_master'] = $this->db->query(
|
||||
"SELECT *, DATE_FORMAT(indent_date,'%d-%m-%Y') AS str_inv_date,
|
||||
(discount_amount + item_discount_amount) AS inv_disc_total,
|
||||
(CGST_Tamount + SGST_Tamount) AS TGST
|
||||
FROM invoice_stock_master WHERE id = ?",
|
||||
[$inv_id]
|
||||
)->getResult();
|
||||
|
||||
$html = view('storestock/store_print', $data);
|
||||
|
||||
$mpdf = new Mpdf([
|
||||
'margin_top' => 40,
|
||||
'margin_bottom' => 12,
|
||||
'margin_left' => 10,
|
||||
'margin_right' => 5,
|
||||
]);
|
||||
$mpdf->showWatermarkText = false;
|
||||
$mpdf->WriteHTML($html);
|
||||
|
||||
$filename = 'Indent-' . $inv_id . '-' . date('Ymdhis') . '.pdf';
|
||||
$mpdf->Output($filename, 'I');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Store Stock Search
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function store_stock()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$data['supplier_data'] = $this->db->query("SELECT * FROM stock_supplier ORDER BY name_supplier")->getResult();
|
||||
$data['med_formulation'] = $this->db->query("SELECT * FROM med_formulation ORDER BY formulation_length")->getResult();
|
||||
|
||||
return view('storestock/store_stock_search', $data);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Store Stock Result (POST)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function store_stock_result()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$supplier_id = (int) $this->request->getPost('input_supplier');
|
||||
$item_name = (string) $this->request->getPost('txtsearch');
|
||||
$chk_reorder = (string) $this->request->getPost('chk_reorder');
|
||||
|
||||
$having = '';
|
||||
if ($chk_reorder === 'on') {
|
||||
$having = ' HAVING SUM(s.total_unit - s.total_lost_unit - s.total_sale_unit - s.total_return_unit) <= p.re_order_qty';
|
||||
}
|
||||
|
||||
$where = ' s.remove_item = 0';
|
||||
$whereParams = [];
|
||||
|
||||
if ($supplier_id > 0) {
|
||||
$where .= ' AND m.sid = ?';
|
||||
$whereParams[] = $supplier_id;
|
||||
}
|
||||
|
||||
if ($item_name !== '') {
|
||||
$esc = $this->db->escapeLikeString($item_name);
|
||||
$where .= " AND p.item_name LIKE '%{$esc}%'";
|
||||
}
|
||||
|
||||
$sql = "SELECT p.id, p.item_name,
|
||||
IFNULL(s.item_code, 0) AS item_found,
|
||||
IFNULL(s.packing, p.packing) AS packing,
|
||||
SUM(s.total_unit - s.total_sale_unit - s.total_lost_unit - s.total_return_unit) AS C_Unit_Stock_Qty,
|
||||
CONCAT(
|
||||
TRUNCATE(SUM(s.total_unit - s.total_sale_unit - s.total_lost_unit - s.total_return_unit) / s.packing, 0),
|
||||
':', MOD(SUM(s.total_unit - s.total_sale_unit - s.total_lost_unit - s.total_return_unit), s.packing)
|
||||
) AS C_Pak_Qty,
|
||||
SUM(s.total_unit) AS P_Unit_Qty,
|
||||
SUM(s.total_unit) / s.packing AS Pak_qty,
|
||||
SUM(s.total_sale_unit) AS sale_unit,
|
||||
SUM(s.total_sale_unit) / s.packing AS C_Pak_Sale_Qty,
|
||||
p.re_order_qty,
|
||||
s.total_lost_unit
|
||||
FROM med_store_product_master p
|
||||
JOIN purchase_invoice_item_stock s ON p.id = s.item_code AND s.remove_item = 0 AND s.item_return = 0
|
||||
JOIN purchase_invoice_stock m ON s.purchase_id = m.id AND p.is_continue = 1
|
||||
WHERE {$where}
|
||||
GROUP BY p.id, s.packing{$having}
|
||||
ORDER BY p.item_name";
|
||||
|
||||
$data['stock_list'] = $this->db->query($sql, $whereParams)->getResult();
|
||||
|
||||
$html = view('storestock/stock_search_result', $data);
|
||||
return $this->response->setBody($html);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Get product stock detail (batch-wise)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function get_product_stock(int $product_id)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$supplier_id = (int) $this->request->getPost('input_supplier');
|
||||
$date_range = (string) $this->request->getPost('date_range');
|
||||
$product_pak = (float) $this->request->getPost('product_pak');
|
||||
|
||||
$where = '1 = 1';
|
||||
$whereParams = [];
|
||||
|
||||
if ($date_range !== '' && $date_range !== '0') {
|
||||
$rangeArray = explode('S', $date_range);
|
||||
if (count($rangeArray) === 2) {
|
||||
$where .= ' AND p.date_of_invoice BETWEEN ? AND ?';
|
||||
$whereParams[] = $rangeArray[0];
|
||||
$whereParams[] = $rangeArray[1];
|
||||
}
|
||||
}
|
||||
|
||||
if ($supplier_id > 0) {
|
||||
$where .= ' AND m.sid = ?';
|
||||
$whereParams[] = $supplier_id;
|
||||
}
|
||||
|
||||
$where .= ' AND i.item_code = ?';
|
||||
$whereParams[] = $product_id;
|
||||
|
||||
if ($product_pak > 0) {
|
||||
$where .= ' AND i.packing = ?';
|
||||
$whereParams[] = $product_pak;
|
||||
}
|
||||
|
||||
$sql = "SELECT i.id, s.name_supplier, s.short_name, i.Item_name,
|
||||
p.Invoice_no, p.date_of_invoice, i.mrp, i.packing,
|
||||
i.batch_no, i.expiry_date, i.purchase_price, i.purchase_unit_rate,
|
||||
i.qty, i.qty_free, i.tqty, i.total_unit, i.total_sale_unit,
|
||||
i.total_return_unit, i.total_lost_unit,
|
||||
(i.total_unit - i.total_sale_unit - i.total_lost_unit - i.total_return_unit) AS cur_unit
|
||||
FROM purchase_invoice_stock p
|
||||
JOIN purchase_invoice_item_stock i ON p.id = i.purchase_id AND i.item_return = 0
|
||||
JOIN stock_supplier s ON p.sid = s.sid
|
||||
WHERE {$where} AND i.remove_item = 0
|
||||
ORDER BY p.id";
|
||||
|
||||
$data['product_purchase_detail'] = $this->db->query($sql, $whereParams)->getResult();
|
||||
|
||||
$html = view('storestock/stock_item_history', $data);
|
||||
return $this->response->setBody($html);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Day Report (stub)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function report_2()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
return view('storestock/day_report');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Main Store Dashboard
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function main_store()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
return view('storestock/main_store_dashboard');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddCashSubmissionPeriodFields extends Migration
|
||||
{
|
||||
private function tableExists(string $table): bool
|
||||
{
|
||||
return $this->db->query('SHOW TABLES LIKE ' . $this->db->escape($table))->getNumRows() > 0;
|
||||
}
|
||||
|
||||
private function fieldExists(string $table, string $field): bool
|
||||
{
|
||||
return $this->db->query('SHOW COLUMNS FROM `' . $table . '` LIKE ' . $this->db->escape($field))->getNumRows() > 0;
|
||||
}
|
||||
|
||||
public function up()
|
||||
{
|
||||
$table = 'finance_scroll_submissions';
|
||||
if (! $this->tableExists($table)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->fieldExists($table, 'start_datetime')) {
|
||||
$this->forge->addColumn($table, [
|
||||
'start_datetime' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
'after' => 'scroll_date',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if (! $this->fieldExists($table, 'end_datetime')) {
|
||||
$this->forge->addColumn($table, [
|
||||
'end_datetime' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
'after' => 'start_datetime',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if (! $this->fieldExists($table, 'collected_by')) {
|
||||
$this->forge->addColumn($table, [
|
||||
'collected_by' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
'after' => 'department',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if (! $this->fieldExists($table, 'payment_count')) {
|
||||
$this->forge->addColumn($table, [
|
||||
'payment_count' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'default' => 0,
|
||||
'null' => false,
|
||||
'after' => 'submitted_amount',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$table = 'finance_scroll_submissions';
|
||||
if (! $this->tableExists($table)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->fieldExists($table, 'payment_count')) {
|
||||
$this->forge->dropColumn($table, 'payment_count');
|
||||
}
|
||||
if ($this->fieldExists($table, 'collected_by')) {
|
||||
$this->forge->dropColumn($table, 'collected_by');
|
||||
}
|
||||
if ($this->fieldExists($table, 'end_datetime')) {
|
||||
$this->forge->dropColumn($table, 'end_datetime');
|
||||
}
|
||||
if ($this->fieldExists($table, 'start_datetime')) {
|
||||
$this->forge->dropColumn($table, 'start_datetime');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class CreateCashSubmissionItemsTable extends Migration
|
||||
{
|
||||
private function tableExists(string $table): bool
|
||||
{
|
||||
return $this->db->query('SHOW TABLES LIKE ' . $this->db->escape($table))->getNumRows() > 0;
|
||||
}
|
||||
|
||||
private function fieldExists(string $table, string $field): bool
|
||||
{
|
||||
return $this->db->query('SHOW COLUMNS FROM `' . $table . '` LIKE ' . $this->db->escape($field))->getNumRows() > 0;
|
||||
}
|
||||
|
||||
public function up()
|
||||
{
|
||||
if ($this->tableExists('finance_scroll_submission_items')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'scroll_submission_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'null' => false,
|
||||
],
|
||||
'payment_history_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'null' => false,
|
||||
],
|
||||
'payment_date' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'amount' => [
|
||||
'type' => 'DECIMAL',
|
||||
'constraint' => '12,2',
|
||||
'default' => 0,
|
||||
],
|
||||
'payment_mode' => [
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 2,
|
||||
'default' => 0,
|
||||
],
|
||||
'payof_type' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'default' => 0,
|
||||
],
|
||||
'payof_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'default' => 0,
|
||||
],
|
||||
'payof_code' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
],
|
||||
'update_by_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'default' => 0,
|
||||
],
|
||||
'update_by' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
],
|
||||
'snapshot_json' => [
|
||||
'type' => 'LONGTEXT',
|
||||
'null' => true,
|
||||
],
|
||||
'created_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey('scroll_submission_id');
|
||||
$this->forge->addKey('payment_history_id');
|
||||
$this->forge->createTable('finance_scroll_submission_items', true);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
if ($this->tableExists('finance_scroll_submission_items')) {
|
||||
$this->forge->dropTable('finance_scroll_submission_items', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddCashSubmissionFieldsToPaymentHistory extends Migration
|
||||
{
|
||||
private function tableExists(string $table): bool
|
||||
{
|
||||
return $this->db->query('SHOW TABLES LIKE ' . $this->db->escape($table))->getNumRows() > 0;
|
||||
}
|
||||
|
||||
private function fieldExists(string $table, string $field): bool
|
||||
{
|
||||
return $this->db->query('SHOW COLUMNS FROM `' . $table . '` LIKE ' . $this->db->escape($field))->getNumRows() > 0;
|
||||
}
|
||||
|
||||
public function up()
|
||||
{
|
||||
if (! $this->tableExists('payment_history')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->fieldExists('payment_history', 'cash_submission_status')) {
|
||||
$this->forge->addColumn('payment_history', [
|
||||
'cash_submission_status' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 20,
|
||||
'null' => false,
|
||||
'default' => 'open',
|
||||
'after' => 'payment_mode',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if (! $this->fieldExists('payment_history', 'cash_submission_scroll_id')) {
|
||||
$this->forge->addColumn('payment_history', [
|
||||
'cash_submission_scroll_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'null' => true,
|
||||
'after' => 'cash_submission_status',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if (! $this->fieldExists('payment_history', 'cash_submission_updated_at')) {
|
||||
$this->forge->addColumn('payment_history', [
|
||||
'cash_submission_updated_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
'after' => 'cash_submission_scroll_id',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$this->db->query('UPDATE payment_history SET cash_submission_status = "open" WHERE cash_submission_status IS NULL OR cash_submission_status = ""');
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
if (! $this->tableExists('payment_history')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->fieldExists('payment_history', 'cash_submission_updated_at')) {
|
||||
$this->forge->dropColumn('payment_history', 'cash_submission_updated_at');
|
||||
}
|
||||
|
||||
if ($this->fieldExists('payment_history', 'cash_submission_scroll_id')) {
|
||||
$this->forge->dropColumn('payment_history', 'cash_submission_scroll_id');
|
||||
}
|
||||
|
||||
if ($this->fieldExists('payment_history', 'cash_submission_status')) {
|
||||
$this->forge->dropColumn('payment_history', 'cash_submission_status');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class CreateBankAuditTables extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// Table 1: Bank statement entries for direct transaction reconciliation
|
||||
// (UPI direct, NEFT, RTGS – each line visible individually in the bank statement)
|
||||
$this->forge->addField([
|
||||
'id' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true],
|
||||
'entry_date' => ['type' => 'DATE', 'null' => false],
|
||||
'reference_no' => ['type' => 'VARCHAR', 'constraint' => 100, 'null' => true],
|
||||
'narration' => ['type' => 'VARCHAR', 'constraint' => 500, 'null' => true],
|
||||
'amount' => ['type' => 'DECIMAL', 'constraint' => '12,2', 'null' => false, 'default' => '0.00'],
|
||||
'transaction_type' => ['type' => 'VARCHAR', 'constraint' => 20, 'null' => false, 'default' => 'credit'],
|
||||
'matched_payment_id' => ['type' => 'BIGINT', 'unsigned' => true, 'null' => true],
|
||||
'reconciliation_status'=> ['type' => 'VARCHAR', 'constraint' => 20, 'null' => false, 'default' => 'unmatched'],
|
||||
'matched_by' => ['type' => 'VARCHAR', 'constraint' => 100, 'null' => true],
|
||||
'matched_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'remarks' => ['type' => 'VARCHAR', 'constraint' => 500, 'null' => true],
|
||||
'created_by' => ['type' => 'VARCHAR', 'constraint' => 100, 'null' => true],
|
||||
'created_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'updated_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey('entry_date');
|
||||
$this->forge->addKey('reconciliation_status');
|
||||
$this->forge->createTable('finance_bank_statement_entries', true);
|
||||
|
||||
// Table 2: POS terminal daily settlement records
|
||||
// (POS transactions don't appear individually in bank; the end-of-day settlement total does)
|
||||
$this->forge->addField([
|
||||
'id' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true],
|
||||
'settlement_date' => ['type' => 'DATE', 'null' => false],
|
||||
'terminal_id' => ['type' => 'VARCHAR', 'constraint' => 100, 'null' => false],
|
||||
'terminal_name' => ['type' => 'VARCHAR', 'constraint' => 200, 'null' => true],
|
||||
'settlement_amount'=> ['type' => 'DECIMAL', 'constraint' => '12,2', 'null' => false, 'default' => '0.00'],
|
||||
'system_total' => ['type' => 'DECIMAL', 'constraint' => '12,2', 'null' => false, 'default' => '0.00'],
|
||||
'variance' => ['type' => 'DECIMAL', 'constraint' => '12,2', 'null' => false, 'default' => '0.00'],
|
||||
'payment_count' => ['type' => 'INT', 'null' => false, 'default' => 0],
|
||||
'status' => ['type' => 'VARCHAR', 'constraint' => 20, 'null' => false, 'default' => 'pending'],
|
||||
'reconciled_by' => ['type' => 'VARCHAR', 'constraint' => 100, 'null' => true],
|
||||
'reconciled_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'remarks' => ['type' => 'VARCHAR', 'constraint' => 500, 'null' => true],
|
||||
'created_by' => ['type' => 'VARCHAR', 'constraint' => 100, 'null' => true],
|
||||
'created_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
'updated_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey('settlement_date');
|
||||
$this->forge->addKey(['settlement_date', 'terminal_id']);
|
||||
$this->forge->createTable('finance_bank_pos_settlements', true);
|
||||
|
||||
// Add reconciliation tracking fields to payment_history
|
||||
$this->forge->addColumn('payment_history', [
|
||||
'bank_reconcile_status' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 20,
|
||||
'null' => true,
|
||||
'default' => null,
|
||||
'after' => 'cash_submission_updated_at',
|
||||
],
|
||||
'bank_statement_entry_id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'default' => null,
|
||||
'after' => 'bank_reconcile_status',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('finance_bank_statement_entries', true);
|
||||
$this->forge->dropTable('finance_bank_pos_settlements', true);
|
||||
$this->forge->dropColumn('payment_history', 'bank_reconcile_status');
|
||||
$this->forge->dropColumn('payment_history', 'bank_statement_entry_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class CreateBankReconciliationAuditTable extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'BIGINT',
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'payment_history_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'unsigned' => true,
|
||||
'null' => false,
|
||||
],
|
||||
'action_type' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 30,
|
||||
'null' => false,
|
||||
'default' => 'matched',
|
||||
],
|
||||
'old_status' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 30,
|
||||
'null' => true,
|
||||
],
|
||||
'new_status' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 30,
|
||||
'null' => true,
|
||||
],
|
||||
'batch_ref' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 100,
|
||||
'null' => true,
|
||||
],
|
||||
'remarks' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 500,
|
||||
'null' => true,
|
||||
],
|
||||
'action_by' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 100,
|
||||
'null' => true,
|
||||
],
|
||||
'action_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey('payment_history_id');
|
||||
$this->forge->addKey('action_type');
|
||||
$this->forge->addKey('batch_ref');
|
||||
$this->forge->createTable('finance_bank_reconciliation_audit', true);
|
||||
|
||||
$this->forge->addColumn('payment_history', [
|
||||
'bank_reconcile_batch_ref' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 100,
|
||||
'null' => true,
|
||||
'after' => 'bank_statement_entry_id',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('finance_bank_reconciliation_audit', true);
|
||||
$this->forge->dropColumn('payment_history', 'bank_reconcile_batch_ref');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class CreateBankSettlementEntriesTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
if (! $this->hasTable('finance_bank_settlement_entries')) {
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'settlement_ref' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 100,
|
||||
],
|
||||
'settlement_date' => [
|
||||
'type' => 'DATE',
|
||||
],
|
||||
'payment_count' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'default' => 0,
|
||||
],
|
||||
'total_amount' => [
|
||||
'type' => 'DECIMAL',
|
||||
'constraint' => '12,2',
|
||||
'default' => '0.00',
|
||||
],
|
||||
'remarks' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
],
|
||||
'created_by' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 100,
|
||||
'null' => true,
|
||||
],
|
||||
'created_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addUniqueKey('settlement_ref');
|
||||
$this->forge->addKey('settlement_date');
|
||||
$this->forge->createTable('finance_bank_settlement_entries', true);
|
||||
}
|
||||
|
||||
if ($this->hasTable('payment_history') && ! $this->hasField('payment_history', 'bank_settlement_entry_id')) {
|
||||
$this->forge->addColumn('payment_history', [
|
||||
'bank_settlement_entry_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'after' => 'bank_reconcile_batch_ref',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
if ($this->hasTable('payment_history') && $this->hasField('payment_history', 'bank_settlement_entry_id')) {
|
||||
$this->forge->dropColumn('payment_history', 'bank_settlement_entry_id');
|
||||
}
|
||||
|
||||
if ($this->hasTable('finance_bank_settlement_entries')) {
|
||||
$this->forge->dropTable('finance_bank_settlement_entries', true);
|
||||
}
|
||||
}
|
||||
|
||||
private function hasTable(string $table): bool
|
||||
{
|
||||
try {
|
||||
$result = $this->db->query('SHOW TABLES LIKE ' . $this->db->escape($table))->getResultArray();
|
||||
return ! empty($result);
|
||||
} catch (\Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function hasField(string $table, string $field): bool
|
||||
{
|
||||
if (! $this->hasTable($table)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$sql = 'SHOW COLUMNS FROM `' . str_replace('`', '``', $table) . '` LIKE ' . $this->db->escape($field);
|
||||
$result = $this->db->query($sql)->getResultArray();
|
||||
return ! empty($result);
|
||||
} catch (\Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddOpdPayoutTrackingFields extends Migration
|
||||
{
|
||||
private function hasTable(string $table): bool
|
||||
{
|
||||
$row = $this->db->query('SHOW TABLES LIKE ' . $this->db->escape($table))->getRowArray();
|
||||
|
||||
return ! empty($row);
|
||||
}
|
||||
|
||||
private function getTableFields(string $table): array
|
||||
{
|
||||
if (! $this->hasTable($table)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$rows = $this->db->query('SHOW COLUMNS FROM `' . $table . '`')->getResultArray();
|
||||
return array_values(array_filter(array_map(static function (array $row): string {
|
||||
return (string) ($row['Field'] ?? '');
|
||||
}, $rows), static fn(string $field): bool => $field !== ''));
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public function up()
|
||||
{
|
||||
if (! $this->hasTable('opd_master')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->getTableFields('opd_master');
|
||||
|
||||
if (! in_array('payout_draft_id', $fields, true)) {
|
||||
$this->forge->addColumn('opd_master', [
|
||||
'payout_draft_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'after' => 'doc_id',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if (! in_array('payout_calculated_at', $fields, true)) {
|
||||
$this->forge->addColumn('opd_master', [
|
||||
'payout_calculated_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
'after' => 'payout_draft_id',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if (! in_array('payout_calculated_by', $fields, true)) {
|
||||
$this->forge->addColumn('opd_master', [
|
||||
'payout_calculated_by' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
'after' => 'payout_calculated_at',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
if (! $this->hasTable('opd_master')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->getTableFields('opd_master');
|
||||
|
||||
if (in_array('payout_calculated_by', $fields, true)) {
|
||||
$this->forge->dropColumn('opd_master', 'payout_calculated_by');
|
||||
}
|
||||
if (in_array('payout_calculated_at', $fields, true)) {
|
||||
$this->forge->dropColumn('opd_master', 'payout_calculated_at');
|
||||
}
|
||||
if (in_array('payout_draft_id', $fields, true)) {
|
||||
$this->forge->dropColumn('opd_master', 'payout_draft_id');
|
||||
}
|
||||
}
|
||||
}
|
||||
30
app/Models/FinanceBankPosSettlementModel.php
Normal file
30
app/Models/FinanceBankPosSettlementModel.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class FinanceBankPosSettlementModel extends Model
|
||||
{
|
||||
protected $table = 'finance_bank_pos_settlements';
|
||||
protected $primaryKey = 'id';
|
||||
protected $returnType = 'array';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $allowedFields = [
|
||||
'settlement_date',
|
||||
'terminal_id',
|
||||
'terminal_name',
|
||||
'settlement_amount',
|
||||
'system_total',
|
||||
'variance',
|
||||
'payment_count',
|
||||
'status',
|
||||
'reconciled_by',
|
||||
'reconciled_at',
|
||||
'remarks',
|
||||
'created_by',
|
||||
];
|
||||
protected $useTimestamps = true;
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
}
|
||||
24
app/Models/FinanceBankReconciliationAuditModel.php
Normal file
24
app/Models/FinanceBankReconciliationAuditModel.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class FinanceBankReconciliationAuditModel extends Model
|
||||
{
|
||||
protected $table = 'finance_bank_reconciliation_audit';
|
||||
protected $primaryKey = 'id';
|
||||
protected $returnType = 'array';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $allowedFields = [
|
||||
'payment_history_id',
|
||||
'action_type',
|
||||
'old_status',
|
||||
'new_status',
|
||||
'batch_ref',
|
||||
'remarks',
|
||||
'action_by',
|
||||
'action_at',
|
||||
];
|
||||
protected $useTimestamps = false;
|
||||
}
|
||||
29
app/Models/FinanceBankStatementEntryModel.php
Normal file
29
app/Models/FinanceBankStatementEntryModel.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class FinanceBankStatementEntryModel extends Model
|
||||
{
|
||||
protected $table = 'finance_bank_statement_entries';
|
||||
protected $primaryKey = 'id';
|
||||
protected $returnType = 'array';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $allowedFields = [
|
||||
'entry_date',
|
||||
'reference_no',
|
||||
'narration',
|
||||
'amount',
|
||||
'transaction_type',
|
||||
'matched_payment_id',
|
||||
'reconciliation_status',
|
||||
'matched_by',
|
||||
'matched_at',
|
||||
'remarks',
|
||||
'created_by',
|
||||
];
|
||||
protected $useTimestamps = true;
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
}
|
||||
29
app/Models/FinanceScrollSubmissionItemModel.php
Normal file
29
app/Models/FinanceScrollSubmissionItemModel.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class FinanceScrollSubmissionItemModel extends Model
|
||||
{
|
||||
protected $table = 'finance_scroll_submission_items';
|
||||
protected $primaryKey = 'id';
|
||||
protected $returnType = 'array';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $allowedFields = [
|
||||
'scroll_submission_id',
|
||||
'payment_history_id',
|
||||
'payment_date',
|
||||
'amount',
|
||||
'payment_mode',
|
||||
'payof_type',
|
||||
'payof_id',
|
||||
'payof_code',
|
||||
'update_by_id',
|
||||
'update_by',
|
||||
'snapshot_json',
|
||||
];
|
||||
protected $useTimestamps = true;
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = '';
|
||||
}
|
||||
|
|
@ -12,7 +12,11 @@ class FinanceScrollSubmissionModel extends Model
|
|||
protected $useAutoIncrement = true;
|
||||
protected $allowedFields = [
|
||||
'scroll_date',
|
||||
'start_datetime',
|
||||
'end_datetime',
|
||||
'department',
|
||||
'collected_by',
|
||||
'payment_count',
|
||||
'total_receipts',
|
||||
'submitted_amount',
|
||||
'variance_amount',
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@
|
|||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/hospital-stock') ?>','maindiv','Hospital Stock Management');">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('Storestock') ?>','maindiv','Hospital Stock');">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<span>Hospital Stock</span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -10,105 +10,123 @@
|
|||
|
||||
<section class="section profile">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title mb-0">OPD History</h3>
|
||||
<div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<div>
|
||||
<h3 class="card-title mb-0">Old Prescription With Scanned Records</h3>
|
||||
<div class="small text-muted mt-1">Patient: <?= esc($patient->p_fname ?? '') ?></div>
|
||||
</div>
|
||||
<span class="badge bg-secondary"><?= count($opdGroups ?? []) ?> OPD Record(s)</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="mb-3">Patient: <strong><?= esc($patient->p_fname ?? '') ?></strong></p>
|
||||
|
||||
<?php if (empty($opdGroups)) { ?>
|
||||
<div class="alert alert-info mb-0">No OPD scans found.</div>
|
||||
<div class="alert alert-info mb-0">No OPD history found.</div>
|
||||
<?php } else { ?>
|
||||
<?php foreach ($opdGroups as $group) { ?>
|
||||
<div class="border rounded p-3 mb-3">
|
||||
<div class="d-flex flex-wrap justify-content-between">
|
||||
<article class="border rounded p-3 mb-4 bg-white shadow-sm">
|
||||
<div class="d-flex justify-content-between align-items-start flex-wrap gap-2 mb-2">
|
||||
<div>
|
||||
<strong><?= esc($group['opd_code']) ?></strong>
|
||||
<span class="text-muted ms-2">Dr. <?= esc($group['doc_name']) ?></span>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
<?= esc($group['opd_date']) ?> <?= esc($group['queue_no']) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<?php if ((int) ($group['rx_session_id'] ?? 0) > 0) { ?>
|
||||
<a class="btn btn-primary btn-sm" target="_blank"
|
||||
href="<?= base_url('Opd_prescription/opd_prescription_print/' . (int) $group['opd_id'] . '/' . (int) $group['rx_session_id']) ?>">
|
||||
Prescription Print
|
||||
</a>
|
||||
<?php } ?>
|
||||
|
||||
<?php if (!empty($group['bp']) || !empty($group['diastolic']) || !empty($group['pulse']) || !empty($group['temp']) || !empty($group['spo2'])) { ?>
|
||||
<div class="small text-muted mt-2">
|
||||
<?php if (!empty($group['bp'])) { ?><span class="me-3"><strong>BP:</strong> <?= esc($group['bp']) ?></span><?php } ?>
|
||||
<?php if (!empty($group['diastolic'])) { ?><span class="me-3"><strong>Diastolic:</strong> <?= esc($group['diastolic']) ?></span><?php } ?>
|
||||
<?php if (!empty($group['pulse'])) { ?><span class="me-3"><strong>Pulse:</strong> <?= esc($group['pulse']) ?></span><?php } ?>
|
||||
<?php if (!empty($group['temp'])) { ?><span class="me-3"><strong>Temp:</strong> <?= esc($group['temp']) ?></span><?php } ?>
|
||||
<?php if (!empty($group['spo2'])) { ?><span class="me-3"><strong>SPO2:</strong> <?= esc($group['spo2']) ?></span><?php } ?>
|
||||
<div class="fw-semibold">Dr. <?= esc($group['doc_name'] ?? '') ?></div>
|
||||
<div class="small text-muted">
|
||||
<strong>OPD ID:</strong> <?= esc($group['opd_code'] ?? '') ?>
|
||||
<span class="ms-2"><strong>Date:</strong> <?= esc($group['opd_date'] ?? '') ?></span>
|
||||
<?php if (!empty($group['queue_no'])) { ?><span class="ms-2"><strong><?= esc($group['queue_no']) ?></strong></span><?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php if (!empty($group['complaints']) || !empty($group['diagnosis']) || !empty($group['investigation']) || !empty($group['advice'])) { ?>
|
||||
<div class="row g-2 mt-2">
|
||||
<?php if (!empty($group['complaints'])) { ?>
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="border rounded p-2 bg-light small">
|
||||
<strong>Complaints:</strong> <?= esc($group['complaints']) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php if (!empty($group['diagnosis'])) { ?>
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="border rounded p-2 bg-light small">
|
||||
<strong>Diagnosis:</strong> <?= esc($group['diagnosis']) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php if (!empty($group['investigation'])) { ?>
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="border rounded p-2 bg-light small">
|
||||
<strong>Investigation:</strong> <?= esc($group['investigation']) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php if (!empty($group['advice'])) { ?>
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="border rounded p-2 bg-light small">
|
||||
<strong>Advice:</strong> <?= esc($group['advice']) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<?php if (empty($group['files'])) { ?>
|
||||
<div class="text-muted mt-2">No scanned files for this OPD.</div>
|
||||
<?php } else { ?>
|
||||
<div class="small fw-semibold mt-3 mb-1">Scanned Files</div>
|
||||
<div class="row g-2 mt-2">
|
||||
<?php foreach ($group['files'] as $index => $file) { ?>
|
||||
<div class="col-6 col-md-3">
|
||||
<?php if ($file['isPdf']) { ?>
|
||||
<a class="btn btn-outline-secondary btn-sm w-100" href="<?= esc($file['path']) ?>" target="_blank">Open PDF</a>
|
||||
<?php } else { ?>
|
||||
<img src="<?= esc($file['path']) ?>" class="img-fluid rounded border opd-thumb"
|
||||
data-bs-toggle="modal" data-bs-target="#opdScanModal"
|
||||
data-src="<?= esc($file['path']) ?>" alt="OPD Scan">
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<?php if ((int) ($group['rx_session_id'] ?? 0) > 0) { ?>
|
||||
<a class="btn btn-outline-primary btn-sm" target="_blank"
|
||||
href="<?= base_url('Opd_prescription/opd_prescription_print/' . (int) $group['opd_id'] . '/' . (int) $group['rx_session_id']) ?>">
|
||||
Prescription Print
|
||||
</a>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($group['bp']) || !empty($group['diastolic']) || !empty($group['pulse']) || !empty($group['temp']) || !empty($group['spo2'])) { ?>
|
||||
<div class="small text-dark mb-2">
|
||||
<?php if (!empty($group['bp'])) { ?><span class="me-3"><strong>BP:</strong> <?= esc($group['bp']) ?></span><?php } ?>
|
||||
<?php if (!empty($group['diastolic'])) { ?><span class="me-3"><strong>Diastolic:</strong> <?= esc($group['diastolic']) ?></span><?php } ?>
|
||||
<?php if (!empty($group['pulse'])) { ?><span class="me-3"><strong>Pulse:</strong> <?= esc($group['pulse']) ?></span><?php } ?>
|
||||
<?php if (!empty($group['temp'])) { ?><span class="me-3"><strong>Temp:</strong> <?= esc($group['temp']) ?></span><?php } ?>
|
||||
<?php if (!empty($group['spo2'])) { ?><span class="me-3"><strong>SPO2:</strong> <?= esc($group['spo2']) ?></span><?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($group['complaints']) || !empty($group['diagnosis']) || !empty($group['investigation']) || !empty($group['advice']) || !empty($group['next_visit']) || !empty($group['refer_to'])) { ?>
|
||||
<div class="small text-dark mb-3">
|
||||
<?php if (!empty($group['complaints'])) { ?><div><strong>Complaint:</strong> <?= esc($group['complaints']) ?></div><?php } ?>
|
||||
<?php if (!empty($group['diagnosis'])) { ?><div><strong>Diagnosis:</strong> <?= esc($group['diagnosis']) ?></div><?php } ?>
|
||||
<?php if (!empty($group['investigation'])) { ?><div><strong>Investigation Advised:</strong> <?= esc($group['investigation']) ?></div><?php } ?>
|
||||
<?php if (!empty($group['advice'])) { ?><div><strong>Advice:</strong> <?= esc($group['advice']) ?></div><?php } ?>
|
||||
<?php if (!empty($group['next_visit'])) { ?><div><strong>Next Visit:</strong> <?= esc($group['next_visit']) ?></div><?php } ?>
|
||||
<?php if (!empty($group['refer_to'])) { ?><div><strong>Refer To:</strong> <?= esc($group['refer_to']) ?></div><?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php if (!empty($group['medicines'])) { ?>
|
||||
<div class="table-responsive mb-3">
|
||||
<table class="table table-sm table-bordered align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Prescribed</th>
|
||||
<th>Dose</th>
|
||||
<th>Timing - Freq. - Route - Duration</th>
|
||||
<th>Qty</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($group['medicines'] as $medicine) { ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?= esc(trim(($medicine['med_type'] ?? '') . ' ' . ($medicine['med_name'] ?? ''))) ?>
|
||||
<?php if (!empty($medicine['remark'])) { ?>
|
||||
<div class="small text-muted mt-1"><?= esc($medicine['remark']) ?></div>
|
||||
<?php } ?>
|
||||
</td>
|
||||
<td><?= esc($medicine['dose'] ?? '') ?></td>
|
||||
<td>
|
||||
<?= esc(implode(' - ', array_values(array_filter([
|
||||
$medicine['timing'] ?? '',
|
||||
$medicine['frequency'] ?? '',
|
||||
$medicine['where'] ?? '',
|
||||
$medicine['days'] ?? '',
|
||||
], static fn($value): bool => trim((string) $value) !== '')))) ?>
|
||||
</td>
|
||||
<td><?= esc($medicine['qty'] ?? '') ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php if (empty($group['files'])) { ?>
|
||||
<div class="text-muted">No scanned files for this OPD.</div>
|
||||
<?php } else { ?>
|
||||
<div class="fw-semibold mb-2">Scanned Files</div>
|
||||
<?php foreach ($group['files'] as $file) { ?>
|
||||
<div class="mb-3">
|
||||
<?php if ($file['isPdf']) { ?>
|
||||
<div class="d-flex gap-2 flex-wrap mb-2">
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= esc($file['path']) ?>" target="_blank">Open PDF</a>
|
||||
</div>
|
||||
<embed src="<?= esc($file['path']) ?>" type="application/pdf" width="100%" height="900px" class="border rounded">
|
||||
<?php } else { ?>
|
||||
<img src="<?= esc($file['path']) ?>" class="img-fluid rounded border opd-thumb"
|
||||
data-bs-toggle="modal" data-bs-target="#opdScanModal"
|
||||
data-src="<?= esc($file['path']) ?>" alt="OPD Scan">
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</article>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="opdScanModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">OPD Scan</h5>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<section class="content finance-bank-deposit">
|
||||
<div class="mb-3">
|
||||
<h2 class="mb-1">Bank Deposit Register</h2>
|
||||
<p class="text-muted mb-0">Track cash to bank deposits and reconcile with submitted scrolls.</p>
|
||||
<h2 class="mb-1">Bank Transaction Audit And Statement Update</h2>
|
||||
<p class="text-muted mb-0">Track deposits, audit bank transactions, and mark entries updated in bank statement.</p>
|
||||
</div>
|
||||
|
||||
<div id="deposit_alert"></div>
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
<div class="col-md-3"><input type="text" class="form-control" name="department" placeholder="Department" required></div>
|
||||
<div class="col-md-3"><input type="text" class="form-control" name="bank_name" placeholder="Bank Name" required></div>
|
||||
<div class="col-md-3"><input type="text" class="form-control" name="slip_no" placeholder="Slip No"></div>
|
||||
<div class="col-md-3"><input type="number" $11" class="form-control" name="deposited_amount" placeholder="Deposited Amount" required></div>
|
||||
<div class="col-md-3"><input type="number" min="0" step="0.01" class="form-control" name="deposited_amount" placeholder="Deposited Amount" required></div>
|
||||
<div class="col-md-3">
|
||||
<select class="form-select" name="related_scroll_id">
|
||||
<option value="">Related Scroll (optional)</option>
|
||||
|
|
@ -51,6 +51,28 @@
|
|||
load_form_div('<?= base_url('Finance/bank_deposits_table') ?>', 'bank_deposit_table_wrap');
|
||||
}
|
||||
|
||||
window.financeDepositStatusAction = function(depositId, action) {
|
||||
var fd = new window.FormData();
|
||||
fd.append('deposit_id', String(depositId));
|
||||
fd.append('action', action);
|
||||
|
||||
fetch('<?= base_url('Finance/bank_deposit_status_update') ?>', {
|
||||
method: 'POST',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
body: fd
|
||||
})
|
||||
.then(function(res) { return res.json().then(function(data) { return { ok: res.ok, data: data }; }); })
|
||||
.then(function(result) {
|
||||
if (!result.ok || !result.data || result.data.status !== 1) {
|
||||
showAlert((result.data && result.data.message) ? result.data.message : 'Request failed', false);
|
||||
return;
|
||||
}
|
||||
showAlert(result.data.message || 'Updated successfully', true);
|
||||
refreshTable();
|
||||
})
|
||||
.catch(function() { showAlert('Network or server error.', false); });
|
||||
};
|
||||
|
||||
var form = document.getElementById('bank_deposit_form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
|
|
|
|||
|
|
@ -1,88 +1,127 @@
|
|||
<section class="content finance-cashbook">
|
||||
<?php
|
||||
$queue = $queue_summary ?? ['all' => 0, 'pending' => 0, 'received' => 0, 'deposited' => 0];
|
||||
$collectorOptions = $collector_options ?? [];
|
||||
$showBillingPanel = isset($show_billing_panel) ? (bool) $show_billing_panel : true;
|
||||
$showAccountsPanel = isset($show_accounts_panel) ? (bool) $show_accounts_panel : true;
|
||||
|
||||
if (! $showBillingPanel && ! $showAccountsPanel) {
|
||||
$showAccountsPanel = true;
|
||||
}
|
||||
|
||||
$pageTitle = 'Cash Submission';
|
||||
$pageSubtitle = 'Select patient payment rows from Payment History, submit exact totals to Accounts, and verify with full payment-level audit.';
|
||||
if ($showBillingPanel && ! $showAccountsPanel) {
|
||||
$pageTitle = 'Billing Cash Submission';
|
||||
$pageSubtitle = 'Select cash payments and submit to Accounts Department.';
|
||||
} elseif (! $showBillingPanel && $showAccountsPanel) {
|
||||
$pageTitle = 'Accounts Cash Submission Verification';
|
||||
$pageSubtitle = 'Accept submitted cash statements and mark them deposited after verification.';
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="mb-3">
|
||||
<h2 class="mb-1">Cash Collection & Disbursement SOP</h2>
|
||||
<p class="text-muted mb-0">Capture receipts/disbursements, scroll submission, and compliance checks for Section 269ST and 40A(3).</p>
|
||||
<h2 class="mb-1"><?= esc($pageTitle) ?></h2>
|
||||
<p class="text-muted mb-0"><?= esc($pageSubtitle) ?></p>
|
||||
</div>
|
||||
|
||||
<div id="cash_alert"></div>
|
||||
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="card border-primary"><div class="card-body py-2"><div class="small text-muted">Today Receipts</div><div class="h5 mb-0 text-primary"><?= number_format((float) ($summary['today_receipts'] ?? 0), 2) ?></div></div></div>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="card border-info"><div class="card-body py-2"><div class="small text-muted">Today Disbursements</div><div class="h5 mb-0 text-info"><?= number_format((float) ($summary['today_disbursements'] ?? 0), 2) ?></div></div></div>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="card border-danger"><div class="card-body py-2"><div class="small text-muted">Compliance Hold</div><div class="h5 mb-0 text-danger"><?= (int) ($summary['hold_count'] ?? 0) ?></div></div></div>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="card border-dark"><div class="card-body py-2"><div class="small text-muted">Pending Scrolls</div><div class="h5 mb-0"><?= (int) ($summary['pending_scroll'] ?? 0) ?></div></div></div>
|
||||
</div>
|
||||
<div class="col-md-3 col-6"><div class="card border-secondary"><div class="card-body py-2"><div class="small text-muted">All</div><div class="h5 mb-0"><?= (int) ($queue['all'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-md-3 col-6"><div class="card border-warning"><div class="card-body py-2"><div class="small text-muted">Pending</div><div class="h5 mb-0 text-warning"><?= (int) ($queue['pending'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-md-3 col-6"><div class="card border-primary"><div class="card-body py-2"><div class="small text-muted">Received</div><div class="h5 mb-0 text-primary"><?= (int) ($queue['received'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-md-3 col-6"><div class="card border-success"><div class="card-body py-2"><div class="small text-muted">Deposited</div><div class="h5 mb-0 text-success"><?= (int) ($queue['deposited'] ?? 0) ?></div></div></div></div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-xl-6">
|
||||
<?php if ($showBillingPanel): ?>
|
||||
<div class="<?= $showAccountsPanel ? 'col-xl-7' : 'col-12' ?>">
|
||||
<div class="card h-100">
|
||||
<div class="card-header"><strong>1) Cash Entry</strong></div>
|
||||
<div class="card-header"><strong>1) Select Payments For Submission</strong></div>
|
||||
<div class="card-body">
|
||||
<form id="cash_txn_form" class="row g-2">
|
||||
<div class="col-md-4"><input type="date" name="txn_date" class="form-control" value="<?= date('Y-m-d') ?>" required></div>
|
||||
<form id="scroll_filter_form" class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Start Date & Time</label>
|
||||
<input type="datetime-local" name="start_datetime" class="form-control" value="<?= date('Y-m-d\\T00:00') ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">End Date & Time</label>
|
||||
<input type="datetime-local" name="end_datetime" class="form-control" value="<?= date('Y-m-d\\TH:i') ?>" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<select class="form-select" name="txn_type" required>
|
||||
<option value="receipt">Receipt</option>
|
||||
<option value="disbursement">Disbursement</option>
|
||||
<label class="form-label mb-1">Collected By</label>
|
||||
<select class="form-select" name="collected_by">
|
||||
<option value="">All Users</option>
|
||||
<?php foreach ($collectorOptions as $opt): ?>
|
||||
<?php $oid = (int) ($opt['user_id'] ?? 0); ?>
|
||||
<?php $oname = trim((string) ($opt['user_name'] ?? '')); ?>
|
||||
<?php if ($oname !== ''): ?>
|
||||
<option value="<?= $oid > 0 ? $oid : esc($oname) ?>"><?= esc($oname) ?></option>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<select class="form-select" name="flow_type" required>
|
||||
<option value="patient">Patient</option>
|
||||
<option value="pharmacy">Pharmacy</option>
|
||||
<option value="vendor">Vendor</option>
|
||||
<option value="salary">Salary</option>
|
||||
<option value="reimbursement">Reimbursement</option>
|
||||
<option value="doctor_payout">Doctor Payout</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
<label class="form-label mb-1">Payment Mode</label>
|
||||
<input type="text" class="form-control" value="Cash" readonly>
|
||||
<input type="hidden" name="payment_mode" value="cash">
|
||||
</div>
|
||||
<div class="col-md-4"><input type="text" class="form-control" name="department" placeholder="Department (OPD/IPD/Pharmacy)"></div>
|
||||
<div class="col-md-4"><input type="text" class="form-control" name="reference_no" placeholder="Reference No"></div>
|
||||
<div class="col-md-4"><input type="number" $11" class="form-control" name="amount" placeholder="Amount" required></div>
|
||||
<div class="col-md-4">
|
||||
<select class="form-select" name="mode" required>
|
||||
<option value="cash">Cash</option>
|
||||
<option value="bank">Bank</option>
|
||||
<option value="upi">UPI</option>
|
||||
<option value="card">Card</option>
|
||||
</select>
|
||||
<label class="form-label mb-1">Department</label>
|
||||
<input type="text" class="form-control" name="department" value="Billing" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="button" id="load_payments_btn" class="btn btn-outline-secondary btn-sm">Load Payments</button>
|
||||
</div>
|
||||
<div class="col-md-8"><input type="text" class="form-control" name="party_name" placeholder="Party / Patient / Vendor"></div>
|
||||
<div class="col-12"><textarea class="form-control" name="narration" rows="2" placeholder="Narration"></textarea></div>
|
||||
<div class="col-12"><button type="submit" class="btn btn-primary btn-sm">Save Cash Entry</button></div>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
<div id="cash_txn_table_wrap"><?= view('finance/partials/cash_transactions_table', ['transactions' => $transactions ?? []]) ?></div>
|
||||
|
||||
<form id="scroll_form" class="row g-2">
|
||||
<input type="hidden" name="start_datetime" value="">
|
||||
<input type="hidden" name="end_datetime" value="">
|
||||
<input type="hidden" name="collected_by" value="">
|
||||
<input type="hidden" name="payment_mode" value="cash">
|
||||
<input type="hidden" name="department" value="Billing">
|
||||
|
||||
<div id="payment_selection_wrap"><?= view('finance/partials/payment_selection_table', ['rows' => [], 'totals' => ['payment_count' => 0, 'total_receipts' => 0]]) ?></div>
|
||||
|
||||
<div class="col-md-6"><input type="text" name="submitted_by" class="form-control" placeholder="Submitted By"></div>
|
||||
<div class="col-md-6"><input type="text" name="remarks" class="form-control" placeholder="Remarks"></div>
|
||||
<div class="col-md-6"><input type="number" min="0" step="0.01" name="submitted_amount" class="form-control" placeholder="Calculated Total" readonly></div>
|
||||
<div class="col-md-6"><input type="text" id="selection_info" class="form-control" value="Selected payments: 0" readonly></div>
|
||||
<div class="col-12"><button type="submit" class="btn btn-primary btn-sm">Submit Selected Payments To Accounts</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="col-xl-6">
|
||||
<?php if ($showAccountsPanel): ?>
|
||||
<div class="<?= $showBillingPanel ? 'col-xl-5' : 'col-12' ?>">
|
||||
<div class="card h-100">
|
||||
<div class="card-header"><strong>2) Scroll Submission & Reconciliation</strong></div>
|
||||
<div class="card-header"><strong>2) Accounts Acceptance And Verification Queue</strong></div>
|
||||
<div class="card-body">
|
||||
<form id="scroll_form" class="row g-2">
|
||||
<div class="col-md-4"><input type="date" name="scroll_date" class="form-control" value="<?= date('Y-m-d') ?>" required></div>
|
||||
<div class="col-md-4"><input type="text" name="department" class="form-control" placeholder="Department" required></div>
|
||||
<div class="col-md-4"><input type="number" $11" name="submitted_amount" class="form-control" placeholder="Submitted Amount" required></div>
|
||||
<div class="col-md-6"><input type="text" name="submitted_by" class="form-control" placeholder="Submitted By"></div>
|
||||
<div class="col-md-6"><input type="text" name="remarks" class="form-control" placeholder="Remarks"></div>
|
||||
<div class="col-12"><button type="submit" class="btn btn-primary btn-sm">Submit Scroll</button></div>
|
||||
</form>
|
||||
<hr>
|
||||
<div id="scroll_table_wrap"><?= view('finance/partials/scroll_table', ['scrolls' => $scrolls ?? []]) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="scrollItemsModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Submitted Payment Details</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="scroll_items_body">
|
||||
<div class="text-muted">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -100,27 +139,214 @@
|
|||
}
|
||||
}
|
||||
|
||||
function refreshTables() {
|
||||
load_form_div('<?= base_url('Finance/cash_transactions_table') ?>', 'cash_txn_table_wrap');
|
||||
load_form_div('<?= base_url('Finance/scroll_table') ?>', 'scroll_table_wrap');
|
||||
function refreshScrollTable() {
|
||||
if (document.getElementById('scroll_table_wrap')) {
|
||||
load_form_div('<?= base_url('Finance/scroll_table') ?>', 'scroll_table_wrap');
|
||||
}
|
||||
}
|
||||
|
||||
function wireForm(formId, endpoint) {
|
||||
var form = document.getElementById(formId);
|
||||
function copyFiltersToSubmitForm() {
|
||||
var filterForm = document.getElementById('scroll_filter_form');
|
||||
var submitForm = document.getElementById('scroll_form');
|
||||
if (!filterForm || !submitForm) {
|
||||
return;
|
||||
}
|
||||
|
||||
['start_datetime', 'end_datetime', 'collected_by', 'payment_mode', 'department'].forEach(function(name) {
|
||||
var src = filterForm.querySelector('[name="' + name + '"]');
|
||||
var dst = submitForm.querySelector('[name="' + name + '"]');
|
||||
if (src && dst) {
|
||||
dst.value = src.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateSelectionSummary() {
|
||||
var wrap = document.getElementById('payment_selection_wrap');
|
||||
var selected = 0;
|
||||
var total = 0;
|
||||
|
||||
if (wrap) {
|
||||
wrap.querySelectorAll('.payment-checkbox:checked').forEach(function(chk) {
|
||||
selected++;
|
||||
var row = chk.closest('tr');
|
||||
if (row) {
|
||||
var amountCell = row.querySelector('.payment-amount');
|
||||
if (amountCell) {
|
||||
total += Number(amountCell.getAttribute('data-amount') || '0');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var amountInput = document.querySelector('#scroll_form [name="submitted_amount"]');
|
||||
if (amountInput) {
|
||||
amountInput.value = total.toFixed(2);
|
||||
}
|
||||
|
||||
var info = document.getElementById('selection_info');
|
||||
if (info) {
|
||||
info.value = 'Selected payments: ' + String(selected);
|
||||
}
|
||||
}
|
||||
|
||||
function wirePaymentSelectionEvents() {
|
||||
var wrap = document.getElementById('payment_selection_wrap');
|
||||
if (!wrap) {
|
||||
return;
|
||||
}
|
||||
|
||||
var selectAll = wrap.querySelector('#payment_select_all');
|
||||
if (selectAll) {
|
||||
selectAll.addEventListener('change', function() {
|
||||
var checked = !!selectAll.checked;
|
||||
wrap.querySelectorAll('.payment-checkbox').forEach(function(chk) {
|
||||
chk.checked = checked;
|
||||
});
|
||||
updateSelectionSummary();
|
||||
});
|
||||
}
|
||||
|
||||
wrap.querySelectorAll('.payment-checkbox').forEach(function(chk) {
|
||||
chk.addEventListener('change', updateSelectionSummary);
|
||||
});
|
||||
|
||||
updateSelectionSummary();
|
||||
}
|
||||
|
||||
function loadPayments() {
|
||||
var form = document.getElementById('scroll_filter_form');
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
var formData = new window.FormData(form);
|
||||
copyFiltersToSubmitForm();
|
||||
|
||||
fetch(endpoint, {
|
||||
var params = new window.URLSearchParams(new window.FormData(form));
|
||||
fetch('<?= base_url('Finance/payment_selection_table') ?>?' + params.toString(), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(function(res) {
|
||||
return res.text().then(function(html) {
|
||||
return { ok: res.ok, html: html };
|
||||
});
|
||||
})
|
||||
.then(function(result) {
|
||||
if (!result.ok) {
|
||||
showAlert('Unable to load payment list.', false);
|
||||
return;
|
||||
}
|
||||
|
||||
var wrap = document.getElementById('payment_selection_wrap');
|
||||
if (wrap) {
|
||||
wrap.innerHTML = result.html;
|
||||
}
|
||||
wirePaymentSelectionEvents();
|
||||
showAlert('Payments loaded. Select rows to submit.', true);
|
||||
})
|
||||
.catch(function() {
|
||||
showAlert('Network or server error while loading payments.', false);
|
||||
});
|
||||
}
|
||||
|
||||
window.financeScrollAction = function(scrollId, action) {
|
||||
var endpoint = action === 'accept'
|
||||
? '<?= base_url('Finance/scroll_accept') ?>'
|
||||
: '<?= base_url('Finance/scroll_verify') ?>';
|
||||
|
||||
var fd = new window.FormData();
|
||||
fd.append('scroll_id', String(scrollId));
|
||||
|
||||
fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: fd
|
||||
})
|
||||
.then(function(res) {
|
||||
return res.json().then(function(data) {
|
||||
return { ok: res.ok, data: data };
|
||||
});
|
||||
})
|
||||
.then(function(result) {
|
||||
if (!result.ok || !result.data || result.data.status !== 1) {
|
||||
showAlert((result.data && result.data.message) ? result.data.message : 'Action failed', false);
|
||||
return;
|
||||
}
|
||||
|
||||
showAlert(result.data.message || 'Updated successfully', true);
|
||||
refreshScrollTable();
|
||||
})
|
||||
.catch(function() {
|
||||
showAlert('Network or server error.', false);
|
||||
});
|
||||
};
|
||||
|
||||
window.financeViewScrollItems = function(scrollId) {
|
||||
var target = document.getElementById('scroll_items_body');
|
||||
if (target) {
|
||||
target.innerHTML = '<div class="text-muted">Loading...</div>';
|
||||
}
|
||||
|
||||
fetch('<?= base_url('Finance/scroll_items_table') ?>?scroll_id=' + encodeURIComponent(String(scrollId)), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(function(res) {
|
||||
return res.text().then(function(html) {
|
||||
return { ok: res.ok, html: html };
|
||||
});
|
||||
})
|
||||
.then(function(result) {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
target.innerHTML = result.ok ? result.html : '<div class="text-danger">Unable to load submission items.</div>';
|
||||
})
|
||||
.catch(function() {
|
||||
if (target) {
|
||||
target.innerHTML = '<div class="text-danger">Network or server error.</div>';
|
||||
}
|
||||
});
|
||||
|
||||
var modalEl = document.getElementById('scrollItemsModal');
|
||||
if (modalEl && window.bootstrap && window.bootstrap.Modal) {
|
||||
var modal = window.bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
modal.show();
|
||||
}
|
||||
};
|
||||
|
||||
var loadBtn = document.getElementById('load_payments_btn');
|
||||
if (loadBtn) {
|
||||
loadBtn.addEventListener('click', loadPayments);
|
||||
}
|
||||
|
||||
var submitForm = document.getElementById('scroll_form');
|
||||
if (submitForm) {
|
||||
submitForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
copyFiltersToSubmitForm();
|
||||
|
||||
var payload = new window.FormData(submitForm);
|
||||
var wrap = document.getElementById('payment_selection_wrap');
|
||||
if (wrap) {
|
||||
wrap.querySelectorAll('.payment-checkbox:checked').forEach(function(chk) {
|
||||
payload.append('payment_ids[]', chk.value);
|
||||
});
|
||||
}
|
||||
|
||||
fetch('<?= base_url('Finance/scroll_create') ?>', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: formData
|
||||
body: payload
|
||||
})
|
||||
.then(function(res) {
|
||||
return res.json().then(function(data) {
|
||||
|
|
@ -133,9 +359,9 @@
|
|||
return;
|
||||
}
|
||||
|
||||
showAlert(result.data.message || 'Saved successfully', true);
|
||||
form.reset();
|
||||
refreshTables();
|
||||
showAlert(result.data.message || 'Submission sent.', true);
|
||||
refreshScrollTable();
|
||||
loadPayments();
|
||||
})
|
||||
.catch(function() {
|
||||
showAlert('Network or server error.', false);
|
||||
|
|
@ -143,7 +369,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
wireForm('cash_txn_form', '<?= base_url('Finance/cash_transaction_create') ?>');
|
||||
wireForm('scroll_form', '<?= base_url('Finance/scroll_create') ?>');
|
||||
wirePaymentSelectionEvents();
|
||||
})();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,123 +1,53 @@
|
|||
<section class="content finance-sop">
|
||||
<div class="finance-head mb-3">
|
||||
<h2 class="mb-1">Finance & Accounting - Phase 1</h2>
|
||||
<p class="mb-0 text-muted">Vendor onboarding, PO booking, GRN capture, and invoice registration.</p>
|
||||
<div class="mt-2 d-flex flex-wrap gap-2">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm js-finance-launch" data-url="<?= base_url('Finance/section/vendor_master') ?>" data-title="Vendor Master">
|
||||
<i class="bi bi-building"></i> Vendor Master
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm js-finance-launch" data-url="<?= base_url('Finance/section/purchase_order') ?>" data-title="Purchase Order">
|
||||
<i class="bi bi-card-checklist"></i> Purchase Order
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm js-finance-launch" data-url="<?= base_url('Finance/section/grn_entry') ?>" data-title="GRN Entry">
|
||||
<i class="bi bi-box-seam"></i> GRN Entry
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm js-finance-launch" data-url="<?= base_url('Finance/section/vendor_invoice') ?>" data-title="Vendor Invoice">
|
||||
<i class="bi bi-receipt"></i> Vendor Invoice
|
||||
</button>
|
||||
<a class="btn btn-primary btn-sm" href="javascript:load_form('<?= base_url('Finance/phase2') ?>','Finance & Accounting - Phase 2');">
|
||||
Open Finance & Accounting - Phase 2
|
||||
</a>
|
||||
<a class="btn btn-outline-primary btn-sm" href="javascript:load_form('<?= base_url('Finance/cashbook') ?>','Cash Collection & Disbursement SOP');">
|
||||
Open Cash Collection & Disbursement SOP
|
||||
</a>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="javascript:load_form('<?= base_url('Finance/doctor_payout') ?>','Doctor Payout Workflow');">
|
||||
Open Doctor Payout Workflow
|
||||
</a>
|
||||
<a class="btn btn-outline-success btn-sm" href="javascript:load_form('<?= base_url('Finance/bank_deposits') ?>','Bank Deposit Register');">
|
||||
Open Bank Deposit Register
|
||||
</a>
|
||||
<a class="btn btn-outline-dark btn-sm" href="javascript:load_form('<?= base_url('Finance/compliance_report') ?>','Finance Compliance Report');">
|
||||
Open Finance Compliance Report
|
||||
</a>
|
||||
</div>
|
||||
<?php $user = function_exists('auth') ? auth()->user() : null; ?>
|
||||
<?php $canBillingSubmit = $user && method_exists($user, 'can') ? ($user->can('finance.cash.billing.submit') || $user->can('finance.*')) : false; ?>
|
||||
<?php $canAccountsVerify = $user && method_exists($user, 'can') ? ($user->can('finance.cash.accounts.accept') || $user->can('finance.cash.accounts.verify') || $user->can('finance.*')) : false; ?>
|
||||
<?php $canBankAudit = $user && method_exists($user, 'can') ? ($user->can('finance.bank.deposit.create') || $user->can('finance.bank.audit') || $user->can('finance.bank.statement.update') || $user->can('finance.*')) : false; ?>
|
||||
|
||||
<div class="mb-3">
|
||||
<h2 class="mb-1">Accounts And Finance</h2>
|
||||
<p class="text-muted mb-0">Simple operational workflow for Billing cash statement submission, Accounts verification, and bank transaction audit.</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="card border-success">
|
||||
<div class="card-body py-2">
|
||||
<div class="small text-muted">Matched</div>
|
||||
<div class="h5 mb-0 text-success"><?= (int) (($match_summary['matched'] ?? 0)) ?></div>
|
||||
<div class="row g-3">
|
||||
<?php if ($canBillingSubmit): ?>
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100 border-primary">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">1) Cash Collection Statement</h5>
|
||||
<p class="small text-muted">Billing Department creates and submits daily cash statement to Accounts.</p>
|
||||
<a class="btn btn-primary btn-sm" href="javascript:load_form('<?= base_url('billing/cash-submission/create') ?>','Billing Cash Statement Submission');">
|
||||
Open Billing Statement
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="card border-warning">
|
||||
<div class="card-body py-2">
|
||||
<div class="small text-muted">Minor Variance</div>
|
||||
<div class="h5 mb-0 text-warning"><?= (int) (($match_summary['minor_variance'] ?? 0)) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($canAccountsVerify): ?>
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100 border-info">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">2) Accounts Accept & Verify</h5>
|
||||
<p class="small text-muted">Accounts Department accepts submitted statements and verifies payment records.</p>
|
||||
<a class="btn btn-info btn-sm text-white" href="javascript:load_form('<?= base_url('Finance/cashbook/accounts') ?>','Accounts Accept and Verify');">
|
||||
Open Verification Queue
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="card border-danger">
|
||||
<div class="card-body py-2">
|
||||
<div class="small text-muted">Mismatch / Not Checked</div>
|
||||
<div class="h5 mb-0 text-danger"><?= (int) (($match_summary['mismatch'] ?? 0)) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($canBankAudit): ?>
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100 border-success">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">3) Bank Trans Audit</h5>
|
||||
<p class="small text-muted">Audit bank transactions and mark updates posted in the bank statement register.</p>
|
||||
<a class="btn btn-success btn-sm" href="javascript:load_form('<?= base_url('Finance/bank_deposits') ?>','Bank Transaction Audit');">
|
||||
Open Bank Audit Register
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="card border-dark">
|
||||
<div class="card-body py-2">
|
||||
<div class="small text-muted">Compliance Hold</div>
|
||||
<div class="h5 mb-0"><?= (int) (($match_summary['hold'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<strong id="finance_sub_title">Vendor Master</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="finance-main"></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var launchers = Array.prototype.slice.call(document.querySelectorAll('.js-finance-launch'));
|
||||
var titleBox = document.getElementById('finance_sub_title');
|
||||
|
||||
function activateButton(activeBtn) {
|
||||
launchers.forEach(function(btn) {
|
||||
btn.classList.remove('btn-primary');
|
||||
btn.classList.add('btn-outline-primary');
|
||||
});
|
||||
|
||||
if (activeBtn) {
|
||||
activeBtn.classList.remove('btn-outline-primary');
|
||||
activeBtn.classList.add('btn-primary');
|
||||
}
|
||||
}
|
||||
|
||||
function loadSection(btn) {
|
||||
if (!btn) {
|
||||
return;
|
||||
}
|
||||
|
||||
var url = btn.getAttribute('data-url');
|
||||
var title = btn.getAttribute('data-title') || 'Finance Section';
|
||||
activateButton(btn);
|
||||
if (titleBox) {
|
||||
titleBox.textContent = title;
|
||||
}
|
||||
|
||||
load_form_div(url, 'finance-main', title + ' :Finance');
|
||||
}
|
||||
|
||||
launchers.forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
loadSection(btn);
|
||||
});
|
||||
});
|
||||
|
||||
if (launchers.length > 0) {
|
||||
loadSection(launchers[0]);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
|
|
|||
94
app/Views/finance/partials/bank_audit_direct_table.php
Normal file
94
app/Views/finance/partials/bank_audit_direct_table.php
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:36px;"><input type="checkbox" id="select_all_bank_payments" onchange="toggleSelectAllBankPayments(this)"></th>
|
||||
<th>#</th>
|
||||
<th>HMS Ref</th>
|
||||
<th>Payment Date</th>
|
||||
<th>Amount</th>
|
||||
<th>Txn Ref / UTR</th>
|
||||
<th>Channel</th>
|
||||
<th>Accepted By / Matched By</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($rows ?? [])): ?>
|
||||
<tr><td colspan="10" class="text-center text-muted py-3">No bank payments found for the selected criteria.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach (($rows ?? []) as $row): ?>
|
||||
<?php
|
||||
$status = (string) ($row['bank_reconcile_status'] ?? '');
|
||||
$isMatched = $status === 'matched';
|
||||
$sourceLabel = trim((string) ($row['bank_source_label'] ?? ''));
|
||||
if ($sourceLabel === '[]') {
|
||||
$sourceLabel = '';
|
||||
}
|
||||
$isPos = stripos($sourceLabel, 'machine') !== false || stripos((string) ($row['bankcard_machine'] ?? ''), 'machine') !== false;
|
||||
$channel = $isPos ? 'POS' : 'UPI/Direct';
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<?php if (! $isMatched): ?>
|
||||
<input type="checkbox" class="bank-payment-check" value="<?= (int) ($row['id'] ?? 0) ?>">
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td class="font-monospace small">PH-<?= (int) ($row['id'] ?? 0) ?></td>
|
||||
<td class="text-nowrap"><?= esc((string) ($row['payment_date'] ?? '')) ?></td>
|
||||
<td class="text-end fw-semibold">₹<?= number_format((float) ($row['amount'] ?? 0), 2) ?></td>
|
||||
<td class="font-monospace small"><?= esc((string) ($row['card_tran_id'] ?? '—')) ?></td>
|
||||
<td>
|
||||
<span class="badge <?= $isPos ? 'bg-info text-dark' : 'bg-secondary' ?>"><?= esc($channel) ?></span>
|
||||
<?php if ($sourceLabel !== ''): ?>
|
||||
<div class="small text-muted"><?= esc($sourceLabel) ?></div>
|
||||
<?php elseif ((string) ($row['bankcard_machine'] ?? '') !== ''): ?>
|
||||
<div class="small text-muted"><?= esc((string) ($row['bankcard_machine'] ?? '')) ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="small">
|
||||
<div><?= esc((string) ($row['accepted_by_name'] ?? '—')) ?></div>
|
||||
<?php if ($isMatched && (string)($row['matched_by'] ?? '') !== ''): ?>
|
||||
<div class="text-muted" style="font-size:0.78em;">
|
||||
<i class="bi bi-check2-circle me-1 text-success"></i>Matched: <?= esc((string)($row['matched_by'] ?? '')) ?>
|
||||
<?php if ((string)($row['matched_at'] ?? '') !== ''): ?>
|
||||
<br><span><?= esc(date('d-M-y H:i', strtotime((string)$row['matched_at']))) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($isMatched): ?>
|
||||
<span class="badge bg-success">Matched</span>
|
||||
<?php if ((string) ($row['bank_reconcile_batch_ref'] ?? '') !== ''): ?>
|
||||
<div class="small text-muted"><?= esc((string) ($row['bank_reconcile_batch_ref'] ?? '')) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ((int) ($row['bank_settlement_entry_id'] ?? 0) > 0): ?>
|
||||
<div class="small mt-1"><span class="badge bg-warning text-dark">Settlement</span></div>
|
||||
<?php if ((string) ($row['settlement_ref'] ?? '') !== ''): ?>
|
||||
<div class="small text-muted"><?= esc((string) ($row['settlement_ref'] ?? '')) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-warning text-dark">Unmatched</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-nowrap">
|
||||
<?php if ($isMatched): ?>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="unmatchPayment(<?= (int)($row['id']??0) ?>)">
|
||||
<i class="bi bi-x-circle me-1"></i>Unmatch
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="matchSinglePayment(<?= (int)($row['id']??0) ?>)">
|
||||
<i class="bi bi-link-45deg me-1"></i>Match
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -9,11 +9,12 @@
|
|||
<th>Slip</th>
|
||||
<th>Amount</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($deposits ?? [])): ?>
|
||||
<tr><td colspan="7" class="text-center text-muted">No bank deposits found.</td></tr>
|
||||
<tr><td colspan="8" class="text-center text-muted">No bank deposits found.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach (($deposits ?? []) as $row): ?>
|
||||
<?php $status = (string) ($row['reconciliation_status'] ?? 'pending'); ?>
|
||||
|
|
@ -25,12 +26,25 @@
|
|||
<td><?= esc((string) ($row['slip_no'] ?? '')) ?></td>
|
||||
<td><?= number_format((float) ($row['deposited_amount'] ?? 0), 2) ?></td>
|
||||
<td>
|
||||
<?php if ($status === 'matched'): ?>
|
||||
<?php if ($status === 'statement_updated'): ?>
|
||||
<span class="badge bg-success">Statement Updated</span>
|
||||
<?php elseif ($status === 'audited'): ?>
|
||||
<span class="badge bg-info text-dark">Audited</span>
|
||||
<?php elseif ($status === 'matched'): ?>
|
||||
<span class="badge bg-success">Matched</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-warning text-dark">Pending</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($status === 'statement_updated'): ?>
|
||||
<span class="text-muted small">Completed</span>
|
||||
<?php elseif ($status === 'audited'): ?>
|
||||
<button type="button" class="btn btn-outline-success btn-sm" onclick="financeDepositStatusAction(<?= (int) ($row['id'] ?? 0) ?>, 'statement_update')">Update Statement</button>
|
||||
<?php else: ?>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="financeDepositStatusAction(<?= (int) ($row['id'] ?? 0) ?>, 'audit')">Mark Audited</button>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
|
|
|||
77
app/Views/finance/partials/bank_pos_settlements_table.php
Normal file
77
app/Views/finance/partials/bank_pos_settlements_table.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Date</th>
|
||||
<th>Terminal</th>
|
||||
<th>Settlement Amt</th>
|
||||
<th>System Total</th>
|
||||
<th>Variance</th>
|
||||
<th>Transactions</th>
|
||||
<th>Status</th>
|
||||
<th>By</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($rows ?? [])): ?>
|
||||
<tr><td colspan="10" class="text-center text-muted py-3">No POS settlements recorded yet.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach (($rows ?? []) as $row): ?>
|
||||
<?php
|
||||
$status = (string) ($row['status'] ?? 'pending');
|
||||
$variance = (float) ($row['variance'] ?? 0);
|
||||
$badgeMap = [
|
||||
'matched' => '<span class="badge bg-success">Matched</span>',
|
||||
'variance' => '<span class="badge bg-danger">Variance</span>',
|
||||
'accepted' => '<span class="badge bg-primary">Accepted</span>',
|
||||
'pending' => '<span class="badge bg-warning text-dark">Pending</span>',
|
||||
];
|
||||
$badge = $badgeMap[$status] ?? '<span class="badge bg-secondary">' . esc($status) . '</span>';
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td class="text-nowrap"><?= esc((string)($row['settlement_date']??'')) ?></td>
|
||||
<td>
|
||||
<span class="fw-semibold"><?= esc((string)($row['terminal_id']??'')) ?></span>
|
||||
<?php if ((string)($row['terminal_name']??'') !== ''): ?>
|
||||
<div class="small text-muted"><?= esc((string)($row['terminal_name'])) ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end">₹<?= number_format((float)($row['settlement_amount']??0),2) ?></td>
|
||||
<td class="text-end">₹<?= number_format((float)($row['system_total']??0),2) ?></td>
|
||||
<td class="text-end <?= abs($variance) <= 0.01 ? 'text-success' : 'text-danger fw-bold' ?>">
|
||||
<?= ($variance >= 0 ? '+' : '') . number_format($variance, 2) ?>
|
||||
</td>
|
||||
<td class="text-center"><?= (int)($row['payment_count']??0) ?></td>
|
||||
<td><?= $badge ?></td>
|
||||
<td class="small">
|
||||
<?= esc((string)($row['reconciled_by']??'')) ?>
|
||||
<?php if ((string)($row['reconciled_at']??'') !== ''): ?>
|
||||
<div class="text-muted"><?= esc(substr((string)$row['reconciled_at'],0,16)) ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-nowrap">
|
||||
<?php if ($status === 'accepted'): ?>
|
||||
<span class="text-muted small">Closed</span>
|
||||
<?php elseif ($status === 'matched'): ?>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm"
|
||||
onclick="openAcceptModal(<?= (int)($row['id']??0) ?>)">
|
||||
<i class="bi bi-lock me-1"></i>Accept
|
||||
</button>
|
||||
<?php elseif ($status === 'variance'): ?>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm"
|
||||
onclick="openAcceptModal(<?= (int)($row['id']??0) ?>)">
|
||||
<i class="bi bi-exclamation-triangle me-1"></i>Accept Variance
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<span class="text-muted small">No txns found</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
$rows = $rows ?? [];
|
||||
$entry = $entry ?? [];
|
||||
$settlementId = (int) ($settlement_id ?? 0);
|
||||
$total = 0.0;
|
||||
foreach ($rows as $r) {
|
||||
$total += (float) ($r['amount'] ?? 0);
|
||||
}
|
||||
?>
|
||||
<div class="p-2 border-bottom bg-light small d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<div>
|
||||
<strong>Settlement Ref:</strong> <?= esc((string) ($entry['settlement_ref'] ?? '')) ?>
|
||||
<span class="ms-3"><strong>Date:</strong> <?= esc((string) ($entry['settlement_date'] ?? '')) ?></span>
|
||||
<span class="ms-3"><strong>Rows:</strong> <?= count($rows) ?></span>
|
||||
<span class="ms-3"><strong>Total:</strong> ₹<?= number_format($total, 2) ?></span>
|
||||
</div>
|
||||
<?php if ($settlementId > 0): ?>
|
||||
<button type="button" class="btn btn-outline-success btn-sm"
|
||||
onclick="window.open('<?= base_url('Finance/bank_settlement_linked_payments_export?settlement_id=' . $settlementId) ?>', '_blank')">
|
||||
<i class="bi bi-file-earmark-spreadsheet me-1"></i>Export CSV
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>HMS Ref</th>
|
||||
<th>Payment Date</th>
|
||||
<th class="text-end">Amount</th>
|
||||
<th>Txn Ref / UTR</th>
|
||||
<th>Channel</th>
|
||||
<th>Accepted By</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($rows)): ?>
|
||||
<tr>
|
||||
<td colspan="8" class="text-center text-muted py-3">No linked payments found.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach ($rows as $row): ?>
|
||||
<?php
|
||||
$sourceLabel = trim((string) ($row['bank_source_label'] ?? ''));
|
||||
if ($sourceLabel === '[]') {
|
||||
$sourceLabel = '';
|
||||
}
|
||||
$isPos = stripos($sourceLabel, 'machine') !== false || stripos((string) ($row['bankcard_machine'] ?? ''), 'machine') !== false;
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td class="font-monospace small">PH-<?= (int) ($row['id'] ?? 0) ?></td>
|
||||
<td class="text-nowrap"><?= esc((string) ($row['payment_date'] ?? '')) ?></td>
|
||||
<td class="text-end fw-semibold">₹<?= number_format((float) ($row['amount'] ?? 0), 2) ?></td>
|
||||
<td class="font-monospace small"><?= esc((string) ($row['card_tran_id'] ?? '—')) ?></td>
|
||||
<td>
|
||||
<span class="badge <?= $isPos ? 'bg-info text-dark' : 'bg-secondary' ?>"><?= $isPos ? 'POS' : 'UPI/Direct' ?></span>
|
||||
<?php if ($sourceLabel !== ''): ?>
|
||||
<div class="small text-muted"><?= esc($sourceLabel) ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= esc((string) ($row['accepted_by_name'] ?? '—')) ?></td>
|
||||
<td>
|
||||
<?php if ((string) ($row['bank_reconcile_status'] ?? '') === 'matched'): ?>
|
||||
<span class="badge bg-success">Matched</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-warning text-dark">Unmatched</span>
|
||||
<?php endif; ?>
|
||||
<?php if ((string) ($row['bank_reconcile_batch_ref'] ?? '') !== ''): ?>
|
||||
<div class="small text-muted"><?= esc((string) ($row['bank_reconcile_batch_ref'] ?? '')) ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
61
app/Views/finance/partials/bank_statement_entries_table.php
Normal file
61
app/Views/finance/partials/bank_statement_entries_table.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Date</th>
|
||||
<th>Ref / UTR</th>
|
||||
<th>Narration</th>
|
||||
<th>Amount</th>
|
||||
<th>Type</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($entries ?? [])): ?>
|
||||
<tr><td colspan="8" class="text-center text-muted py-3">No statement entries found.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach (($entries ?? []) as $entry): ?>
|
||||
<?php $status = (string) ($entry['reconciliation_status'] ?? 'unmatched'); ?>
|
||||
<tr>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td class="text-nowrap"><?= esc((string)($entry['entry_date'] ?? '')) ?></td>
|
||||
<td class="font-monospace small"><?= esc((string)($entry['reference_no'] ?? '—')) ?></td>
|
||||
<td class="small"><?= esc((string)($entry['narration'] ?? '—')) ?></td>
|
||||
<td class="text-end fw-semibold">₹<?= number_format((float)($entry['amount'] ?? 0), 2) ?></td>
|
||||
<td>
|
||||
<?php if ((string)($entry['transaction_type']??'') === 'debit'): ?>
|
||||
<span class="badge bg-secondary">Debit</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-info text-dark">Credit</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($status === 'matched'): ?>
|
||||
<span class="badge bg-success">Matched</span>
|
||||
<?php if ($entry['matched_payment_id']): ?>
|
||||
<div class="small text-muted">Pmt #<?= (int)($entry['matched_payment_id']) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-warning text-dark">Unmatched</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($status === 'unmatched'): ?>
|
||||
<!-- Match button used when loaded inside the match modal -->
|
||||
<button type="button" class="btn btn-success btn-sm" style="display:none"
|
||||
data-entry-id="<?= (int)($entry['id']??0) ?>"
|
||||
onclick="confirmMatch(<?= (int)($entry['id']??0) ?>)">
|
||||
<i class="bi bi-check2 me-1"></i>Select
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<span class="text-muted small"><?= esc((string)($entry['matched_by']??'')) ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
52
app/Views/finance/partials/payment_selection_table.php
Normal file
52
app/Views/finance/partials/payment_selection_table.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
$rows = $rows ?? [];
|
||||
$totals = $totals ?? ['payment_count' => 0, 'total_receipts' => 0];
|
||||
?>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<div class="small text-muted">Matched Payments: <strong><?= (int) ($totals['payment_count'] ?? 0) ?></strong></div>
|
||||
<div class="small text-muted">Total: <strong><?= number_format((float) ($totals['total_receipts'] ?? 0), 2) ?></strong></div>
|
||||
</div>
|
||||
<div class="table-responsive border rounded">
|
||||
<table class="table table-sm table-striped align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:36px;"><input type="checkbox" id="payment_select_all"></th>
|
||||
<th>#</th>
|
||||
<th>Payment Date</th>
|
||||
<th>Invoice Ref</th>
|
||||
<th>Collected By</th>
|
||||
<th>Mode</th>
|
||||
<th class="text-end">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($rows)): ?>
|
||||
<tr><td colspan="7" class="text-center text-muted">No payments found for selected filter.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach ($rows as $row): ?>
|
||||
<?php $pid = (int) ($row['id'] ?? 0); ?>
|
||||
<tr>
|
||||
<td><input type="checkbox" class="payment-checkbox" name="payment_ids[]" value="<?= $pid ?>"></td>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td><?= esc((string) ($row['payment_date'] ?? '')) ?></td>
|
||||
<td><?= esc((string) ($row['payof_code'] ?? '')) ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$collector = trim((string) ($row['update_by'] ?? ''));
|
||||
$collectorId = (int) ($row['update_by_id'] ?? 0);
|
||||
echo esc($collector !== '' ? $collector : ('User #' . $collectorId));
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
$mode = (int) ($row['payment_mode'] ?? 0);
|
||||
echo esc($mode === 1 ? 'Cash' : ($mode === 2 ? 'Bank' : 'Other'));
|
||||
?>
|
||||
</td>
|
||||
<td class="text-end payment-amount" data-amount="<?= (float) ($row['amount'] ?? 0) ?>"><?= number_format((float) ($row['amount'] ?? 0), 2) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
$rows = $rows ?? [];
|
||||
?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<strong>Recent OPD Payout Drafts</strong>
|
||||
<span class="small text-muted">Filtered by current date/doctor/state selection</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Payout Date</th>
|
||||
<th>Case Ref</th>
|
||||
<th>Doctor</th>
|
||||
<th class="text-end">Units</th>
|
||||
<th class="text-end">Rate</th>
|
||||
<th class="text-end">Calculated</th>
|
||||
<th class="text-end">Approved</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($rows)): ?>
|
||||
<tr>
|
||||
<td colspan="10" class="text-center text-muted py-3">No payout drafts found for selected filters.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach ($rows as $row): ?>
|
||||
<?php $status = strtolower(trim((string) ($row['status'] ?? 'draft'))); ?>
|
||||
<tr>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td class="text-nowrap"><?= esc((string) ($row['payout_date'] ?? '')) ?></td>
|
||||
<td class="font-monospace"><?= esc((string) ($row['case_reference'] ?? '')) ?></td>
|
||||
<td><?= esc((string) ($row['doctor_name'] ?? '')) ?></td>
|
||||
<td class="text-end"><?= (int) ($row['units'] ?? 0) ?></td>
|
||||
<td class="text-end">Rs <?= number_format((float) ($row['rate'] ?? 0), 2) ?></td>
|
||||
<td class="text-end">Rs <?= number_format((float) ($row['calculated_amount'] ?? 0), 2) ?></td>
|
||||
<td class="text-end">Rs <?= number_format((float) ($row['approved_amount'] ?? 0), 2) ?></td>
|
||||
<td>
|
||||
<?php if ($status === 'paid'): ?>
|
||||
<span class="badge bg-success">Paid</span>
|
||||
<?php elseif ($status === 'ceo_approved'): ?>
|
||||
<span class="badge bg-primary">CEO Approved</span>
|
||||
<?php elseif ($status === 'finance_approved'): ?>
|
||||
<span class="badge bg-info text-dark">Finance Approved</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary">Draft</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($status === 'draft'): ?>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm"
|
||||
onclick="editOpdPayoutDraft(<?= (int) ($row['id'] ?? 0) ?>, '<?= esc((string) ($row['payout_date'] ?? ''), 'js') ?>', '<?= esc((string) ($row['approved_amount'] ?? '0'), 'js') ?>', '<?= esc((string) ($row['remarks'] ?? ''), 'js') ?>')">
|
||||
<i class="bi bi-pencil-square me-1"></i>Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm mt-1"
|
||||
onclick="deleteOpdPayoutDraft(<?= (int) ($row['id'] ?? 0) ?>)">
|
||||
<i class="bi bi-trash me-1"></i>Delete
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<span class="text-muted small">Locked</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
256
app/Views/finance/partials/payout_opd_consult_summary.php
Normal file
256
app/Views/finance/partials/payout_opd_consult_summary.php
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
<?php
|
||||
$summary = $summary ?? [];
|
||||
$doctorBreakdown = $doctor_breakdown ?? [];
|
||||
$fromDate = (string) ($from_date ?? '');
|
||||
$toDate = (string) ($to_date ?? '');
|
||||
$doctorName = trim((string) ($doctor_name ?? 'All Doctors'));
|
||||
$stateUnit = trim((string) ($state_unit ?? ''));
|
||||
$stateUnitLabel = trim((string) ($state_unit_label ?? 'State/Unit'));
|
||||
|
||||
$formatDate = static function (string $value): string {
|
||||
$value = trim($value);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$timestamp = strtotime($value);
|
||||
return $timestamp === false ? $value : date('d-m-Y', $timestamp);
|
||||
};
|
||||
|
||||
$money = static function ($value): string {
|
||||
return 'Rs ' . number_format((float) $value, 2);
|
||||
};
|
||||
?>
|
||||
|
||||
<div class="card border-0 bg-light mb-3">
|
||||
<div class="card-body py-2 px-3 d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<div>
|
||||
<strong>Summary Range:</strong> <?= esc($formatDate($fromDate)) ?> to <?= esc($formatDate($toDate)) ?>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Doctor:</strong> <?= esc($doctorName !== '' ? $doctorName : 'All Doctors') ?>
|
||||
</div>
|
||||
<div>
|
||||
<strong><?= esc($stateUnitLabel) ?>:</strong> <?= esc($stateUnit !== '' ? $stateUnit : 'All State/Unit') ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-primary"><div class="card-body py-2"><div class="small text-muted">Completed OPDs</div><div class="h5 mb-0 text-primary"><?= (int) ($summary['completed_opd'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-secondary"><div class="card-body py-2"><div class="small text-muted">Routine OPDs</div><div class="h5 mb-0"><?= (int) ($summary['routine_opd'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-danger"><div class="card-body py-2"><div class="small text-muted">Emergency OPDs</div><div class="h5 mb-0 text-danger"><?= (int) ($summary['emergency_opd'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-info"><div class="card-body py-2"><div class="small text-muted">Doctors Involved</div><div class="h5 mb-0 text-info"><?= (int) ($summary['doctor_count'] ?? 0) ?></div></div></div></div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-dark"><div class="card-body py-2"><div class="small text-muted">Already Calculated OPDs</div><div class="h5 mb-0\"><?= (int) ($summary['calculated_opd'] ?? 0) ?></div></div></div></div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-success"><div class="card-body py-2"><div class="small text-muted">Gross OPD Revenue</div><div class="h5 mb-0 text-success"><?= esc($money($summary['gross_amount'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-success"><div class="card-body py-2"><div class="small text-muted">Total Received</div><div class="h5 mb-0 text-success"><?= esc($money($summary['total_received'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-warning"><div class="card-body py-2"><div class="small text-muted">Cash Received</div><div class="h5 mb-0 text-warning"><?= esc($money($summary['cash_received'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-primary"><div class="card-body py-2"><div class="small text-muted">Bank Received</div><div class="h5 mb-0 text-primary"><?= esc($money($summary['bank_received'] ?? 0)) ?></div></div></div></div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-dark"><div class="card-body py-2"><div class="small text-muted">Total Credit Amount</div><div class="h5 mb-0"><?= esc($money($summary['credit_amount'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-dark"><div class="card-body py-2"><div class="small text-muted">Organizational Credit</div><div class="h5 mb-0"><?= esc($money($summary['org_credit_amount'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-info"><div class="card-body py-2"><div class="small text-muted">Approved Consents</div><div class="h5 mb-0 text-info"><?= (int) ($summary['approved_consents'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-secondary"><div class="card-body py-2"><div class="small text-muted">Payout Records / Amount</div><div class="h6 mb-0"><?= (int) ($summary['payout_count'] ?? 0) ?> / <?= esc($money($summary['payout_amount'] ?? 0)) ?></div></div></div></div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<strong>Doctor-wise OPD and Collection Summary</strong>
|
||||
<span class="small text-muted">Running and New are shown for internal review; routine figure is non-emergency total.</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Doctor</th>
|
||||
<th class="text-end">Completed</th>
|
||||
<th class="text-end">Routine</th>
|
||||
<th class="text-end">Emergency</th>
|
||||
<th class="text-end">Running</th>
|
||||
<th class="text-end">New</th>
|
||||
<th class="text-end">Gross Amount</th>
|
||||
<th class="text-end">Cash</th>
|
||||
<th class="text-end">Bank</th>
|
||||
<th class="text-end">Credit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($doctorBreakdown)): ?>
|
||||
<tr>
|
||||
<td colspan="11" class="text-center text-muted py-3">No OPD summary records found for the selected filters.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach ($doctorBreakdown as $row): ?>
|
||||
<tr>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td><?= esc((string) ($row['doctor_name'] ?? '')) ?></td>
|
||||
<td class="text-end"><?= (int) ($row['completed_opd'] ?? 0) ?></td>
|
||||
<td class="text-end"><?= (int) ($row['routine_opd'] ?? 0) ?></td>
|
||||
<td class="text-end"><?= (int) ($row['emergency_opd'] ?? 0) ?></td>
|
||||
<td class="text-end"><?= (int) ($row['running_opd'] ?? 0) ?></td>
|
||||
<td class="text-end"><?= (int) ($row['new_opd'] ?? 0) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['gross_amount'] ?? 0)) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['cash_received'] ?? 0)) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['bank_received'] ?? 0)) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['credit_amount'] ?? 0)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$baseForPayout = (float) ($summary['total_received'] ?? 0);
|
||||
?>
|
||||
<div class="card border-0 shadow-sm mt-3">
|
||||
<div class="card-header bg-white">
|
||||
<strong>Payout Calculation (Preview)</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2 align-items-end mb-2">
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<label class="form-label form-label-sm">Base Amount</label>
|
||||
<input type="number" class="form-control form-control-sm" id="opd_payout_base_amount" value="<?= number_format($baseForPayout, 2, '.', '') ?>" step="0.01" min="0">
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6">
|
||||
<label class="form-label form-label-sm">Doctor Share %</label>
|
||||
<input type="number" class="form-control form-control-sm" id="opd_payout_doctor_share" value="75" step="0.01" min="0" max="100">
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6">
|
||||
<label class="form-label form-label-sm">Hospital Share %</label>
|
||||
<input type="number" class="form-control form-control-sm" id="opd_payout_hospital_share" value="25" step="0.01" min="0" max="100">
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6">
|
||||
<label class="form-label form-label-sm">Deductions</label>
|
||||
<input type="number" class="form-control form-control-sm" id="opd_payout_deductions" value="0" step="0.01" min="0">
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<label class="form-label form-label-sm">Adjustments (+/-)</label>
|
||||
<input type="number" class="form-control form-control-sm" id="opd_payout_adjustments" value="0" step="0.01">
|
||||
</div>
|
||||
<div class="col-12 d-flex justify-content-end">
|
||||
<button type="button" class="btn btn-success btn-sm" id="opd_payout_create_draft_btn">
|
||||
<i class="bi bi-file-earmark-plus me-1"></i>Create Payout Draft
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4 col-12"><div class="card border-info"><div class="card-body py-2"><div class="small text-muted">Doctor Gross Share</div><div class="h6 mb-0 text-info" id="opd_payout_doctor_gross">Rs 0.00</div></div></div></div>
|
||||
<div class="col-md-4 col-12"><div class="card border-secondary"><div class="card-body py-2"><div class="small text-muted">Hospital Share</div><div class="h6 mb-0" id="opd_payout_hospital_gross">Rs 0.00</div></div></div></div>
|
||||
<div class="col-md-4 col-12"><div class="card border-success"><div class="card-body py-2"><div class="small text-muted">Net Payable To Doctor</div><div class="h6 mb-0 text-success" id="opd_payout_net_payable">Rs 0.00</div></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
function toNumber(id) {
|
||||
var el = document.getElementById(id);
|
||||
if (!el) return 0;
|
||||
var value = parseFloat(el.value || '0');
|
||||
return isNaN(value) ? 0 : value;
|
||||
}
|
||||
|
||||
function formatMoney(value) {
|
||||
var num = isNaN(value) ? 0 : value;
|
||||
return 'Rs ' + num.toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2});
|
||||
}
|
||||
|
||||
function recalc() {
|
||||
var baseAmount = toNumber('opd_payout_base_amount');
|
||||
var doctorShare = toNumber('opd_payout_doctor_share');
|
||||
var hospitalShare = toNumber('opd_payout_hospital_share');
|
||||
var deductions = toNumber('opd_payout_deductions');
|
||||
var adjustments = toNumber('opd_payout_adjustments');
|
||||
|
||||
var doctorGross = (baseAmount * doctorShare) / 100;
|
||||
var hospitalGross = (baseAmount * hospitalShare) / 100;
|
||||
var netPayable = doctorGross - deductions + adjustments;
|
||||
|
||||
var doctorEl = document.getElementById('opd_payout_doctor_gross');
|
||||
var hospitalEl = document.getElementById('opd_payout_hospital_gross');
|
||||
var netEl = document.getElementById('opd_payout_net_payable');
|
||||
|
||||
if (doctorEl) doctorEl.textContent = formatMoney(doctorGross);
|
||||
if (hospitalEl) hospitalEl.textContent = formatMoney(hospitalGross);
|
||||
if (netEl) netEl.textContent = formatMoney(netPayable);
|
||||
}
|
||||
|
||||
['opd_payout_base_amount', 'opd_payout_doctor_share', 'opd_payout_hospital_share', 'opd_payout_deductions', 'opd_payout_adjustments']
|
||||
.forEach(function (id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) {
|
||||
el.addEventListener('input', recalc);
|
||||
}
|
||||
});
|
||||
|
||||
var draftBtn = document.getElementById('opd_payout_create_draft_btn');
|
||||
if (draftBtn) {
|
||||
draftBtn.addEventListener('click', function () {
|
||||
var fromDate = document.getElementById('opd_payout_from_date')?.value || '';
|
||||
var toDate = document.getElementById('opd_payout_to_date')?.value || '';
|
||||
var doctorId = document.getElementById('opd_payout_doctor_id')?.value || '';
|
||||
var stateUnit = document.getElementById('opd_payout_state_unit')?.value || '';
|
||||
|
||||
if (!doctorId) {
|
||||
if (typeof window.showOpdPayoutAlert === 'function') {
|
||||
window.showOpdPayoutAlert('Please select a doctor before creating payout draft.', false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var fd = new window.FormData();
|
||||
fd.append('from_date', fromDate);
|
||||
fd.append('to_date', toDate);
|
||||
fd.append('doctor_id', doctorId);
|
||||
fd.append('state_unit', stateUnit);
|
||||
fd.append('base_amount', String(toNumber('opd_payout_base_amount')));
|
||||
fd.append('doctor_share', String(toNumber('opd_payout_doctor_share')));
|
||||
fd.append('hospital_share', String(toNumber('opd_payout_hospital_share')));
|
||||
fd.append('deductions', String(toNumber('opd_payout_deductions')));
|
||||
fd.append('adjustments', String(toNumber('opd_payout_adjustments')));
|
||||
|
||||
fetch('<?= base_url('Finance/payout/opd-consult-draft-create') ?>', {
|
||||
method: 'POST',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
body: fd,
|
||||
})
|
||||
.then(function (res) { return res.json().then(function (data) { return {ok: res.ok, data: data}; }); })
|
||||
.then(function (result) {
|
||||
var ok = result.ok && result.data && result.data.status === 1;
|
||||
var msg = (result.data && result.data.message) ? result.data.message : (ok ? 'Payout draft created.' : 'Unable to create payout draft.');
|
||||
if (ok && result.data.case_reference) {
|
||||
msg += ' Ref: ' + result.data.case_reference;
|
||||
}
|
||||
if (typeof window.showOpdPayoutAlert === 'function') {
|
||||
window.showOpdPayoutAlert(msg, ok);
|
||||
}
|
||||
if (ok && typeof window.refreshOpdConsultPayoutSummary === 'function') {
|
||||
window.refreshOpdConsultPayoutSummary();
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
if (typeof window.showOpdPayoutAlert === 'function') {
|
||||
window.showOpdPayoutAlert('Network or server error while creating payout draft.', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
recalc();
|
||||
})();
|
||||
</script>
|
||||
43
app/Views/finance/partials/scroll_items_table.php
Normal file
43
app/Views/finance/partials/scroll_items_table.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php $rows = $rows ?? []; ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Payment ID</th>
|
||||
<th>Payment Date</th>
|
||||
<th>Invoice Ref</th>
|
||||
<th>Collector</th>
|
||||
<th>Mode</th>
|
||||
<th class="text-end">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($rows)): ?>
|
||||
<tr><td colspan="7" class="text-center text-muted">No payment rows were saved for this submission.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; $total = 0.0; foreach ($rows as $row): ?>
|
||||
<?php $amt = (float) ($row['amount'] ?? 0); $total += $amt; ?>
|
||||
<tr>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td><?= (int) ($row['payment_history_id'] ?? 0) ?></td>
|
||||
<td><?= esc((string) ($row['payment_date'] ?? '')) ?></td>
|
||||
<td><?= esc((string) ($row['payof_code'] ?? '')) ?></td>
|
||||
<td><?= esc((string) ($row['update_by'] ?? '')) ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$mode = (int) ($row['payment_mode'] ?? 0);
|
||||
echo esc($mode === 1 ? 'Cash' : ($mode === 2 ? 'Bank' : 'Other'));
|
||||
?>
|
||||
</td>
|
||||
<td class="text-end"><?= number_format($amt, 2) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<tr>
|
||||
<td colspan="6" class="text-end fw-semibold">Total</td>
|
||||
<td class="text-end fw-semibold"><?= number_format($total, 2) ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -3,32 +3,61 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Date</th>
|
||||
<th>Collection Period</th>
|
||||
<th>Department</th>
|
||||
<th>Collected By</th>
|
||||
<th>Payments</th>
|
||||
<th>Total Receipts</th>
|
||||
<th>Submitted</th>
|
||||
<th>Variance</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($scrolls ?? [])): ?>
|
||||
<tr><td colspan="7" class="text-center text-muted">No scroll submissions yet.</td></tr>
|
||||
<tr><td colspan="10" class="text-center text-muted">No cash statements submitted yet.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach (($scrolls ?? []) as $row): ?>
|
||||
<?php $st = (string) ($row['reconciliation_status'] ?? 'pending'); ?>
|
||||
<tr>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td><?= esc((string) ($row['scroll_date'] ?? '')) ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$start = trim((string) ($row['start_datetime'] ?? ''));
|
||||
$end = trim((string) ($row['end_datetime'] ?? ''));
|
||||
if ($start !== '' && $end !== '') {
|
||||
echo esc($start) . ' to ' . esc($end);
|
||||
} else {
|
||||
echo esc((string) ($row['scroll_date'] ?? ''));
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td><?= esc((string) ($row['department'] ?? '')) ?></td>
|
||||
<td><?= esc((string) ($row['collected_by'] ?? ($row['submitted_by'] ?? ''))) ?></td>
|
||||
<td><?= (int) ($row['payment_count'] ?? 0) ?></td>
|
||||
<td><?= number_format((float) ($row['total_receipts'] ?? 0), 2) ?></td>
|
||||
<td><?= number_format((float) ($row['submitted_amount'] ?? 0), 2) ?></td>
|
||||
<td><?= number_format((float) ($row['variance_amount'] ?? 0), 2) ?></td>
|
||||
<td>
|
||||
<?php $st = (string) ($row['reconciliation_status'] ?? 'pending'); ?>
|
||||
<?php if ($st === 'matched'): ?>
|
||||
<span class="badge bg-success">Matched</span>
|
||||
<?php else: ?>
|
||||
<?php if ($st === 'deposited' || $st === 'verified' || $st === 'matched'): ?>
|
||||
<span class="badge bg-success">Deposited</span>
|
||||
<?php elseif ($st === 'received' || $st === 'accepted'): ?>
|
||||
<span class="badge bg-primary">Received</span>
|
||||
<?php elseif ($st === 'submitted' || $st === 'pending'): ?>
|
||||
<span class="badge bg-warning text-dark">Pending</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary"><?= esc(ucfirst($st)) ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm me-1" onclick="financeViewScrollItems(<?= (int) ($row['id'] ?? 0) ?>)">View Payments</button>
|
||||
<?php if ($st === 'submitted' || $st === 'pending'): ?>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="financeScrollAction(<?= (int) ($row['id'] ?? 0) ?>, 'accept')">Mark Received</button>
|
||||
<?php elseif ($st === 'accepted' || $st === 'received'): ?>
|
||||
<button type="button" class="btn btn-outline-success btn-sm" onclick="financeScrollAction(<?= (int) ($row['id'] ?? 0) ?>, 'verify')">Mark Deposited</button>
|
||||
<?php else: ?>
|
||||
<span class="text-muted small">No action</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -74,20 +74,23 @@ if (! $canHospitalStock && $user && method_exists($user, 'inGroup')) {
|
|||
|
||||
$canFinance = false;
|
||||
if ($user && method_exists($user, 'can')) {
|
||||
$canFinance = $user->can('finance.access')
|
||||
|| $user->can('finance.vendor.manage')
|
||||
|| $user->can('finance.po.manage')
|
||||
|| $user->can('finance.grn.manage')
|
||||
|| $user->can('finance.invoice.manage')
|
||||
|| $user->can('finance.cash.manage')
|
||||
|| $user->can('finance.doctor_payout.manage')
|
||||
|| $user->can('finance.bank_deposit.manage')
|
||||
|| $user->can('finance.compliance.view')
|
||||
$canFinance = $user->can('finance.workflow.view')
|
||||
|| $user->can('finance.cash.billing.submit')
|
||||
|| $user->can('finance.cash.accounts.accept')
|
||||
|| $user->can('finance.cash.accounts.verify')
|
||||
|| $user->can('finance.bank.deposit.create')
|
||||
|| $user->can('finance.bank.audit')
|
||||
|| $user->can('finance.bank.statement.update')
|
||||
|| $user->can('finance.*');
|
||||
}
|
||||
|
||||
if (! $canFinance && $user && method_exists($user, 'inGroup')) {
|
||||
$canFinance = $user->inGroup('superadmin', 'admin', 'developer');
|
||||
$canFinanceBilling = false;
|
||||
$canFinanceAccounts = false;
|
||||
$canFinanceBank = false;
|
||||
if ($user && method_exists($user, 'can')) {
|
||||
$canFinanceBilling = $user->can('finance.cash.billing.submit') || $user->can('finance.*');
|
||||
$canFinanceAccounts = $user->can('finance.cash.accounts.accept') || $user->can('finance.cash.accounts.verify') || $user->can('finance.*');
|
||||
$canFinanceBank = $user->can('finance.bank.deposit.create') || $user->can('finance.bank.audit') || $user->can('finance.bank.statement.update') || $user->can('finance.*');
|
||||
}
|
||||
|
||||
$canDoctorWork = false;
|
||||
|
|
@ -246,44 +249,61 @@ if (! $canChargesSettings && $user && method_exists($user, 'inGroup')) {
|
|||
<span>Org Packing </span>
|
||||
</a>
|
||||
</li>
|
||||
<?php if ($canFinanceBilling) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('billing/cash-submission/create') ?>','Cash Submission')">
|
||||
<i class="bi bi-cash-stack"></i>
|
||||
<span>Cash Submission</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
<?php if ($canFinance) { ?>
|
||||
<li class="nav-heading">Accounts And Finance</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Finance/index') ?>','Finance & Accounting')">
|
||||
<i class="bi bi-bank"></i>
|
||||
<span>Finance & Accounting</span>
|
||||
<span>Accounts And Finance</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php if ($canFinanceBilling) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('billing/cash-submission/create') ?>','Billing Cash Statement Submission')">
|
||||
<i class="bi bi-journal-arrow-up"></i>
|
||||
<span>Billing Cash Statement</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<?php if ($canFinanceAccounts) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Finance/cashbook/accounts') ?>','Accounts Accept and Verify')">
|
||||
<i class="bi bi-patch-check"></i>
|
||||
<span>Accounts Verify Payment</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<?php if ($canFinanceBank) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Finance/bank_audit') ?>','Bank Payment Audit')">
|
||||
<i class="bi bi-building"></i>
|
||||
<span>Bank Audit And Statement</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Finance/phase2') ?>','Finance & Accounting - Phase 2')">
|
||||
<i class="bi bi-diagram-3"></i>
|
||||
<span>Finance Phase 2</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Finance/cashbook') ?>','Cash Collection & Disbursement SOP')">
|
||||
<i class="bi bi-cash-coin"></i>
|
||||
<span>Cash & Disbursement SOP</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Finance/doctor_payout') ?>','Doctor Payout Workflow')">
|
||||
<i class="bi bi-person-badge"></i>
|
||||
<span>Doctor Payout Workflow</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Finance/bank_deposits') ?>','Bank Deposit Register')">
|
||||
<i class="bi bi-building"></i>
|
||||
<span>Bank Deposit Register</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Finance/compliance_report') ?>','Finance Compliance Report')">
|
||||
<i class="bi bi-shield-exclamation"></i>
|
||||
<span>Finance Compliance Report</span>
|
||||
<a class="nav-link collapsed" data-bs-target="#finance-payout-nav" data-bs-toggle="collapse" href="#">
|
||||
<i class="bi bi-cash-stack"></i>
|
||||
<span>Payout</span>
|
||||
<i class="bi bi-chevron-down ms-auto"></i>
|
||||
</a>
|
||||
<ul id="finance-payout-nav" class="nav-content collapse" data-bs-parent="#sidebar-nav">
|
||||
<li>
|
||||
<a href="javascript:load_form('<?= base_url('Finance/payout/opd-consult') ?>','OPD Consult Payout')">
|
||||
<i class="bi bi-circle"></i>
|
||||
<span>OPD Consult Payout</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<?php if ($canIpdBilling) { ?>
|
||||
|
|
@ -360,7 +380,7 @@ if (! $canChargesSettings && $user && method_exists($user, 'inGroup')) {
|
|||
<?php if ($canHospitalStock) { ?>
|
||||
<li class="nav-heading">Hospital Stock</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('setting/admin/hospital-stock') ?>','Hospital Stock Management');">
|
||||
<a class="nav-link collapsed" href="javascript:load_form('<?= base_url('Storestock') ?>','Hospital Stock');">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<span>Hospital Stock</span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
<li><a href="javascript:load_form('<?= base_url('Report/collection_report') ?>','Collection Report');"><i class="bi bi-arrow-right-short"></i> Total Payment (Cash and Bank)</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/report_opd_total') ?>','OPD Total Report');"><i class="bi bi-arrow-right-short"></i> OPD Total Report</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/billing_operations_report') ?>','Billing Operations Report');"><i class="bi bi-arrow-right-short"></i> Billing Operations Report</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/bank_deposits') ?>','Bank Deposit Register');"><i class="bi bi-arrow-right-short"></i> Bank Deposit Register</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/bank_deposits') ?>','Bank Transaction Audit');"><i class="bi bi-arrow-right-short"></i> Bank Transaction Audit</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -32,12 +32,10 @@
|
|||
<li><a href="javascript:load_form('<?= base_url('Report/collection_report') ?>','Collection Report');"><i class="bi bi-arrow-right-short"></i> Total Payment (Cash and Bank)</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/report_opd_total') ?>','OPD Total Report');"><i class="bi bi-arrow-right-short"></i> OPD Total Report</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/billing_operations_report') ?>','Billing Operations Report');"><i class="bi bi-arrow-right-short"></i> Billing Operations Report</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/index') ?>','Finance & Accounting');"><i class="bi bi-arrow-right-short"></i> Finance & Accounting (Phase 1)</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/phase2') ?>','Finance & Accounting - Phase 2');"><i class="bi bi-arrow-right-short"></i> Finance & Accounting (Phase 2)</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/cashbook') ?>','Cash Collection & Disbursement SOP');"><i class="bi bi-arrow-right-short"></i> Cash Collection & Disbursement SOP</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/doctor_payout') ?>','Doctor Payout Workflow');"><i class="bi bi-arrow-right-short"></i> Doctor Payout Workflow</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/bank_deposits') ?>','Bank Deposit Register');"><i class="bi bi-arrow-right-short"></i> Bank Deposit Register</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/compliance_report') ?>','Finance Compliance Report');"><i class="bi bi-arrow-right-short"></i> Finance Compliance Report</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/index') ?>','Accounts And Finance');"><i class="bi bi-arrow-right-short"></i> Accounts And Finance Dashboard</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/cashbook') ?>','Billing Cash Statement Submission');"><i class="bi bi-arrow-right-short"></i> Billing Cash Statement Submission</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/cashbook') ?>','Accounts Accept and Verify');"><i class="bi bi-arrow-right-short"></i> Accounts Accept and Verify</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/bank_deposits') ?>','Bank Transaction Audit');"><i class="bi bi-arrow-right-short"></i> Bank Transaction Audit</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/nabh_audit_report') ?>','NABH Audit Report');"><i class="bi bi-arrow-right-short"></i> NABH Audit Report</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
392
app/Views/storestock/_niceadmin_theme.php
Normal file
392
app/Views/storestock/_niceadmin_theme.php
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
<style>
|
||||
.storestock-ui .content-header h1 {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 700;
|
||||
color: #012970;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.storestock-ui .content-header h1 small {
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
margin-left: .45rem;
|
||||
}
|
||||
|
||||
.storestock-ui .box {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef4;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 0 24px rgba(1, 41, 112, 0.06);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.storestock-ui .box-header,
|
||||
.storestock-ui .box-footer {
|
||||
padding: .85rem 1rem;
|
||||
border-color: #ebeef4;
|
||||
}
|
||||
|
||||
.storestock-ui .box-body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.storestock-ui .box-title {
|
||||
color: #012970;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.storestock-ui .btn-app {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
min-width: 128px;
|
||||
min-height: 94px;
|
||||
margin: .35rem;
|
||||
padding: .75rem;
|
||||
border: 1px solid #ebeef4;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
color: #012970;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: all .18s ease;
|
||||
}
|
||||
|
||||
.storestock-ui .btn-app i {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: .35rem;
|
||||
color: #4154f1;
|
||||
}
|
||||
|
||||
.storestock-ui .btn-app:hover {
|
||||
transform: translateY(-1px);
|
||||
border-color: #dbe3ff;
|
||||
box-shadow: 0 6px 20px rgba(65, 84, 241, 0.12);
|
||||
}
|
||||
|
||||
.storestock-ui .table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.storestock-ui .table > thead > tr > th {
|
||||
background: #f6f9ff;
|
||||
color: #012970;
|
||||
border-bottom-width: 1px;
|
||||
font-weight: 700;
|
||||
font-size: .86rem;
|
||||
}
|
||||
|
||||
.storestock-ui .table > tbody > tr > td,
|
||||
.storestock-ui .table > tfoot > tr > th,
|
||||
.storestock-ui .table > tfoot > tr > td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.storestock-ui .form-control,
|
||||
.storestock-ui .input-sm {
|
||||
border-radius: 8px;
|
||||
border-color: #dfe3ea;
|
||||
}
|
||||
|
||||
.storestock-ui .input-group-addon {
|
||||
background: #f6f9ff;
|
||||
border: 1px solid #dfe3ea;
|
||||
border-right: 0;
|
||||
border-radius: 8px 0 0 8px;
|
||||
color: #4154f1;
|
||||
padding: .45rem .7rem;
|
||||
}
|
||||
|
||||
.storestock-ui .btn-xs,
|
||||
.storestock-ui .btn-sm {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.storestock-ui .nav-stacked > li > a {
|
||||
display: block;
|
||||
padding: .5rem .2rem;
|
||||
color: #4154f1;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.storestock-ui .nav-stacked > li > a:hover {
|
||||
color: #012970;
|
||||
}
|
||||
|
||||
.storestock-ui .bg-gray {
|
||||
background: #f6f9ff;
|
||||
color: #012970;
|
||||
border-bottom: 1px solid #ebeef4;
|
||||
}
|
||||
|
||||
.storestock-ui .modal-content {
|
||||
border-radius: 12px;
|
||||
border: 1px solid #ebeef4;
|
||||
box-shadow: 0 0 30px rgba(1, 41, 112, 0.15);
|
||||
}
|
||||
|
||||
.storestock-ui .modal-header {
|
||||
background: #f6f9ff;
|
||||
border-bottom: 1px solid #ebeef4;
|
||||
}
|
||||
|
||||
.storestock-ui .modal-title {
|
||||
color: #012970;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.storestock-ui .module-hero {
|
||||
border: 1px solid #e9edf5;
|
||||
background: linear-gradient(135deg, #f6f9ff 0%, #ffffff 85%);
|
||||
border-radius: 14px;
|
||||
padding: 1rem 1.1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.storestock-ui .module-hero h4 {
|
||||
margin: 0;
|
||||
color: #012970;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.storestock-ui .module-hero p {
|
||||
margin: .35rem 0 0 0;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.storestock-ui .stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(120px, 1fr));
|
||||
gap: .75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.storestock-ui .stat-card {
|
||||
border: 1px solid #ebeef4;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
padding: .75rem .9rem;
|
||||
}
|
||||
|
||||
.storestock-ui .stat-card .label {
|
||||
display: block;
|
||||
color: #6c757d;
|
||||
font-size: .76rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: .35rem;
|
||||
}
|
||||
|
||||
.storestock-ui .stat-card .value {
|
||||
color: #012970;
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.storestock-ui .action-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(180px, 1fr));
|
||||
gap: .75rem;
|
||||
}
|
||||
|
||||
.storestock-ui .action-card {
|
||||
display: block;
|
||||
border: 1px solid #e6eaf2;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
padding: .95rem;
|
||||
text-decoration: none;
|
||||
color: #212529;
|
||||
transition: all .18s ease;
|
||||
}
|
||||
|
||||
.storestock-ui .action-card:hover {
|
||||
transform: translateY(-1px);
|
||||
border-color: #dbe3ff;
|
||||
box-shadow: 0 8px 22px rgba(65, 84, 241, 0.14);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.storestock-ui .action-card .icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #eef2ff;
|
||||
color: #4154f1;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: .55rem;
|
||||
}
|
||||
|
||||
.storestock-ui .action-card h5 {
|
||||
margin: 0;
|
||||
font-size: .98rem;
|
||||
color: #012970;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.storestock-ui .action-card p {
|
||||
margin: .35rem 0 0 0;
|
||||
color: #6c757d;
|
||||
font-size: .82rem;
|
||||
}
|
||||
|
||||
.storestock-ui .toolbar-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .5rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: .8rem;
|
||||
}
|
||||
|
||||
.storestock-ui .toolbar-row .left,
|
||||
.storestock-ui .toolbar-row .right {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.storestock-ui .pill-btn {
|
||||
border: 1px solid #dfe3ea;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
color: #012970;
|
||||
padding: .4rem .8rem;
|
||||
font-size: .82rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.storestock-ui .pill-btn.active,
|
||||
.storestock-ui .pill-btn:hover {
|
||||
border-color: #4154f1;
|
||||
color: #4154f1;
|
||||
}
|
||||
|
||||
.storestock-ui .panel-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(260px, 1fr));
|
||||
gap: .9rem;
|
||||
}
|
||||
|
||||
.storestock-ui .control-card {
|
||||
border: 1px solid #ebeef4;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
padding: .95rem;
|
||||
}
|
||||
|
||||
.storestock-ui .control-card h5 {
|
||||
margin: 0 0 .65rem 0;
|
||||
color: #012970;
|
||||
font-size: .95rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.storestock-ui .control-card .help {
|
||||
margin-top: .55rem;
|
||||
color: #6c757d;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.storestock-ui .menu-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(220px, 1fr));
|
||||
gap: .9rem;
|
||||
}
|
||||
|
||||
.storestock-ui .menu-section {
|
||||
border: 1px solid #e9edf5;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.storestock-ui .menu-section .title {
|
||||
background: #f6f9ff;
|
||||
color: #012970;
|
||||
font-weight: 700;
|
||||
padding: .75rem .9rem;
|
||||
border-bottom: 1px solid #e9edf5;
|
||||
}
|
||||
|
||||
.storestock-ui .menu-list {
|
||||
padding: .5rem .9rem .75rem;
|
||||
}
|
||||
|
||||
.storestock-ui .menu-list a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .45rem;
|
||||
color: #4154f1;
|
||||
padding: .38rem 0;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.storestock-ui .menu-list a:hover {
|
||||
color: #012970;
|
||||
}
|
||||
|
||||
.storestock-ui .search-bar {
|
||||
display: grid;
|
||||
grid-template-columns: 170px 1fr 300px;
|
||||
gap: .7rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.storestock-ui .table-wrap {
|
||||
border: 1px solid #ebeef4;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.storestock-ui .insight-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(280px, 1fr));
|
||||
gap: .9rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.storestock-ui .mini-badge {
|
||||
display: inline-block;
|
||||
min-width: 46px;
|
||||
text-align: center;
|
||||
border-radius: 999px;
|
||||
padding: .2rem .45rem;
|
||||
font-size: .75rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.storestock-ui .mini-badge.warn {
|
||||
background: #fff6e5;
|
||||
color: #ad6800;
|
||||
}
|
||||
|
||||
.storestock-ui .mini-badge.danger {
|
||||
background: #ffe8e8;
|
||||
color: #b42318;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.storestock-ui .stats-grid,
|
||||
.storestock-ui .action-grid,
|
||||
.storestock-ui .panel-grid,
|
||||
.storestock-ui .menu-grid,
|
||||
.storestock-ui .search-bar {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.storestock-ui .insight-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
151
app/Views/storestock/dashboard.php
Normal file
151
app/Views/storestock/dashboard.php
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
<?= $this->include('storestock/_niceadmin_theme') ?>
|
||||
<div class="storestock-ui">
|
||||
<?php
|
||||
$stats = $stats ?? [];
|
||||
$lists = $lists ?? [];
|
||||
$stockValue = (float) ($stats['stock_value'] ?? 0);
|
||||
?>
|
||||
<section class="content-header">
|
||||
<h1>Store Stock <small>Dashboard</small></h1>
|
||||
</section>
|
||||
<section class="content">
|
||||
<div class="module-hero">
|
||||
<h4>Hospital Stock Control Center</h4>
|
||||
<p>Track indents, monitor stock movement, and jump to master controls from one screen.</p>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<span class="label">Indents Today</span>
|
||||
<span class="value"><?= (int) ($stats['today_indents'] ?? 0) ?></span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="label">Indents This Month</span>
|
||||
<span class="value"><?= (int) ($stats['month_indents'] ?? 0) ?></span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="label">Low Stock Items</span>
|
||||
<span class="value"><?= (int) ($stats['low_stock_count'] ?? 0) ?></span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="label">Near Expiry (60 Days)</span>
|
||||
<span class="value"><?= (int) ($stats['near_expiry_count'] ?? 0) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid" style="grid-template-columns: repeat(2, minmax(180px, 1fr));">
|
||||
<div class="stat-card">
|
||||
<span class="label">Pending Purchase Invoices</span>
|
||||
<span class="value"><?= (int) ($stats['pending_purchase'] ?? 0) ?></span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="label">Approx. Current Stock Value</span>
|
||||
<span class="value">Rs. <?= number_format($stockValue, 2) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-grid">
|
||||
<a class="action-card" href="javascript:load_form_div('/Storestock/Indent_List','maindiv','Store : Indent');">
|
||||
<span class="icon"><i class="fa fa-shopping-cart"></i></span>
|
||||
<h5>Indent Desk</h5>
|
||||
<p>Create and process departmental item requests.</p>
|
||||
</a>
|
||||
<a class="action-card" href="javascript:load_form_div('/Storestock/Report_2','maindiv','Day Report :Store');">
|
||||
<span class="icon"><i class="fa fa-line-chart"></i></span>
|
||||
<h5>Day Report</h5>
|
||||
<p>Review daily issue trend and transaction summary.</p>
|
||||
</a>
|
||||
<a class="action-card" href="javascript:load_form_div('/Storestock/store_stock','maindiv','Store Stock : Store');">
|
||||
<span class="icon"><i class="fa fa-barcode"></i></span>
|
||||
<h5>Stock Search</h5>
|
||||
<p>Find real-time availability with reorder controls.</p>
|
||||
</a>
|
||||
<a class="action-card" href="javascript:load_form_div('/Storestock/main_store','maindiv','Store Main : Store');">
|
||||
<span class="icon"><i class="fa fa-cogs"></i></span>
|
||||
<h5>Store Controls</h5>
|
||||
<p>Open masters, inventory, and procurement tools.</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="box" style="margin-top:1rem;">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Workspace</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div id="maindiv" class="row"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="insight-grid">
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Low Stock Alerts</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="table-wrap">
|
||||
<table class="table table-bordered table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item</th>
|
||||
<th style="width:120px;">Current Unit</th>
|
||||
<th style="width:140px;">Reorder Threshold</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $lowStockItems = $lists['low_stock_items'] ?? []; ?>
|
||||
<?php if (empty($lowStockItems)) { ?>
|
||||
<tr><td colspan="3" class="text-center text-muted">No low-stock items right now.</td></tr>
|
||||
<?php } ?>
|
||||
<?php foreach ($lowStockItems as $row) { ?>
|
||||
<tr>
|
||||
<td><?= esc($row['item_name'] ?? '') ?></td>
|
||||
<td><?= esc($row['cur_units'] ?? 0) ?></td>
|
||||
<td><?= esc($row['re_order_qty'] ?? 0) ?> x <?= esc($row['packing'] ?? 1) ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Near Expiry Alerts</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="table-wrap">
|
||||
<table class="table table-bordered table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item / Batch</th>
|
||||
<th style="width:100px;">Expiry</th>
|
||||
<th style="width:90px;">Days Left</th>
|
||||
<th style="width:95px;">Cur Packs</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $nearExpiryItems = $lists['near_expiry_items'] ?? []; ?>
|
||||
<?php if (empty($nearExpiryItems)) { ?>
|
||||
<tr><td colspan="4" class="text-center text-muted">No near-expiry batches in next 60 days.</td></tr>
|
||||
<?php } ?>
|
||||
<?php foreach ($nearExpiryItems as $row) { ?>
|
||||
<tr>
|
||||
<td><?= esc($row['item_name'] ?? '') ?> <span class="text-muted">(<?= esc($row['batch_no'] ?? '-') ?>)</span></td>
|
||||
<td><?= esc($row['expiry_str'] ?? '-') ?></td>
|
||||
<td>
|
||||
<span class="mini-badge <?= ((int) ($row['days_left'] ?? 0)) <= 15 ? 'danger' : 'warn' ?>">
|
||||
<?= (int) ($row['days_left'] ?? 0) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td><?= esc($row['cur_packs'] ?? 0) ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
47
app/Views/storestock/day_report.php
Normal file
47
app/Views/storestock/day_report.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?= $this->include('storestock/_niceadmin_theme') ?>
|
||||
<div class="storestock-ui">
|
||||
<div class="col-md-12">
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Day Report - Store</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<form class="form-day-report" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label>From Date</label>
|
||||
<input type="date" class="form-control input-sm" id="date_from" name="date_from"
|
||||
value="<?= date('Y-m-d') ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label>To Date</label>
|
||||
<input type="date" class="form-control input-sm" id="date_to" name="date_to"
|
||||
value="<?= date('Y-m-d') ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group" style="margin-top:24px;">
|
||||
<button type="submit" class="btn btn-info btn-sm">Generate Report</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div id="day_report_result" class="row"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('form.form-day-report').on('submit', function (e) {
|
||||
e.preventDefault();
|
||||
$('#day_report_result').html('<p class="text-muted"><i class="fa fa-spinner fa-spin"></i> Loading...</p>');
|
||||
// Day report endpoint to be implemented
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
103
app/Views/storestock/final_invoice.php
Normal file
103
app/Views/storestock/final_invoice.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?= $this->include('storestock/_niceadmin_theme') ?>
|
||||
<div class="storestock-ui">
|
||||
<div id="Medical_invoice_final">
|
||||
<section class="content-header">
|
||||
<h1>Indent <small>No. : <?= esc($invoice_stock_master[0]->indent_code ?? '') ?></small></h1>
|
||||
</section>
|
||||
<section class="content">
|
||||
<?= csrf_field() ?>
|
||||
<div class="box box-danger">
|
||||
<div class="box-header">
|
||||
<div class="row">
|
||||
<input type="hidden" id="location_type" name="location_type" value="<?= $invoice_stock_master[0]->location_type ?? 0 ?>" />
|
||||
<input type="hidden" id="location_id" name="location_id" value="<?= $invoice_stock_master[0]->location_id ?? 0 ?>" />
|
||||
<input type="hidden" id="indent_id" name="indent_id" value="<?= $invoice_stock_master[0]->id ?? 0 ?>" />
|
||||
<?php if (($invoice_stock_master[0]->location_id ?? 0) > 0) { ?>
|
||||
<div class="col-md-12">
|
||||
<p>
|
||||
<strong>Location Name :</strong> <?= esc($invoice_stock_master[0]->issued_name) ?>
|
||||
<strong>/ Indent No. :</strong> <?= esc($invoice_stock_master[0]->indent_code) ?>
|
||||
<strong>/ Date :</strong> <?= date('d-m-Y', strtotime($invoice_stock_master[0]->indent_date)) ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row" id="show_item_list">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Item Code</th>
|
||||
<th>Item Name</th>
|
||||
<th>Formulation</th>
|
||||
<th>Batch No</th>
|
||||
<th>Exp.</th>
|
||||
<th>Rate</th>
|
||||
<th>Qty.</th>
|
||||
<th>Price</th>
|
||||
<th>Disc.</th>
|
||||
<th>HSNCODE</th>
|
||||
<th>CGST</th>
|
||||
<th>SGST</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$srno = 0;
|
||||
foreach ($inv_items as $row) {
|
||||
$srno++;
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $srno ?></td>
|
||||
<td><?= esc($row->item_code) ?></td>
|
||||
<td><?= esc($row->item_Name) ?></td>
|
||||
<td><?= esc($row->formulation) ?></td>
|
||||
<td><?= esc($row->batch_no) ?></td>
|
||||
<td><?= esc($row->expiry) ?></td>
|
||||
<td><?= $row->price ?></td>
|
||||
<td><?= $row->qty ?></td>
|
||||
<td><?= $row->amount ?></td>
|
||||
<td><?= $row->disc_amount ?></td>
|
||||
<td><?= esc($row->HSNCODE) ?></td>
|
||||
<td><?= $row->CGST ?? 0 ?></td>
|
||||
<td><?= $row->SGST ?? 0 ?></td>
|
||||
<td><?= $row->tamount ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="11">Gross Total</th>
|
||||
<th><?= $invoiceGtotal[0]->Gtotal ?? 0 ?></th>
|
||||
<th><?= $invoiceGtotal[0]->tamt ?? 0 ?></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-print">
|
||||
<div class="col-xs-6">
|
||||
<a href="/Storestock/print_single_indent/<?= $invoice_stock_master[0]->id ?>"
|
||||
target="_blank" class="btn btn-default">
|
||||
<i class="fa fa-print"></i> Print
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
document.title = 'Indent : <?= esc($invoice_stock_master[0]->issued_name ?? '') ?> / <?= esc($invoice_stock_master[0]->indent_code ?? '') ?>';
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
261
app/Views/storestock/indent_edit.php
Normal file
261
app/Views/storestock/indent_edit.php
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
<?= $this->include('storestock/_niceadmin_theme') ?>
|
||||
<div class="storestock-ui">
|
||||
<section class="content-header">
|
||||
<h1>
|
||||
Indent No. <?= esc($invoice_stock_master[0]->indent_code ?? '') ?>
|
||||
<small>Edit</small>
|
||||
</h1>
|
||||
</section>
|
||||
<section class="content">
|
||||
<?= csrf_field() ?>
|
||||
<div class="col-md-8" style="padding: 5px;">
|
||||
<div class="box box-danger">
|
||||
<div class="box-header">
|
||||
<div class="row">
|
||||
<input type="hidden" id="location_type" name="location_type" value="<?= $invoice_stock_master[0]->location_type ?? 0 ?>" />
|
||||
<input type="hidden" id="location_id" name="location_id" value="<?= $invoice_stock_master[0]->location_id ?? 0 ?>" />
|
||||
<input type="hidden" id="indent_id" name="indent_id" value="<?= $invoice_stock_master[0]->id ?? 0 ?>" />
|
||||
<?php if (($invoice_stock_master[0]->location_id ?? 0) > 0) { ?>
|
||||
<div class="col-md-12">
|
||||
<p>
|
||||
<strong>Location Name :</strong> <?= esc($invoice_stock_master[0]->issued_name) ?>
|
||||
<strong>/ Indent No. :</strong> <?= esc($invoice_stock_master[0]->indent_code) ?>
|
||||
<strong>/ Date :</strong> <?= date('d-m-Y', strtotime($invoice_stock_master[0]->indent_date)) ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="col-md-12">
|
||||
<!-- Item List (AJAX-refreshable) -->
|
||||
<div class="row">
|
||||
<div id="show_item_list">
|
||||
<?= $content ?>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div id="show_add_item_list"></div>
|
||||
</div>
|
||||
|
||||
<!-- Add Item Form -->
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<label for="input_drug">Product Search:</label>
|
||||
<input class="form-control input-sm" name="input_drug" id="input_drug"
|
||||
placeholder="Item Code / Item Name" type="text" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<span class="text-muted" id="input_product_code"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="hidden" id="l_ssno" name="l_ssno" value="0" />
|
||||
<input type="hidden" id="item_code" name="item_code" value="0" />
|
||||
<p class="text-red">
|
||||
<span class="text-green" id="input_product_name"></span>
|
||||
<span class="text-light-blue" id="input_batch"></span>
|
||||
<span class="text-lead" id="input_product_mrp"></span>
|
||||
<span class="text-yellow" id="stock_product_qty"></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label>Unit Rate</label>
|
||||
<input class="form-control input-sm" id="input_product_unit_rate"
|
||||
name="input_product_unit_rate" placeholder="Unit Rate" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label>Qty</label>
|
||||
<input class="form-control number input-sm" id="input_product_qty"
|
||||
name="input_product_qty" placeholder="Qty (Tabs/Units)" type="text"
|
||||
value="0" autocomplete="off" />
|
||||
<input type="hidden" id="hid_c_qty" name="hid_c_qty" value="0" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label>Disc %</label>
|
||||
<input class="form-control number input-sm" id="input_disc"
|
||||
name="input_disc" placeholder="Discount %" type="text"
|
||||
value="0" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<button type="button" class="btn btn-primary" onclick="add_item_invoice()">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /Add Item Form -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<button type="button" class="btn btn-success" id="finalinvoice">Final Invoice</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4" style="padding: 5px;">
|
||||
<div class="box box-info">
|
||||
<div class="box-header with-border">
|
||||
<h4 class="box-title">Search Medicine</h4>
|
||||
</div>
|
||||
<div class="box-body" style="overflow:scroll;" id="search_body_part"></div>
|
||||
<div class="box-footer" id="search_footer_part"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
document.title = 'Indent : <?= esc($invoice_stock_master[0]->issued_name ?? '') ?> / <?= esc($invoice_stock_master[0]->indent_code ?? '') ?>';
|
||||
|
||||
// Final invoice button
|
||||
$('#finalinvoice').click(function () {
|
||||
var inv_id = $('#indent_id').val();
|
||||
load_form_div('/Storestock/final_invoice/' + inv_id, 'maindiv');
|
||||
});
|
||||
|
||||
// Autocomplete
|
||||
$("#input_drug").autocomplete({
|
||||
source: function (request, response) {
|
||||
$.getJSON('/Storestock/get_drug', { term: request.term }, response);
|
||||
},
|
||||
minLength: 2,
|
||||
select: function (event, ui) {
|
||||
$('#input_product_code').text(ui.item.l_item_code);
|
||||
$('#input_product_name').text(ui.item.value);
|
||||
$('#input_batch').text(' | Batch: ' + ui.item.l_Batch);
|
||||
$('#input_product_mrp').text(' | MRP: ' + ui.item.l_mrp);
|
||||
$('#input_product_unit_rate').val(ui.item.l_unit_rate);
|
||||
$('#stock_product_qty').text(' | Qty: ' + ui.item.l_c_qty);
|
||||
$('#hid_c_qty').val(ui.item.l_c_qty);
|
||||
$('#l_ssno').val(ui.item.l_ss_no);
|
||||
$('#item_code').val(ui.item.l_item_code);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function add_item_invoice() {
|
||||
var inv_id = $('#indent_id').val();
|
||||
var l_ssno = $('#l_ssno').val();
|
||||
var input_qty = $('#input_product_qty').val();
|
||||
var hid_c_qty = $('#hid_c_qty').val();
|
||||
var product_unit_rate = $('#input_product_unit_rate').val();
|
||||
var csrf_value = $('input[name="<?= csrf_token() ?>"]').val();
|
||||
var wait = parseInt($('#wait_for_next').val() || '0');
|
||||
|
||||
if (wait > 0) {
|
||||
notify('error', 'Please Attention', 'Wait for last process');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parseInt(l_ssno) < 1) {
|
||||
notify('error', 'Please Attention', 'Select Product First');
|
||||
$('#input_drug').focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parseInt(input_qty) < 1) {
|
||||
notify('error', 'Please Attention', 'Qty. is 0');
|
||||
$('#input_product_qty').focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parseInt(input_qty) > parseInt(hid_c_qty)) {
|
||||
notify('error', 'Please Attention', 'Product Qty. is less than available: ' + hid_c_qty);
|
||||
$('#input_product_qty').focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
$('#wait_for_next').val('1');
|
||||
setTimeout(function () { $('#wait_for_next').val('0'); }, 1000);
|
||||
|
||||
$.post('/Storestock/add_item/1', {
|
||||
"l_ssno" : l_ssno,
|
||||
"qty" : input_qty,
|
||||
"product_unit_rate" : product_unit_rate,
|
||||
"disc" : $('#input_disc').val(),
|
||||
"inv_id" : inv_id,
|
||||
'<?= csrf_token() ?>' : csrf_value
|
||||
}, function (data) {
|
||||
// Reset form
|
||||
$('#input_product_code').text('');
|
||||
$('#input_product_name').text('');
|
||||
$('#input_batch').text('');
|
||||
$('#input_product_mrp').text('');
|
||||
$('#input_product_unit_rate').val('');
|
||||
$('#l_ssno').val(0);
|
||||
$('#input_drug').val('');
|
||||
$('#input_product_qty').val('0');
|
||||
$('#input_disc').val('0');
|
||||
$('#stock_product_qty').text('');
|
||||
$('#input_drug').focus();
|
||||
|
||||
if (data.exist > 0) {
|
||||
notify('warning', 'Please Attention', 'This Item Already in List');
|
||||
}
|
||||
|
||||
if (data.insertid > 0) {
|
||||
$('#show_item_list').html(data.content);
|
||||
} else {
|
||||
notify('error', 'Please Attention', data.error);
|
||||
$('#wait_for_next').val('0');
|
||||
}
|
||||
|
||||
// Refresh CSRF
|
||||
$('input[name="<?= csrf_token() ?>"]').val(data.csrf_token_value);
|
||||
|
||||
}, 'json');
|
||||
}
|
||||
|
||||
function update_qty(item_id) {
|
||||
var new_qty = $('#input_qty_' + item_id).val();
|
||||
var csrf_value = $('input[name="<?= csrf_token() ?>"]').val();
|
||||
|
||||
$.post('/Storestock/add_item/2', {
|
||||
"itemid" : item_id,
|
||||
"u_qty" : new_qty,
|
||||
'<?= csrf_token() ?>' : csrf_value
|
||||
}, function (data) {
|
||||
if (data.insertid > 0) {
|
||||
$('#show_item_list').html(data.content);
|
||||
} else {
|
||||
notify('error', 'Please Attention', data.error);
|
||||
}
|
||||
$('input[name="<?= csrf_token() ?>"]').val(data.csrf_token_value);
|
||||
}, 'json');
|
||||
}
|
||||
|
||||
function remove_item_invoice(item_id) {
|
||||
if (!confirm('Are you sure you want to remove this item?')) return false;
|
||||
var csrf_value = $('input[name="<?= csrf_token() ?>"]').val();
|
||||
|
||||
$.post('/Storestock/add_item/3', {
|
||||
"itemid" : item_id,
|
||||
'<?= csrf_token() ?>' : csrf_value
|
||||
}, function (data) {
|
||||
if (data.insertid > 0) {
|
||||
$('#show_item_list').html(data.content);
|
||||
} else {
|
||||
notify('error', 'Please Attention', data.error || 'Delete failed');
|
||||
}
|
||||
$('input[name="<?= csrf_token() ?>"]').val(data.csrf_token_value);
|
||||
}, 'json');
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
98
app/Views/storestock/indent_item_list.php
Normal file
98
app/Views/storestock/indent_item_list.php
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
$item_codes = [];
|
||||
foreach ($inv_stock_item as $row) {
|
||||
$item_codes[] = $row->item_code;
|
||||
}
|
||||
$duplicate_codes = array_filter(
|
||||
array_count_values($item_codes),
|
||||
fn($c) => $c > 1
|
||||
);
|
||||
?>
|
||||
<table class="table table-striped table-condensed table-hover">
|
||||
<tr>
|
||||
<th style="width:10px;">#</th>
|
||||
<th>Item Name</th>
|
||||
<th>Batch No</th>
|
||||
<th>Exp.</th>
|
||||
<th style="text-align:right;">Rate</th>
|
||||
<th style="text-align:right;">S.Qty.</th>
|
||||
<th>Qty.</th>
|
||||
<th style="text-align:right;">Price</th>
|
||||
<th style="text-align:right;">Amount</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<?php
|
||||
$srno = 0;
|
||||
foreach ($inv_stock_item as $row) {
|
||||
$srno++;
|
||||
$style = '';
|
||||
|
||||
if (isset($row->sale_return) && $row->sale_return == 1) {
|
||||
$style = 'style="background:orange;"';
|
||||
}
|
||||
|
||||
if (isset($row->no_day) && $row->no_day < 90) {
|
||||
$style = 'style="color:red;"';
|
||||
}
|
||||
|
||||
if (isset($row->cur_qty) && $row->cur_qty < 6) {
|
||||
$style = 'style="color:red;"';
|
||||
}
|
||||
|
||||
$style_repeat = isset($duplicate_codes[$row->item_code]) ? 'style="color:green;"' : '';
|
||||
?>
|
||||
<tr <?= $style ?>>
|
||||
<td><?= $srno ?></td>
|
||||
<td <?= $style_repeat ?>><?= esc($row->item_Name) ?></td>
|
||||
<td><?= esc($row->batch_no) ?></td>
|
||||
<td><?= esc($row->expiry) ?></td>
|
||||
<td style="text-align:right;"><?= $row->price ?></td>
|
||||
<td style="text-align:right;"><?= $row->qty ?>
|
||||
<input type="hidden" id="hid_oldqty_<?= $row->id ?>" value="<?= $row->qty ?>" />
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!isset($row->sale_return) || $row->sale_return == 0) { ?>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" class="form-control input-sm number"
|
||||
style="width:50px;height:20px;padding:1px;"
|
||||
id="input_qty_<?= $row->id ?>"
|
||||
value="<?= $row->qty ?>" min="1">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-info btn-flat btn-xs"
|
||||
style="width:20px;height:20px;padding:1px;"
|
||||
onclick="update_qty(<?= $row->id ?>)">
|
||||
<i class="fa fa-edit"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</td>
|
||||
<td style="text-align:right;"><?= $row->amount ?></td>
|
||||
<td style="text-align:right;"><?= $row->tamount ?></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-danger btn-xs"
|
||||
onclick="remove_item_invoice(<?= $row->id ?>)">
|
||||
<i class="fa fa-remove"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<input type="hidden" id="srno" name="srno" value="<?= $srno ?>" />
|
||||
<input type="hidden" id="wait_for_next" name="wait_for_next" value="0" />
|
||||
<!-- Totals -->
|
||||
<tr>
|
||||
<th>#</th><th></th><th></th><th></th><th></th><th></th>
|
||||
<th>Gross Total</th>
|
||||
<th style="text-align:right;"><?= $invoiceGtotal[0]->Gtotal ?? 0 ?></th>
|
||||
<th style="text-align:right;"><?= $invoiceGtotal[0]->tamt ?? 0 ?></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>#</th><th></th><th></th><th></th><th></th>
|
||||
<th>Tax Total</th>
|
||||
<th colspan="2">TCGST : <?= $invoiceGtotal[0]->TCGST ?? 0 ?> / TSGST : <?= $invoiceGtotal[0]->TSGST ?? 0 ?></th>
|
||||
<th></th><th></th>
|
||||
</tr>
|
||||
</table>
|
||||
99
app/Views/storestock/indent_list.php
Normal file
99
app/Views/storestock/indent_list.php
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<?= $this->include('storestock/_niceadmin_theme') ?>
|
||||
<div class="storestock-ui">
|
||||
<div class="col-md-12">
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<div class="toolbar-row">
|
||||
<div class="left">
|
||||
<h3 class="box-title" style="margin-right:.65rem;">Indent List</h3>
|
||||
<button type="button" class="pill-btn active">Live Queue</button>
|
||||
<button type="button" class="pill-btn" onclick="$('#indent-grid').DataTable().ajax.reload();">Refresh</button>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a class="btn btn-primary btn-sm" href="javascript:load_form_div('/Storestock/new_indent','maindiv');">
|
||||
<i class="fa fa-plus"></i> New Indent
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="table-wrap">
|
||||
<table class="table table-bordered table-striped" id="indent-grid" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Indent No.</th>
|
||||
<th>Department</th>
|
||||
<th>Date</th>
|
||||
<th style="display:none;">ID</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<td><input type="text" data-column="0" class="form-control input-sm" placeholder="Indent No."></td>
|
||||
<td><input type="text" data-column="1" class="form-control input-sm" placeholder="Department"></td>
|
||||
<td><input type="date" data-column="2" class="form-control input-sm"></td>
|
||||
<td style="display:none;"></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
|
||||
var dataTable = $('#indent-grid').DataTable({
|
||||
"order": [[0, "desc"]],
|
||||
"processing": true,
|
||||
"serverSide": true,
|
||||
"ajax": {
|
||||
url: "Storestock/getIndentTable",
|
||||
dataType: "json",
|
||||
type: "post",
|
||||
data: function (d) {
|
||||
d['<?= csrf_token() ?>'] = '<?= csrf_hash() ?>';
|
||||
},
|
||||
error: function () {
|
||||
$("#indent-grid_processing").css("display", "none");
|
||||
$("#indent-grid tbody").html('<tr><td colspan="4">No data found in the server</td></tr>');
|
||||
}
|
||||
},
|
||||
"columnDefs": [
|
||||
{
|
||||
targets: 3,
|
||||
visible: false
|
||||
},
|
||||
{
|
||||
targets: 0,
|
||||
render: function (data, type, row) {
|
||||
if (type === 'display') {
|
||||
var url = "javascript:load_form_div('/Storestock/Indent_show/" +
|
||||
encodeURIComponent(row[3]) + "','maindiv','Inv.:" +
|
||||
encodeURIComponent(row[1]) + "/" + encodeURIComponent(row[3]) + " :Store');";
|
||||
return '<a href="' + url + '">' + data + '</a>';
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
$("#indent-grid_filter").css("display", "none");
|
||||
|
||||
$('input[type=date]').on('input', function () {
|
||||
var i = $(this).attr('data-column');
|
||||
var v = $(this).val();
|
||||
dataTable.columns(i).search(v).draw();
|
||||
});
|
||||
|
||||
$('input[type=text]').on('input', function () {
|
||||
var i = $(this).attr('data-column');
|
||||
var v = $(this).val();
|
||||
dataTable.columns(i).search(v).draw();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
47
app/Views/storestock/main_store_dashboard.php
Normal file
47
app/Views/storestock/main_store_dashboard.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?= $this->include('storestock/_niceadmin_theme') ?>
|
||||
<div class="storestock-ui">
|
||||
<section class="content-header">
|
||||
<h1>Store Main <small>Panel</small></h1>
|
||||
</section>
|
||||
<section class="content">
|
||||
<div class="module-hero">
|
||||
<h4>Store Operations</h4>
|
||||
<p>Use grouped controls to manage masters, inventory, and procurement operations.</p>
|
||||
</div>
|
||||
|
||||
<div class="menu-grid">
|
||||
<div class="menu-section">
|
||||
<div class="title">Master Controls</div>
|
||||
<div class="menu-list">
|
||||
<a href="javascript:load_form_div('/Storestock/SupplierList','maindiv','Supplier : Store');">
|
||||
<i class="fa fa-hand-pointer-o"></i> Supplier
|
||||
</a>
|
||||
<a href="javascript:load_form_div('/product_stock_master/drug_master_list','maindiv','Drug Master : Store');">
|
||||
<i class="fa fa-hand-pointer-o"></i> Item Master
|
||||
</a>
|
||||
<a href="javascript:load_form_div('/product_master/company_master_list','maindiv','Drug Company : Store');">
|
||||
<i class="fa fa-hand-pointer-o"></i> Company
|
||||
</a>
|
||||
<a href="javascript:load_form_div('/Storestock/location_master_list','maindiv','Location : Store');">
|
||||
<i class="fa fa-hand-pointer-o"></i> Location Master
|
||||
</a>
|
||||
<a href="javascript:load_form_div('/Master_data/Employee_master_list','maindiv','Employee : Store');">
|
||||
<i class="fa fa-hand-pointer-o"></i> Employee Master
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="menu-section">
|
||||
<div class="title">Inventory Controls</div>
|
||||
<div class="menu-list">
|
||||
<a href="javascript:load_form_div('/Storestock/Purchase','maindiv','Purchase : Store');">
|
||||
<i class="fa fa-hand-pointer-o"></i> Purchase
|
||||
</a>
|
||||
<a href="javascript:load_form_div('/Storestock/Purchase_return','maindiv','Purchase Return : Store');">
|
||||
<i class="fa fa-hand-pointer-o"></i> Purchase Return
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
92
app/Views/storestock/new_indent.php
Normal file
92
app/Views/storestock/new_indent.php
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<?= $this->include('storestock/_niceadmin_theme') ?>
|
||||
<div class="storestock-ui">
|
||||
<div class="col-md-12">
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">New Indent</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<?= csrf_field() ?>
|
||||
<div class="module-hero" style="margin-bottom:.9rem;">
|
||||
<h4>Create Indent Request</h4>
|
||||
<p>Select date, then choose request origin: location or employee.</p>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-bottom:.9rem;">
|
||||
<div class="col-md-4">
|
||||
<label>Date</label>
|
||||
<div class="input-group input-group-sm date">
|
||||
<div class="input-group-addon"><i class="fa fa-calendar"></i></div>
|
||||
<input class="form-control pull-right datepicker"
|
||||
id="date_indent" name="date_indent" type="text"
|
||||
data-inputmask="'alias': 'dd/mm/yyyy'" data-mask=""
|
||||
value="<?= date('d/m/Y') ?>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-grid">
|
||||
<div class="control-card">
|
||||
<h5><i class="fa fa-map-marker"></i> By Location</h5>
|
||||
<div class="form-group" style="margin-bottom:.5rem;">
|
||||
<label>Select Location</label>
|
||||
<select class="form-control select2" id="location_id" name="location_id">
|
||||
<?php foreach ($location_master as $row) { ?>
|
||||
<option value="<?= $row->l_id ?>"><?= esc($row->loc_name) ?></option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="add_indent(1)">
|
||||
<i class="fa fa-plus-circle"></i> Create from Location
|
||||
</button>
|
||||
<div class="help">Use when indent is raised for a ward/department location.</div>
|
||||
</div>
|
||||
|
||||
<div class="control-card">
|
||||
<h5><i class="fa fa-user"></i> By Employee</h5>
|
||||
<div class="form-group" style="margin-bottom:.5rem;">
|
||||
<label>Select Employee</label>
|
||||
<select class="form-control select2" id="emp_id" name="emp_id">
|
||||
<?php foreach ($employee_master as $row) { ?>
|
||||
<option value="<?= $row->emp_id ?>"><?= esc($row->emp_code) ?> [<?= esc($row->emp_name) ?>]</option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="btn btn-danger btn-sm" onclick="add_indent(2)">
|
||||
<i class="fa fa-plus-circle"></i> Create from Employee
|
||||
</button>
|
||||
<div class="help">Use when indent is raised in personal accountability mode.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('.select2').select2();
|
||||
|
||||
function add_indent(location_type) {
|
||||
var csrf_value = $('input[name="<?= csrf_token() ?>"]').val();
|
||||
var loc_id = 0;
|
||||
|
||||
if (location_type == 1) {
|
||||
loc_id = $('#location_id').val();
|
||||
} else if (location_type == 2) {
|
||||
loc_id = $('#emp_id').val();
|
||||
}
|
||||
|
||||
if (loc_id > 0) {
|
||||
$.post('/Storestock/Indent_create/' + location_type, {
|
||||
"loc_id": loc_id,
|
||||
"date_indent": $('#date_indent').val(),
|
||||
'<?= csrf_token() ?>': csrf_value
|
||||
}, function (data) {
|
||||
$('#maindiv').html(data);
|
||||
});
|
||||
} else {
|
||||
notify('error', 'Please Attention', 'Select a location or employee first');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
42
app/Views/storestock/stock_item_history.php
Normal file
42
app/Views/storestock/stock_item_history.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?= $this->include('storestock/_niceadmin_theme') ?>
|
||||
<div class="storestock-ui">
|
||||
<table class="table table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Pur. ID</th>
|
||||
<th>Supplier</th>
|
||||
<th>Inv.No / Date</th>
|
||||
<th>Bat.No.</th>
|
||||
<th>Exp.Dt.</th>
|
||||
<th>MRP</th>
|
||||
<th>Purchase</th>
|
||||
<th>Qty / Packing</th>
|
||||
<th>Unit Qty</th>
|
||||
<th>Sale U.Qty</th>
|
||||
<th>Lost Unit</th>
|
||||
<th>Curr.U.Qty</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($product_purchase_detail as $row) { ?>
|
||||
<tr>
|
||||
<td><?= esc($row->id) ?></td>
|
||||
<td><?= esc($row->name_supplier) ?></td>
|
||||
<td><?= esc($row->Invoice_no) ?><br /><?= esc($row->date_of_invoice) ?></td>
|
||||
<td><?= esc($row->batch_no) ?></td>
|
||||
<td><?= esc($row->expiry_date) ?></td>
|
||||
<td><?= esc($row->mrp) ?></td>
|
||||
<td><?= esc($row->purchase_price) ?></td>
|
||||
<td><?= esc($row->tqty) ?> / <?= esc($row->packing) ?></td>
|
||||
<td><?= esc($row->total_unit) ?></td>
|
||||
<td><?= esc($row->total_sale_unit) ?></td>
|
||||
<td><?= esc($row->total_lost_unit) ?></td>
|
||||
<td><?= esc($row->cur_unit) ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php if (empty($product_purchase_detail)) { ?>
|
||||
<tr><td colspan="12" class="text-center text-muted">No stock records found.</td></tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
99
app/Views/storestock/stock_search_result.php
Normal file
99
app/Views/storestock/stock_search_result.php
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<?= $this->include('storestock/_niceadmin_theme') ?>
|
||||
<div class="storestock-ui">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="stock_result_body" class="box-body" style="height:500px;overflow-y:auto;">
|
||||
<table id="stock_result_table" class="table table-bordered table-striped TableData">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:300px;">Item Name</th>
|
||||
<th>Current Pak.</th>
|
||||
<th>Current Unit Qty</th>
|
||||
<th>Total Sale Pak.</th>
|
||||
<th>Total Sale Unit Qty</th>
|
||||
<th>Lost Unit</th>
|
||||
<th>Package / Re-Order Qty</th>
|
||||
<th>Detail</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($stock_list as $row) { ?>
|
||||
<tr>
|
||||
<td>
|
||||
<p class="text-danger">
|
||||
<a href="javascript:void(0)"
|
||||
onclick="loadProductStock(<?= (int)$row->id ?>, <?= (float)($row->packing ?? 1) ?>, '<?= esc($row->item_name) ?>')">
|
||||
<?= esc($row->item_name) ?>
|
||||
</a>
|
||||
</p>
|
||||
</td>
|
||||
<td><p class="text-warning"><?= esc($row->C_Pak_Qty ?? '') ?></p></td>
|
||||
<td><p class="text-warning"><?= round($row->C_Unit_Stock_Qty ?? 0, 2) ?></p></td>
|
||||
<td><p class="text-warning"><?= round($row->C_Pak_Sale_Qty ?? 0, 2) ?></p></td>
|
||||
<td><p class="text-warning"><?= round($row->sale_unit ?? 0, 2) ?></p></td>
|
||||
<td><p class="text-danger"><?= round($row->total_lost_unit ?? 0, 2) ?></p></td>
|
||||
<td><p class="text-warning"><?= esc($row->packing ?? '') ?> / <?= esc($row->re_order_qty ?? '') ?></p></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-primary btn-xs"
|
||||
onclick="loadProductStock(<?= (int)$row->id ?>, <?= (float)($row->packing ?? 1) ?>, '<?= esc($row->item_name) ?>')">
|
||||
Show
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th style="width:300px;">Item Name</th>
|
||||
<th>Current Pak.</th>
|
||||
<th>Current Unit Qty</th>
|
||||
<th>Total Sale Pak.</th>
|
||||
<th>Total Sale Unit Qty</th>
|
||||
<th>Lost Unit</th>
|
||||
<th>Package / Re-Order Qty</th>
|
||||
<th>Detail</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Stock History Modal -->
|
||||
<div class="modal fade" id="productStockModal" tabindex="-1" role="dialog" aria-labelledby="productStockModalLabel">
|
||||
<div class="modal-dialog modal-lg" role="document" style="width:95%;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title" id="productStockModalLabel">Product Stock Detail</h4>
|
||||
</div>
|
||||
<div class="modal-body" style="overflow:auto;" id="productStockModalBody">
|
||||
<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
if ($.fn.DataTable) {
|
||||
$('#stock_result_table').DataTable({ "order": [[0, "asc"]] });
|
||||
}
|
||||
});
|
||||
|
||||
function loadProductStock(product_id, product_pak, product_name) {
|
||||
$('#productStockModalLabel').text('Stock Detail : ' + product_name);
|
||||
$('#productStockModalBody').html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Loading...</div>');
|
||||
$('#productStockModal').modal('show');
|
||||
|
||||
$.post('/Storestock/get_product_stock/' + product_id, {
|
||||
product_pak: product_pak,
|
||||
'<?= csrf_token() ?>': '<?= csrf_hash() ?>'
|
||||
}, function (data) {
|
||||
$('#productStockModalBody').html(data);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
104
app/Views/storestock/store_print.php
Normal file
104
app/Views/storestock/store_print.php
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<style>
|
||||
@page {
|
||||
margin-top: 4.2cm;
|
||||
margin-bottom: 1.2cm;
|
||||
margin-left: 1cm;
|
||||
margin-right: 0.5cm;
|
||||
margin-header: 0.5cm;
|
||||
margin-footer: 0.5cm;
|
||||
header: html_myHeader;
|
||||
footer: html_myFooter;
|
||||
}
|
||||
</style>
|
||||
|
||||
<htmlpageheader name="myHeader">
|
||||
<table style="font-size:12px;" cellpadding="5">
|
||||
<tr>
|
||||
<td style="width:60%;vertical-align:top;">
|
||||
<p align="center" style="font-size:25px;">
|
||||
<?php echo defined('H_Name') ? constant('H_Name') : ''; ?>
|
||||
</p>
|
||||
<p align="center" style="font-size:12px;">
|
||||
<?php echo defined('M_address') ? constant('M_address') : ''; ?>,
|
||||
<?php echo defined('H_address_2') ? constant('H_address_2') : ''; ?>
|
||||
</p>
|
||||
</td>
|
||||
<td style="width:40%;">
|
||||
<p>
|
||||
<strong>Location Name :</strong> <?= esc($invoice_stock_master[0]->issued_name ?? '') ?><br/>
|
||||
<strong>Indent No. :</strong> <?= esc($invoice_stock_master[0]->indent_code ?? '') ?><br/>
|
||||
<strong>Date :</strong> <?= esc($invoice_stock_master[0]->str_inv_date ?? '') ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h3 align="center">Indent</h3>
|
||||
</htmlpageheader>
|
||||
|
||||
<htmlpagefooter name="myFooter">
|
||||
<table width="100%" style="font-size:10px;">
|
||||
<tr>
|
||||
<td width="33%">Page : {PAGENO}/{nbpg}</td>
|
||||
<td width="67%" style="text-align:right;">
|
||||
Invoice No.: <?= esc($invoice_stock_master[0]->indent_code ?? '') ?>
|
||||
/ Location : <?= esc($invoice_stock_master[0]->issued_name ?? '') ?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</htmlpagefooter>
|
||||
|
||||
<table style="font-size:10px;width:100%;border-collapse:collapse;border-style:solid;border-width:0.1mm;padding:3mm;" autosize="1">
|
||||
<?php
|
||||
$srno = 0;
|
||||
$head_start = 0;
|
||||
foreach ($inv_items as $row) {
|
||||
if ($head_start == 0) {
|
||||
echo '<tr style="border-style:solid;border-width:0.1mm;"><td colspan="7" style="font-size:18px;"><b>Invoice ID : </b>'
|
||||
. esc($row->indent_code) . ' [<b>Dated : </b><i>' . esc($row->str_inv_date) . '</i>]</td></tr>';
|
||||
echo '<tr>';
|
||||
echo '<th style="width:20px;">#</th>';
|
||||
echo '<th align="left" style="width:200px;">Item Name</th>';
|
||||
echo '<th align="left">Batch No</th>';
|
||||
echo '<th>Exp.</th>';
|
||||
echo '<th align="right">Rate</th>';
|
||||
echo '<th align="right">Qty.</th>';
|
||||
echo '<th align="right">Gross Amt</th>';
|
||||
echo '</tr>';
|
||||
}
|
||||
$srno++;
|
||||
$head_start++;
|
||||
|
||||
if (isset($row->sale_return) && $row->sale_return == 1) {
|
||||
echo '<tr><td colspan="7">Sale Return</td></tr>';
|
||||
}
|
||||
|
||||
echo '<tr>';
|
||||
echo '<td style="width:20px;">' . $srno . '</td>';
|
||||
echo '<td style="width:200px;">' . esc($row->item_Name) . ' ' . esc($row->formulation) . '</td>';
|
||||
echo '<td>' . esc($row->batch_no) . '</td>';
|
||||
echo '<td>' . esc($row->expiry) . '</td>';
|
||||
echo '<td align="right">' . $row->price . '</td>';
|
||||
echo '<td align="right">' . $row->qty . '</td>';
|
||||
echo '<td align="right">' . $row->amount . '</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
|
||||
if (count($invoice_stock_master) > 0) {
|
||||
echo '<tr style="font-size:12px;border-style:solid;border-width:0.1mm;">';
|
||||
echo '<th style="width:20px;">#</th>';
|
||||
echo '<th colspan="5" align="right">Total</th>';
|
||||
echo '<th align="right">' . ($invoice_stock_master[0]->gross_amount ?? '') . '</th>';
|
||||
echo '</tr>';
|
||||
}
|
||||
?>
|
||||
</table>
|
||||
|
||||
<br/><br/>
|
||||
<table width="100%" style="font-size:12px;">
|
||||
<tr>
|
||||
<td style="width:60%;"></td>
|
||||
<td style="width:40%;text-align:center;">
|
||||
<b>For <?php echo defined('H_Name') ? constant('H_Name') : ''; ?></b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
101
app/Views/storestock/store_stock_search.php
Normal file
101
app/Views/storestock/store_stock_search.php
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<?= $this->include('storestock/_niceadmin_theme') ?>
|
||||
<div class="storestock-ui">
|
||||
<section class="content-header">
|
||||
<h1>Store Stock <small>Search</small></h1>
|
||||
</section>
|
||||
<section class="content">
|
||||
<div class="module-hero">
|
||||
<h4>Stock Visibility & Reorder Controls</h4>
|
||||
<p>Filter by schedule, quickly export, and inspect product movement batch-wise.</p>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<form role="form" class="form-stock-search" method="post" action="/Storestock/store_Stock_result">
|
||||
<?= csrf_field() ?>
|
||||
<div class="search-bar">
|
||||
<div>
|
||||
<label style="display:block; margin-bottom:.3rem;">Stock Mode</label>
|
||||
<label style="margin:0; font-weight:600; color:#012970;">
|
||||
<input id="chk_reorder" name="chk_reorder" type="checkbox"> ReOrder List
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style="display:block; margin-bottom:.3rem;">Drug Schedule</label>
|
||||
<select class="form-control select2" id="schedule_id" name="schedule_id[]"
|
||||
multiple="multiple" data-placeholder="Select a Schedule, Blank for All">
|
||||
<option value="1">Schedule H</option>
|
||||
<option value="2">Schedule H1</option>
|
||||
<option value="3">Schedule X</option>
|
||||
<option value="4">Schedule G</option>
|
||||
<option value="5">Narcotic</option>
|
||||
<option value="6">High Risk</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style="display:block; margin-bottom:.3rem;">Search Keyword</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<input class="form-control" type="text" id="txtsearch" name="txtsearch"
|
||||
placeholder="Item Name / Supplier Name">
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary btn-flat">Search</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-row" style="margin-top:.8rem; margin-bottom:0;">
|
||||
<div class="left"></div>
|
||||
<div class="right">
|
||||
<button type="button" id="btn_excel" class="btn btn-info btn-sm">
|
||||
<i class="fa fa-file-excel-o"></i> Excel Export
|
||||
</button>
|
||||
<button type="button" id="btn_excel_3" class="btn btn-default btn-sm">
|
||||
<i class="fa fa-table"></i> Batch-wise Excel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Search Result</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="searchresult" id="searchresult"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
$('.select2').select2();
|
||||
|
||||
$('form.form-stock-search').on('submit', function (e) {
|
||||
e.preventDefault();
|
||||
$.post('/Storestock/store_Stock_result', $(this).serialize(), function (data) {
|
||||
$('#searchresult').html(data);
|
||||
});
|
||||
});
|
||||
|
||||
$('#btn_excel').click(function () {
|
||||
var reorder = $('#chk_reorder').is(':checked') ? '1' : '0';
|
||||
var itemName = $('#txtsearch').val() || '-';
|
||||
var schIds = $('#schedule_id').val();
|
||||
var schStr = (schIds && schIds.length > 0) ? schIds.join('S') : '0';
|
||||
window.open('/Storestock/Stock_result_excel/' + reorder + '/' + encodeURIComponent(itemName) + '/' + schStr, '_blank');
|
||||
});
|
||||
|
||||
$('#btn_excel_3').click(function () {
|
||||
var reorder = $('#chk_reorder').is(':checked') ? '1' : '0';
|
||||
var itemName = $('#txtsearch').val() || '-';
|
||||
window.open('/Storestock/Stock_result_excel_3/0/0/' + encodeURIComponent(itemName) + '/' + reorder, '_blank');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
Loading…
Reference in New Issue
Block a user