feat: update storestock flow and ABDM bridge handling
This commit is contained in:
parent
1790be3fca
commit
1bac1f6e04
|
|
@ -7133,22 +7133,9 @@ class Opd_prescription extends BaseController
|
|||
$code = trim((string) $this->request->getPost('investigation_code'));
|
||||
$name = trim((string) $this->request->getPost('investigation_name'));
|
||||
|
||||
// Profile chips may send only investigation name; resolve code from master to avoid unique-key collisions.
|
||||
if ($code === '' && $name !== '' && $this->db->tableExists('investigation')) {
|
||||
$invFields = $this->db->getFieldNames('investigation');
|
||||
$invCodeField = $this->resolveFirstField($invFields, ['Code', 'code']);
|
||||
$invNameField = $this->resolveFirstField($invFields, ['Name', 'name']);
|
||||
if ($invCodeField !== null && $invNameField !== null) {
|
||||
$resolved = $this->db->table('investigation')
|
||||
->select($invCodeField . ' as code')
|
||||
->where($invNameField, $name)
|
||||
->limit(1)
|
||||
->get()
|
||||
->getRowArray();
|
||||
if (! empty($resolved['code'])) {
|
||||
$code = trim((string) $resolved['code']);
|
||||
}
|
||||
}
|
||||
// Resolve profile shorthand names (CBC/LFT/KFT etc.) to canonical codes.
|
||||
if (($code === '' || $code === '0') && $name !== '') {
|
||||
$code = $this->resolveInvestigationCodeByText($name, $this->buildInvestigationLookupMaps());
|
||||
}
|
||||
|
||||
if ($opdId <= 0 || ($code === '' && $name === '')) {
|
||||
|
|
@ -7229,6 +7216,303 @@ class Opd_prescription extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
public function investigation_add_bulk()
|
||||
{
|
||||
$startedAt = microtime(true);
|
||||
$respond = function (array $payload, int $statusCode = 200) use ($startedAt) {
|
||||
$payload['elapsed_ms'] = (int) round((microtime(true) - $startedAt) * 1000);
|
||||
return $this->response->setStatusCode($statusCode)->setJSON($payload);
|
||||
};
|
||||
|
||||
if (! $this->request->isAJAX()) {
|
||||
return $respond(['update' => 0, 'error_text' => 'Invalid request'], 400);
|
||||
}
|
||||
|
||||
$opdId = (int) $this->request->getPost('opd_id');
|
||||
$sessionId = (int) $this->request->getPost('opd_session_id');
|
||||
|
||||
$rawTests = $this->request->getPost('tests');
|
||||
$tests = [];
|
||||
if (is_string($rawTests) && $rawTests !== '') {
|
||||
$decoded = json_decode($rawTests, true);
|
||||
if (is_array($decoded)) {
|
||||
$tests = $decoded;
|
||||
}
|
||||
} elseif (is_array($rawTests)) {
|
||||
$tests = $rawTests;
|
||||
}
|
||||
|
||||
if ($opdId <= 0 || empty($tests)) {
|
||||
return $respond(['update' => 0, 'error_text' => 'Invalid bulk payload']);
|
||||
}
|
||||
|
||||
$table = $this->findExistingTable(['opd_prescription_investigation']);
|
||||
if ($table === null) {
|
||||
return $respond(['update' => 0, 'error_text' => 'Investigation table not found']);
|
||||
}
|
||||
|
||||
$sessionId = $this->ensurePrescriptionSession($opdId, $sessionId);
|
||||
if ($sessionId <= 0) {
|
||||
return $respond(['update' => 0, 'error_text' => 'Unable to create prescription session']);
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames($table);
|
||||
$hasOpdPreId = in_array('opd_pre_id', $fields, true);
|
||||
$hasOpdId = in_array('opd_id', $fields, true);
|
||||
$hasCode = in_array('investigation_code', $fields, true);
|
||||
$hasName = in_array('investigation_name', $fields, true);
|
||||
|
||||
$existingRowsBuilder = $this->db->table($table);
|
||||
if ($hasOpdPreId) {
|
||||
$existingRowsBuilder->where('opd_pre_id', $sessionId);
|
||||
} elseif ($hasOpdId) {
|
||||
$existingRowsBuilder->where('opd_id', $opdId);
|
||||
} else {
|
||||
return $respond(['update' => 0, 'error_text' => 'Invalid investigation table structure']);
|
||||
}
|
||||
|
||||
$existingRows = $existingRowsBuilder->get()->getResultArray();
|
||||
$existingByCode = [];
|
||||
$existingByName = [];
|
||||
foreach ($existingRows as $row) {
|
||||
$exCode = trim((string) ($row['investigation_code'] ?? ''));
|
||||
$exName = trim((string) ($row['investigation_name'] ?? ''));
|
||||
if ($exCode !== '' && $exCode !== '0') {
|
||||
$existingByCode[strtolower($exCode)] = true;
|
||||
}
|
||||
if ($exName !== '') {
|
||||
$existingByName[strtolower($exName)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$invLookup = $this->buildInvestigationLookupMaps();
|
||||
|
||||
$seenPayload = [];
|
||||
$insertRows = [];
|
||||
$added = 0;
|
||||
$skipped = 0;
|
||||
|
||||
foreach ($tests as $test) {
|
||||
$name = trim((string) (($test['name'] ?? $test['investigation_name'] ?? '')));
|
||||
$code = trim((string) (($test['code'] ?? $test['investigation_code'] ?? '')));
|
||||
|
||||
if ($name === '' && $code === '') {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($code === '' || $code === '0') && $name !== '') {
|
||||
$code = $this->resolveInvestigationCodeByText($name, $invLookup);
|
||||
}
|
||||
|
||||
$payloadKey = strtolower($code . '|' . $name);
|
||||
if (isset($seenPayload[$payloadKey])) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
$seenPayload[$payloadKey] = true;
|
||||
|
||||
$duplicateByCode = ($code !== '' && isset($existingByCode[strtolower($code)]));
|
||||
$duplicateByName = ($name !== '' && isset($existingByName[strtolower($name)]));
|
||||
if ($duplicateByCode || $duplicateByName) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$row = [];
|
||||
if ($hasOpdId) {
|
||||
$row['opd_id'] = $opdId;
|
||||
}
|
||||
if ($hasOpdPreId) {
|
||||
$row['opd_pre_id'] = $sessionId;
|
||||
}
|
||||
if ($hasCode) {
|
||||
$row['investigation_code'] = ($code === '' || $code === '0') ? '' : $code;
|
||||
}
|
||||
if ($hasName) {
|
||||
$row['investigation_name'] = $name;
|
||||
}
|
||||
$insertRows[] = $row;
|
||||
|
||||
if ($code !== '' && $code !== '0') {
|
||||
$existingByCode[strtolower($code)] = true;
|
||||
}
|
||||
if ($name !== '') {
|
||||
$existingByName[strtolower($name)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($insertRows as $row) {
|
||||
try {
|
||||
$this->db->table($table)->insert($row);
|
||||
$added++;
|
||||
$insertId = (int) $this->db->insertID();
|
||||
$this->auditClinicalUpdate('opd_prescription_investigation', 'added', $insertId, null, $row);
|
||||
} catch (\Throwable $e) {
|
||||
$msg = strtolower($e->getMessage());
|
||||
if (strpos($msg, 'duplicate entry') !== false) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
$result = [
|
||||
'update' => 1,
|
||||
'opd_session_id' => $sessionId,
|
||||
'added_count' => $added,
|
||||
'skipped_count' => $skipped,
|
||||
'total_count' => count($tests),
|
||||
'error_text' => $added > 0 ? 'Investigations added' : 'No new investigations added',
|
||||
'csrfName' => csrf_token(),
|
||||
'csrfHash' => csrf_hash(),
|
||||
];
|
||||
|
||||
log_message(
|
||||
'info',
|
||||
'investigation_add_bulk opd_id={opd_id} session_id={session_id} total={total} added={added} skipped={skipped} elapsed_ms={elapsed_ms}',
|
||||
[
|
||||
'opd_id' => $opdId,
|
||||
'session_id' => $sessionId,
|
||||
'total' => count($tests),
|
||||
'added' => $added,
|
||||
'skipped' => $skipped,
|
||||
'elapsed_ms' => (int) round((microtime(true) - $startedAt) * 1000),
|
||||
]
|
||||
);
|
||||
|
||||
return $respond($result);
|
||||
}
|
||||
|
||||
private function buildInvestigationLookupMaps(): array
|
||||
{
|
||||
$out = [
|
||||
'by_name' => [],
|
||||
'by_short' => [],
|
||||
'by_alias' => [],
|
||||
];
|
||||
|
||||
if (! $this->db->tableExists('investigation')) {
|
||||
return $out;
|
||||
}
|
||||
|
||||
$invFields = $this->db->getFieldNames('investigation');
|
||||
$invCodeField = $this->resolveFirstField($invFields, ['Code', 'code']);
|
||||
$invNameField = $this->resolveFirstField($invFields, ['Name', 'name']);
|
||||
$invShortField = $this->resolveFirstField($invFields, ['short_name', 'shortName', 'short']);
|
||||
|
||||
if ($invCodeField === null || $invNameField === null) {
|
||||
return $out;
|
||||
}
|
||||
|
||||
$select = $invCodeField . ' as code, ' . $invNameField . ' as name';
|
||||
if ($invShortField !== null) {
|
||||
$select .= ', ' . $invShortField . ' as short_name';
|
||||
}
|
||||
|
||||
$rows = $this->db->table('investigation')->select($select)->get()->getResultArray();
|
||||
foreach ($rows as $row) {
|
||||
$code = trim((string) ($row['code'] ?? ''));
|
||||
if ($code === '' || $code === '0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = trim((string) ($row['name'] ?? ''));
|
||||
$short = trim((string) ($row['short_name'] ?? ''));
|
||||
|
||||
$nameKey = $this->normalizeInvestigationLookupKey($name);
|
||||
if ($nameKey !== '' && ! isset($out['by_name'][$nameKey])) {
|
||||
$out['by_name'][$nameKey] = $code;
|
||||
}
|
||||
|
||||
$shortKey = $this->normalizeInvestigationLookupKey($short);
|
||||
if ($shortKey !== '' && ! isset($out['by_short'][$shortKey])) {
|
||||
$out['by_short'][$shortKey] = $code;
|
||||
}
|
||||
|
||||
if (preg_match('/\(([A-Za-z0-9\-\s\.]{2,20})\)/', $name, $m) === 1) {
|
||||
$aliasKey = $this->normalizeInvestigationLookupKey((string) ($m[1] ?? ''));
|
||||
if ($aliasKey !== '' && ! isset($out['by_alias'][$aliasKey])) {
|
||||
$out['by_alias'][$aliasKey] = $code;
|
||||
}
|
||||
}
|
||||
|
||||
$acronym = $this->buildInvestigationAcronym($name);
|
||||
if ($acronym !== '' && ! isset($out['by_alias'][$acronym])) {
|
||||
$out['by_alias'][$acronym] = $code;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function resolveInvestigationCodeByText(string $text, array $lookup): string
|
||||
{
|
||||
$key = $this->normalizeInvestigationLookupKey($text);
|
||||
if ($key === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (isset($lookup['by_name'][$key])) {
|
||||
return (string) $lookup['by_name'][$key];
|
||||
}
|
||||
if (isset($lookup['by_short'][$key])) {
|
||||
return (string) $lookup['by_short'][$key];
|
||||
}
|
||||
if (isset($lookup['by_alias'][$key])) {
|
||||
return (string) $lookup['by_alias'][$key];
|
||||
}
|
||||
|
||||
// Common profile aliases seen in legacy shortcuts.
|
||||
$aliasFallback = [
|
||||
'kft' => 'rft',
|
||||
];
|
||||
$fallback = $aliasFallback[$key] ?? '';
|
||||
if ($fallback !== '' && isset($lookup['by_alias'][$fallback])) {
|
||||
return (string) $lookup['by_alias'][$fallback];
|
||||
}
|
||||
if ($fallback !== '' && isset($lookup['by_short'][$fallback])) {
|
||||
return (string) $lookup['by_short'][$fallback];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function normalizeInvestigationLookupKey(string $value): string
|
||||
{
|
||||
$value = trim(strtolower($value));
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
return (string) (preg_replace('/[^a-z0-9]+/', '', $value) ?? '');
|
||||
}
|
||||
|
||||
private function buildInvestigationAcronym(string $name): string
|
||||
{
|
||||
$plain = preg_replace('/\([^)]*\)/', ' ', $name) ?? $name;
|
||||
$plain = trim((string) preg_replace('/\s+/', ' ', $plain));
|
||||
if ($plain === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$stopWords = [
|
||||
'and', 'of', 'for', 'the', 'test', 'panel', 'routine', 'view', 'complete', 'count',
|
||||
];
|
||||
|
||||
$parts = preg_split('/\s+/', strtolower($plain)) ?: [];
|
||||
$acronym = '';
|
||||
foreach ($parts as $part) {
|
||||
$part = trim((string) preg_replace('/[^a-z0-9]/', '', $part));
|
||||
if ($part === '' || in_array($part, $stopWords, true)) {
|
||||
continue;
|
||||
}
|
||||
$acronym .= substr($part, 0, 1);
|
||||
}
|
||||
|
||||
return strlen($acronym) >= 2 ? $acronym : '';
|
||||
}
|
||||
|
||||
public function investigation_remove($id)
|
||||
{
|
||||
$table = $this->findExistingTable(['opd_prescription_investigation']);
|
||||
|
|
@ -7247,6 +7531,81 @@ class Opd_prescription extends BaseController
|
|||
return $this->response->setJSON(['update' => 1, 'error_text' => 'Investigation removed']);
|
||||
}
|
||||
|
||||
public function investigation_remove_bulk()
|
||||
{
|
||||
$startedAt = microtime(true);
|
||||
$respond = function (array $payload, int $statusCode = 200) use ($startedAt) {
|
||||
$payload['elapsed_ms'] = (int) round((microtime(true) - $startedAt) * 1000);
|
||||
return $this->response->setStatusCode($statusCode)->setJSON($payload);
|
||||
};
|
||||
|
||||
if (! $this->request->isAJAX()) {
|
||||
return $respond(['update' => 0, 'error_text' => 'Invalid request'], 400);
|
||||
}
|
||||
|
||||
$rawIds = $this->request->getPost('ids');
|
||||
$ids = [];
|
||||
|
||||
if (is_array($rawIds)) {
|
||||
$ids = $rawIds;
|
||||
} elseif (is_string($rawIds) && trim($rawIds) !== '') {
|
||||
$decoded = json_decode($rawIds, true);
|
||||
if (is_array($decoded)) {
|
||||
$ids = $decoded;
|
||||
} else {
|
||||
$ids = explode(',', $rawIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = array_values(array_unique(array_filter(array_map(static function ($id) {
|
||||
return (int) $id;
|
||||
}, $ids), static function ($id) {
|
||||
return $id > 0;
|
||||
})));
|
||||
|
||||
if (empty($ids)) {
|
||||
return $respond(['update' => 0, 'error_text' => 'No investigations selected']);
|
||||
}
|
||||
|
||||
$table = $this->findExistingTable(['opd_prescription_investigation']);
|
||||
if ($table === null) {
|
||||
return $respond(['update' => 0, 'error_text' => 'Investigation table not found']);
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames($table);
|
||||
if (! in_array('id', $fields, true)) {
|
||||
return $respond(['update' => 0, 'error_text' => 'Investigation id field missing']);
|
||||
}
|
||||
|
||||
$rows = $this->db->table($table)->whereIn('id', $ids)->get()->getResultArray();
|
||||
$this->db->table($table)->whereIn('id', $ids)->delete();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$this->auditClinicalUpdate('opd_prescription_investigation', 'removed', (int) ($row['id'] ?? 0), $row, null);
|
||||
}
|
||||
|
||||
$result = [
|
||||
'update' => 1,
|
||||
'removed_count' => count($rows),
|
||||
'requested_count' => count($ids),
|
||||
'error_text' => 'Investigations removed',
|
||||
'csrfName' => csrf_token(),
|
||||
'csrfHash' => csrf_hash(),
|
||||
];
|
||||
|
||||
log_message(
|
||||
'info',
|
||||
'investigation_remove_bulk requested={requested} removed={removed} elapsed_ms={elapsed_ms}',
|
||||
[
|
||||
'requested' => count($ids),
|
||||
'removed' => count($rows),
|
||||
'elapsed_ms' => (int) round((microtime(true) - $startedAt) * 1000),
|
||||
]
|
||||
);
|
||||
|
||||
return $respond($result);
|
||||
}
|
||||
|
||||
public function medicine_search()
|
||||
{
|
||||
$q = trim((string) $this->request->getGet('q'));
|
||||
|
|
|
|||
|
|
@ -341,6 +341,7 @@ class Patient extends BaseController
|
|||
$txnId = trim((string) ($body['txnId'] ?? $body['txn_id'] ?? $this->request->getPost('txnId') ?? $this->request->getPost('txn_id') ?? ''));
|
||||
$otp = trim((string) ($body['otp'] ?? $this->request->getPost('otp') ?? ''));
|
||||
$mobile = trim((string) ($body['mobile'] ?? $this->request->getPost('mobile') ?? ''));
|
||||
$requestedPid = (int) ($body['p_id'] ?? $this->request->getPost('p_id') ?? 0);
|
||||
|
||||
if ($txnId === '' || $otp === '') {
|
||||
return $this->response->setJSON(['ok' => 0, 'error_text' => 'txnId and otp are required']);
|
||||
|
|
@ -352,6 +353,10 @@ class Patient extends BaseController
|
|||
return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (! empty($result['ok']) && (int) $result['ok'] === 1) {
|
||||
$result = $this->enrichAbhaVerifyResult($result, $requestedPid);
|
||||
}
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
}
|
||||
|
||||
|
|
@ -388,6 +393,7 @@ class Patient extends BaseController
|
|||
$body = $isJson ? ($this->request->getJSON(true) ?? []) : [];
|
||||
$txnId = trim((string) ($body['txnId'] ?? $body['txn_id'] ?? $this->request->getPost('txnId') ?? $this->request->getPost('txn_id') ?? ''));
|
||||
$otp = trim((string) ($body['otp'] ?? $this->request->getPost('otp') ?? ''));
|
||||
$requestedPid = (int) ($body['p_id'] ?? $this->request->getPost('p_id') ?? 0);
|
||||
|
||||
if ($txnId === '' || $otp === '') {
|
||||
return $this->response->setJSON(['ok' => 0, 'error_text' => 'txnId and otp are required']);
|
||||
|
|
@ -399,6 +405,10 @@ class Patient extends BaseController
|
|||
return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (! empty($result['ok']) && (int) $result['ok'] === 1) {
|
||||
$result = $this->enrichAbhaVerifyResult($result, $requestedPid);
|
||||
}
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
}
|
||||
|
||||
|
|
@ -1332,6 +1342,286 @@ class Patient extends BaseController
|
|||
return (int) $this->db->insertID();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $result
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function enrichAbhaVerifyResult(array $result, int $requestedPid): array
|
||||
{
|
||||
$payload = [];
|
||||
if (isset($result['data']) && is_array($result['data'])) {
|
||||
$payload = $result['data'];
|
||||
} elseif (is_array($result)) {
|
||||
$payload = $result;
|
||||
}
|
||||
|
||||
$profile = $this->pickGatewayAbhaProfile($payload);
|
||||
$abhaNumberRaw = trim((string) ($profile['ABHANumber'] ?? $payload['ABHANumber'] ?? ''));
|
||||
$abhaDigits = preg_replace('/\D/', '', $abhaNumberRaw);
|
||||
$abhaAddress = trim((string) ($profile['preferredAbhaAddress'] ?? $payload['abha_address'] ?? ''));
|
||||
$gatewayName = trim((string) ($profile['name'] ?? $payload['name'] ?? ''));
|
||||
$gatewayMobile = trim((string) ($profile['mobile'] ?? $payload['mobile'] ?? $payload['mobileNumber'] ?? ''));
|
||||
$gatewayDob = trim((string) ($profile['dob'] ?? $payload['dob'] ?? ''));
|
||||
$gatewayGender = trim((string) ($profile['gender'] ?? $payload['gender'] ?? ''));
|
||||
$gatewayPhoto = trim((string) ($profile['profilePhoto'] ?? $payload['profilePhoto'] ?? ''));
|
||||
|
||||
$patient = $this->findPatientForAbhaProfile($requestedPid, $abhaDigits, $gatewayMobile);
|
||||
$patientId = (int) ($patient['id'] ?? 0);
|
||||
$before = $patient !== null ? $this->buildPatientSnapshot($patient) : null;
|
||||
|
||||
$photoMeta = [
|
||||
'saved' => false,
|
||||
'path' => '',
|
||||
'error' => '',
|
||||
];
|
||||
|
||||
if ($patientId > 0 && $this->db->tableExists('patient_master')) {
|
||||
$updates = [];
|
||||
|
||||
if ($abhaDigits !== '') {
|
||||
$abhaField = $this->resolvePatientAbhaIdField();
|
||||
if ($abhaField !== null) {
|
||||
$updates[$abhaField] = $abhaDigits;
|
||||
}
|
||||
}
|
||||
|
||||
$pmFields = $this->db->getFieldNames('patient_master') ?? [];
|
||||
if ($abhaAddress !== '' && in_array('abha_address', $pmFields, true)) {
|
||||
$updates['abha_address'] = $abhaAddress;
|
||||
}
|
||||
if (in_array('abdm_linked_at', $pmFields, true) && ($abhaDigits !== '' || $abhaAddress !== '')) {
|
||||
$updates['abdm_linked_at'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
if ($updates !== []) {
|
||||
$this->db->table('patient_master')->where('id', $patientId)->update($updates);
|
||||
}
|
||||
|
||||
if ($gatewayPhoto !== '') {
|
||||
$photoMeta = $this->saveGatewayProfilePhotoToPatient($patientId, $gatewayPhoto);
|
||||
}
|
||||
}
|
||||
|
||||
$afterPatient = null;
|
||||
if ($patientId > 0 && $this->db->tableExists('patient_master')) {
|
||||
$afterPatient = $this->db->table('patient_master')->where('id', $patientId)->get()->getRowArray();
|
||||
}
|
||||
$after = $afterPatient !== null ? $this->buildPatientSnapshot($afterPatient) : $before;
|
||||
|
||||
$result['gateway_user'] = [
|
||||
'name' => $gatewayName,
|
||||
'mobile' => $gatewayMobile,
|
||||
'dob' => $gatewayDob,
|
||||
'gender' => $gatewayGender,
|
||||
'abha_number' => $abhaDigits,
|
||||
'abha_address' => $abhaAddress,
|
||||
'photo_base64' => $gatewayPhoto,
|
||||
];
|
||||
|
||||
$result['hms_patient'] = [
|
||||
'requested_patient_id' => $requestedPid > 0 ? $requestedPid : null,
|
||||
'matched_patient_id' => $patientId > 0 ? $patientId : null,
|
||||
'before' => $before,
|
||||
'after' => $after,
|
||||
'comparison' => [
|
||||
'name_match_before' => $before !== null
|
||||
? mb_strtolower(trim((string) ($before['name'] ?? ''))) === mb_strtolower($gatewayName)
|
||||
: null,
|
||||
'name_match_after' => $after !== null
|
||||
? mb_strtolower(trim((string) ($after['name'] ?? ''))) === mb_strtolower($gatewayName)
|
||||
: null,
|
||||
'mobile_match_before' => $before !== null
|
||||
? preg_replace('/\D/', '', (string) ($before['mobile'] ?? '')) === preg_replace('/\D/', '', $gatewayMobile)
|
||||
: null,
|
||||
'mobile_match_after' => $after !== null
|
||||
? preg_replace('/\D/', '', (string) ($after['mobile'] ?? '')) === preg_replace('/\D/', '', $gatewayMobile)
|
||||
: null,
|
||||
'abha_match_before' => $before !== null
|
||||
? preg_replace('/\D/', '', (string) ($before['abha_id'] ?? '')) === $abhaDigits
|
||||
: null,
|
||||
'abha_match_after' => $after !== null
|
||||
? preg_replace('/\D/', '', (string) ($after['abha_id'] ?? '')) === $abhaDigits
|
||||
: null,
|
||||
'dob_match_before' => $before !== null
|
||||
? trim((string) ($before['dob'] ?? '')) === trim($gatewayDob)
|
||||
: null,
|
||||
'dob_match_after' => $after !== null
|
||||
? trim((string) ($after['dob'] ?? '')) === trim($gatewayDob)
|
||||
: null,
|
||||
'gender_match_before' => $before !== null
|
||||
? $this->normalizeGenderForCompare((string) ($before['gender'] ?? '')) === $this->normalizeGenderForCompare($gatewayGender)
|
||||
: null,
|
||||
'gender_match_after' => $after !== null
|
||||
? $this->normalizeGenderForCompare((string) ($after['gender'] ?? '')) === $this->normalizeGenderForCompare($gatewayGender)
|
||||
: null,
|
||||
],
|
||||
'photo_update' => $photoMeta,
|
||||
];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function pickGatewayAbhaProfile(array $payload): array
|
||||
{
|
||||
if (isset($payload['gateway_patient']) && is_array($payload['gateway_patient'])) {
|
||||
return $payload['gateway_patient'];
|
||||
}
|
||||
if (isset($payload['ABHAProfile']) && is_array($payload['ABHAProfile'])) {
|
||||
return $payload['ABHAProfile'];
|
||||
}
|
||||
if (isset($payload['profile']) && is_array($payload['profile'])) {
|
||||
return $payload['profile'];
|
||||
}
|
||||
if (isset($payload['accounts'][0]) && is_array($payload['accounts'][0])) {
|
||||
return $payload['accounts'][0];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
private function findPatientForAbhaProfile(int $requestedPid, string $abhaDigits, string $mobile): ?array
|
||||
{
|
||||
if (! $this->db->tableExists('patient_master')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($requestedPid > 0) {
|
||||
$row = $this->db->table('patient_master')->where('id', $requestedPid)->get()->getRowArray();
|
||||
if ($row !== null) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
$abhaField = $this->resolvePatientAbhaIdField();
|
||||
if ($abhaDigits !== '' && $abhaField !== null) {
|
||||
$row = $this->db->table('patient_master')->where($abhaField, $abhaDigits)->get()->getRowArray();
|
||||
if ($row !== null) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
$mobileDigits = preg_replace('/\D/', '', $mobile);
|
||||
if ($mobileDigits !== '') {
|
||||
$row = $this->db->table('patient_master')->where('mphone1', $mobileDigits)->get()->getRowArray();
|
||||
if ($row !== null) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function buildPatientSnapshot(array $row): array
|
||||
{
|
||||
$abhaField = $this->resolvePatientAbhaIdField();
|
||||
$abhaId = $abhaField !== null ? trim((string) ($row[$abhaField] ?? '')) : '';
|
||||
$abhaAddress = trim((string) ($row['abha_address'] ?? ''));
|
||||
|
||||
return [
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'p_code' => trim((string) ($row['p_code'] ?? '')),
|
||||
'name' => trim((string) ($row['p_fname'] ?? '')),
|
||||
'mobile' => trim((string) ($row['mphone1'] ?? '')),
|
||||
'dob' => trim((string) ($row['dob'] ?? '')),
|
||||
'gender' => trim((string) ($row['gender'] ?? '')),
|
||||
'abha_id' => $abhaId,
|
||||
'abha_address' => $abhaAddress,
|
||||
'profile_picture' => trim((string) ($row['profile_picture'] ?? '')),
|
||||
'profile_file_id' => (int) ($row['profile_file_id'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{saved:bool,path:string,error:string}
|
||||
*/
|
||||
private function saveGatewayProfilePhotoToPatient(int $pid, string $photoBase64): array
|
||||
{
|
||||
$raw = trim($photoBase64);
|
||||
if ($pid <= 0 || $raw === '') {
|
||||
return ['saved' => false, 'path' => '', 'error' => 'Missing patient id or photo.'];
|
||||
}
|
||||
|
||||
$mime = 'image/jpeg';
|
||||
$encoded = $raw;
|
||||
if (str_starts_with($raw, 'data:image')) {
|
||||
$parts = explode('base64,', $raw, 2);
|
||||
if (count($parts) !== 2) {
|
||||
return ['saved' => false, 'path' => '', 'error' => 'Invalid image data URI.'];
|
||||
}
|
||||
if (preg_match('/data:(image\/[a-zA-Z0-9.+-]+);/i', $parts[0], $m) === 1) {
|
||||
$mime = strtolower((string) ($m[1] ?? 'image/jpeg'));
|
||||
}
|
||||
$encoded = $parts[1];
|
||||
}
|
||||
|
||||
$binary = base64_decode($encoded, true);
|
||||
if ($binary === false) {
|
||||
return ['saved' => false, 'path' => '', 'error' => 'Unable to decode photo base64.'];
|
||||
}
|
||||
|
||||
$ext = match ($mime) {
|
||||
'image/png' => 'png',
|
||||
'image/gif' => 'gif',
|
||||
'image/webp' => 'webp',
|
||||
default => 'jpg',
|
||||
};
|
||||
|
||||
$uploadPath = rtrim(FCPATH, '\\/') . '/uploads/patient';
|
||||
if (! is_dir($uploadPath) && ! mkdir($uploadPath, 0755, true) && ! is_dir($uploadPath)) {
|
||||
return ['saved' => false, 'path' => '', 'error' => 'Unable to create upload directory.'];
|
||||
}
|
||||
|
||||
$fileName = 'abha_profile_' . $pid . '_' . time() . '.' . $ext;
|
||||
$absolutePath = $uploadPath . '/' . $fileName;
|
||||
if (file_put_contents($absolutePath, $binary) === false) {
|
||||
return ['saved' => false, 'path' => '', 'error' => 'Unable to write photo file.'];
|
||||
}
|
||||
|
||||
$publicPath = '/uploads/patient/' . $fileName;
|
||||
$insertId = $this->insertFileUploadRecordFromData($pid, 'profile', $publicPath, $mime, strlen($binary));
|
||||
|
||||
$updates = [];
|
||||
if ($insertId > 0) {
|
||||
$updates['profile_file_id'] = $insertId;
|
||||
}
|
||||
if ($this->db->fieldExists('profile_picture', 'patient_master')) {
|
||||
$updates['profile_picture'] = $publicPath;
|
||||
}
|
||||
|
||||
if ($updates !== []) {
|
||||
$this->db->table('patient_master')->where('id', $pid)->update($updates);
|
||||
}
|
||||
|
||||
return ['saved' => true, 'path' => $publicPath, 'error' => ''];
|
||||
}
|
||||
|
||||
private function normalizeGenderForCompare(string $value): string
|
||||
{
|
||||
$v = strtoupper(trim($value));
|
||||
if ($v === '1' || $v === 'M' || $v === 'MALE') {
|
||||
return 'M';
|
||||
}
|
||||
if ($v === '2' || $v === 'F' || $v === 'FEMALE') {
|
||||
return 'F';
|
||||
}
|
||||
if ($v === '3' || $v === 'O' || $v === 'OTHER') {
|
||||
return 'O';
|
||||
}
|
||||
|
||||
return $v;
|
||||
}
|
||||
|
||||
private function parseDate(?string $date): ?string
|
||||
{
|
||||
if ($date === null || trim($date) === '') {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class AbdmGatewaySettings extends BaseController
|
|||
}
|
||||
|
||||
$gwUrl = trim((string) $this->request->getPost('gateway_url'));
|
||||
$token = trim((string) $this->request->getPost('api_token'));
|
||||
$token = $this->sanitizeBearerToken((string) $this->request->getPost('api_token'));
|
||||
$hfrId = trim((string) $this->request->getPost('hfr_id'));
|
||||
$hmsName = trim((string) $this->request->getPost('hms_name'));
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ class AbdmGatewaySettings extends BaseController
|
|||
|
||||
// Use posted values first, then stored settings
|
||||
$gwUrl = trim((string) $this->request->getPost('gateway_url'));
|
||||
$token = trim((string) $this->request->getPost('api_token'));
|
||||
$token = $this->sanitizeBearerToken((string) $this->request->getPost('api_token'));
|
||||
$hfrId = trim((string) $this->request->getPost('hfr_id'));
|
||||
|
||||
if ($gwUrl === '') {
|
||||
|
|
@ -412,4 +412,16 @@ class AbdmGatewaySettings extends BaseController
|
|||
}
|
||||
return mb_substr($key, 0, 4) . str_repeat('*', mb_strlen($key) - 8) . mb_substr($key, -4);
|
||||
}
|
||||
|
||||
private function sanitizeBearerToken(string $token): string
|
||||
{
|
||||
$token = trim($token);
|
||||
if ($token === '') {
|
||||
return '';
|
||||
}
|
||||
if (stripos($token, 'Bearer ') === 0) {
|
||||
$token = trim(substr($token, 7));
|
||||
}
|
||||
return trim($token, " \t\n\r\0\x0B\"'");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use Mpdf\Mpdf;
|
|||
class Storestock extends BaseController
|
||||
{
|
||||
protected $db;
|
||||
protected array $procedureExistsCache = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
@ -80,6 +81,96 @@ class Storestock extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
private function normalizeUiDate(string $value): string
|
||||
{
|
||||
$value = trim($value);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$dt = \DateTime::createFromFormat('d/m/Y', $value);
|
||||
if ($dt instanceof \DateTime) {
|
||||
return $dt->format('Y-m-d');
|
||||
}
|
||||
|
||||
$dt = \DateTime::createFromFormat('Y-m-d', $value);
|
||||
return $dt instanceof \DateTime ? $dt->format('Y-m-d') : '';
|
||||
}
|
||||
|
||||
private function purchaseReturnInvoiceNoColumn(): string
|
||||
{
|
||||
$fields = $this->db->getFieldNames('purchase_return_invoice') ?? [];
|
||||
if (in_array('p_r_invoice_no', $fields, true)) {
|
||||
return 'p_r_invoice_no';
|
||||
}
|
||||
|
||||
return in_array('Invoice_no', $fields, true) ? 'Invoice_no' : 'id';
|
||||
}
|
||||
|
||||
private function fetchPurchaseReturnItems(int $invId): array
|
||||
{
|
||||
if (
|
||||
$invId <= 0
|
||||
|| ! $this->db->tableExists('purchase_return_invoice_item')
|
||||
|| ! $this->db->tableExists('purchase_invoice_item')
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->db->table('purchase_return_invoice_item r')
|
||||
->select('p.*, r.purchase_inv_id, r.qty AS r_qty, r.id AS r_id', false)
|
||||
->join('purchase_invoice_item p', 'r.purchase_item_id=p.id', 'inner')
|
||||
->where('r.purchase_inv_id', $invId)
|
||||
->orderBy('r.id', 'ASC')
|
||||
->get()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
private function buildPurchaseReturnItemList(int $invId): string
|
||||
{
|
||||
return view('storestock/purchase_return_invoice_item', [
|
||||
'purchase_return_invoice_item' => $this->fetchPurchaseReturnItems($invId),
|
||||
]);
|
||||
}
|
||||
|
||||
private function procedureExists(string $procedureName): bool
|
||||
{
|
||||
if (array_key_exists($procedureName, $this->procedureExistsCache)) {
|
||||
return $this->procedureExistsCache[$procedureName];
|
||||
}
|
||||
|
||||
try {
|
||||
$row = $this->db->query(
|
||||
'SELECT ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = DATABASE() AND ROUTINE_TYPE = ? AND ROUTINE_NAME = ? LIMIT 1',
|
||||
['PROCEDURE', $procedureName]
|
||||
)->getRowArray();
|
||||
|
||||
$this->procedureExistsCache[$procedureName] = ! empty($row['ROUTINE_NAME']);
|
||||
} catch (\Throwable $e) {
|
||||
$this->procedureExistsCache[$procedureName] = false;
|
||||
}
|
||||
|
||||
return $this->procedureExistsCache[$procedureName];
|
||||
}
|
||||
|
||||
private function runOptionalProcedure(string $procedureName, array $arguments = []): void
|
||||
{
|
||||
if (! $this->procedureExists($procedureName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = 'CALL ' . $procedureName . '(' . implode(', ', array_fill(0, count($arguments), '?')) . ')';
|
||||
|
||||
try {
|
||||
$this->db->query($sql, $arguments);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('warning', 'Optional procedure failed: {procedure} - {message}', [
|
||||
'procedure' => $procedureName,
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildDashboardInsights(): array
|
||||
{
|
||||
$todayIndents = (int) $this->safeScalar(
|
||||
|
|
@ -618,7 +709,7 @@ class Storestock extends BaseController
|
|||
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})");
|
||||
$this->runOptionalProcedure('p_update_med_GST_store', [$inv_id]);
|
||||
|
||||
$data['inv_stock_item'] = $this->db->query(
|
||||
"SELECT i.*, IFNULL(DATEDIFF(expiry, CURDATE()), 1000) AS no_day,
|
||||
|
|
@ -717,7 +808,7 @@ class Storestock extends BaseController
|
|||
}
|
||||
|
||||
// Call stored procedure to recalculate GST
|
||||
$this->db->query("CALL p_update_med_GST_store({$inv_id})");
|
||||
$this->runOptionalProcedure('p_update_med_GST_store', [$inv_id]);
|
||||
|
||||
$data['invoice_stock_master'] = $this->db->query(
|
||||
"SELECT *, (discount_amount + item_discount_amount) AS inv_disc_total,
|
||||
|
|
@ -751,7 +842,7 @@ class Storestock extends BaseController
|
|||
return $err;
|
||||
}
|
||||
|
||||
$this->db->query("CALL p_update_med_GST_store({$inv_id})");
|
||||
$this->runOptionalProcedure('p_update_med_GST_store', [$inv_id]);
|
||||
|
||||
$data['inv_items'] = $this->db->query(
|
||||
"SELECT i.indent_id, i.id, i.item_Name, i.formulation,
|
||||
|
|
@ -948,4 +1039,570 @@ class Storestock extends BaseController
|
|||
}
|
||||
return view('storestock/main_store_dashboard');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Purchase
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function Purchase()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$data['purchase_list'] = [];
|
||||
if ($this->db->tableExists('purchase_invoice_stock')) {
|
||||
$data['purchase_list'] = $this->db->table('purchase_invoice_stock p')
|
||||
->select("p.id, p.Invoice_no, p.date_of_invoice, DATE_FORMAT(p.date_of_invoice,'%d-%m-%Y') AS str_date_of_invoice, p.sid, IFNULL(s.name_supplier,'-') AS name_supplier, IFNULL(s.short_name,'-') AS short_name, IFNULL(s.gst_no,'-') AS gst_no, IFNULL(p.T_Net_Amount,0) AS tamount, IFNULL(p.inv_status,0) AS inv_status, IFNULL(p.ischallan,0) AS ischallan", false)
|
||||
->join('stock_supplier s', 'p.sid=s.sid', 'left')
|
||||
->orderBy('p.id', 'DESC')
|
||||
->limit(50)
|
||||
->get()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
return view('storestock/purchase', $data);
|
||||
}
|
||||
|
||||
public function PurchaseInvoice()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$searchRaw = (string) ($this->request->getPost('txtsearch') ?? $this->request->getGet('txtsearch') ?? '');
|
||||
$search = trim(preg_replace('/[^A-Za-z0-9_ \-]/', '', $searchRaw));
|
||||
|
||||
$data['purchase_list'] = [];
|
||||
if ($this->db->tableExists('purchase_invoice_stock')) {
|
||||
$builder = $this->db->table('purchase_invoice_stock p')
|
||||
->select("p.id, p.Invoice_no, p.date_of_invoice, DATE_FORMAT(p.date_of_invoice,'%d-%m-%Y') AS str_date_of_invoice, p.sid, IFNULL(s.name_supplier,'-') AS name_supplier, IFNULL(s.short_name,'-') AS short_name, IFNULL(s.gst_no,'-') AS gst_no, IFNULL(p.T_Net_Amount,0) AS tamount, IFNULL(p.inv_status,0) AS inv_status, IFNULL(p.ischallan,0) AS ischallan", false)
|
||||
->join('stock_supplier s', 'p.sid=s.sid', 'left');
|
||||
|
||||
if ($search !== '') {
|
||||
$builder->groupStart();
|
||||
$builder->like('p.Invoice_no', $search);
|
||||
if (ctype_digit($search)) {
|
||||
$builder->orWhere('p.id', (int) $search);
|
||||
}
|
||||
$builder->groupEnd();
|
||||
}
|
||||
|
||||
$data['purchase_list'] = $builder->orderBy('p.id', 'DESC')->limit(50)->get()->getResult();
|
||||
}
|
||||
|
||||
return view('storestock/purchase_supp_list', $data);
|
||||
}
|
||||
|
||||
public function PurchaseNew()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$data['supplier_data'] = $this->db->tableExists('stock_supplier')
|
||||
? $this->db->table('stock_supplier')->orderBy('name_supplier', 'ASC')->get()->getResult()
|
||||
: [];
|
||||
|
||||
return view('storestock/purchase_new_invoice', $data);
|
||||
}
|
||||
|
||||
public function CreatePurchase()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
if (! $this->request->isAJAX() || ! $this->db->tableExists('purchase_invoice_stock')) {
|
||||
return $this->response->setJSON([
|
||||
'insertid' => 0,
|
||||
'show_text' => 'Invalid request',
|
||||
]);
|
||||
}
|
||||
|
||||
$sid = (int) ($this->request->getPost('input_supplier') ?? 0);
|
||||
$invoiceNo = trim((string) ($this->request->getPost('input_invoicecode') ?? ''));
|
||||
$invoiceDate = $this->normalizeUiDate((string) ($this->request->getPost('datepicker_invoice') ?? ''));
|
||||
$billType = (int) ($this->request->getPost('cbo_billtype') ?? 0);
|
||||
|
||||
if ($sid <= 0 || $invoiceNo === '' || $invoiceDate === '') {
|
||||
return $this->response->setJSON([
|
||||
'insertid' => 0,
|
||||
'show_text' => 'Supplier, invoice no and invoice date are required',
|
||||
]);
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('purchase_invoice_stock') ?? [];
|
||||
$insert = [];
|
||||
if (in_array('sid', $fields, true)) {
|
||||
$insert['sid'] = $sid;
|
||||
}
|
||||
if (in_array('Invoice_no', $fields, true)) {
|
||||
$insert['Invoice_no'] = $invoiceNo;
|
||||
}
|
||||
if (in_array('date_of_invoice', $fields, true)) {
|
||||
$insert['date_of_invoice'] = $invoiceDate;
|
||||
}
|
||||
if (in_array('ischallan', $fields, true)) {
|
||||
$insert['ischallan'] = $billType;
|
||||
}
|
||||
if (in_array('inv_status', $fields, true)) {
|
||||
$insert['inv_status'] = 0;
|
||||
}
|
||||
|
||||
$this->db->table('purchase_invoice_stock')->insert($insert);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'insertid' => (int) $this->db->insertID(),
|
||||
'show_text' => 'Added Successfully',
|
||||
]);
|
||||
}
|
||||
|
||||
public function PurchaseMasterEdit(int $invId)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$data['supplier_data'] = $this->db->tableExists('stock_supplier')
|
||||
? $this->db->table('stock_supplier')->orderBy('name_supplier', 'ASC')->get()->getResult()
|
||||
: [];
|
||||
$data['inv_master_data'] = $this->db->query(
|
||||
'SELECT p.*, s.name_supplier FROM purchase_invoice_stock p JOIN stock_supplier s ON p.sid=s.sid WHERE p.id = ?',
|
||||
[$invId]
|
||||
)->getResult();
|
||||
$data['purchase_item'] = $this->db->query(
|
||||
'SELECT * FROM purchase_invoice_item_stock WHERE purchase_id = ?',
|
||||
[$invId]
|
||||
)->getResult();
|
||||
|
||||
if (empty($data['inv_master_data'])) {
|
||||
return $this->response->setStatusCode(404)->setBody('<div class="alert alert-warning m-3">Purchase invoice not found.</div>');
|
||||
}
|
||||
|
||||
return view('storestock/purchase_invoice_master_edit', $data);
|
||||
}
|
||||
|
||||
public function PurchaseInvoiceEdit(int $invId)
|
||||
{
|
||||
return $this->PurchaseMasterEdit($invId);
|
||||
}
|
||||
|
||||
public function UpdatePurchase()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
if (! $this->request->isAJAX() || ! $this->db->tableExists('purchase_invoice_stock')) {
|
||||
return $this->response->setJSON([
|
||||
'insertid' => 0,
|
||||
'show_text' => 'Invalid request',
|
||||
]);
|
||||
}
|
||||
|
||||
$recId = (int) ($this->request->getPost('hid_purchaseid') ?? 0);
|
||||
$sid = (int) ($this->request->getPost('input_supplier') ?? 0);
|
||||
$invoiceNo = trim((string) ($this->request->getPost('input_invoicecode') ?? ''));
|
||||
$invoiceDate = $this->normalizeUiDate((string) ($this->request->getPost('datepicker_invoice') ?? ''));
|
||||
$billType = (int) ($this->request->getPost('cbo_billtype') ?? 0);
|
||||
|
||||
if ($recId <= 0 || $sid <= 0 || $invoiceNo === '' || $invoiceDate === '') {
|
||||
return $this->response->setJSON([
|
||||
'insertid' => 0,
|
||||
'show_text' => 'Please fill supplier, invoice number and invoice date.',
|
||||
]);
|
||||
}
|
||||
|
||||
$update = [];
|
||||
$fields = $this->db->getFieldNames('purchase_invoice_stock') ?? [];
|
||||
if (in_array('sid', $fields, true)) {
|
||||
$update['sid'] = $sid;
|
||||
}
|
||||
if (in_array('Invoice_no', $fields, true)) {
|
||||
$update['Invoice_no'] = $invoiceNo;
|
||||
}
|
||||
if (in_array('date_of_invoice', $fields, true)) {
|
||||
$update['date_of_invoice'] = $invoiceDate;
|
||||
}
|
||||
if (in_array('ischallan', $fields, true)) {
|
||||
$update['ischallan'] = $billType;
|
||||
}
|
||||
|
||||
if ($update !== []) {
|
||||
$this->db->table('purchase_invoice_stock')->where('id', $recId)->update($update);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'insertid' => 1,
|
||||
'show_text' => 'Data Update',
|
||||
]);
|
||||
}
|
||||
|
||||
public function UpdatePurchaseInvoiceStatus(int $purchaseInvId, int $invStatus)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
if ($this->db->tableExists('purchase_invoice_stock')) {
|
||||
$this->db->table('purchase_invoice_stock')->where('id', $purchaseInvId)->update([
|
||||
'inv_status' => $invStatus,
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->to(site_url('Storestock/PurchaseMasterEdit/' . $purchaseInvId));
|
||||
}
|
||||
|
||||
public function purchase_invoice_delete(int $invId)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
if ($invId <= 0 || ! $this->db->tableExists('purchase_invoice_stock')) {
|
||||
return $this->response->setJSON([
|
||||
'is_delete' => 0,
|
||||
'show_text' => 'Invalid invoice',
|
||||
]);
|
||||
}
|
||||
|
||||
$invoice = $this->db->table('purchase_invoice_stock')->where('id', $invId)->get()->getRow();
|
||||
if (! $invoice) {
|
||||
return $this->response->setJSON([
|
||||
'is_delete' => 0,
|
||||
'show_text' => 'Invoice not found',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->db->tableExists('purchase_invoice_item_stock')) {
|
||||
$count = $this->db->table('purchase_invoice_item_stock')->where('purchase_id', $invId)->countAllResults();
|
||||
if ($count > 0) {
|
||||
return $this->response->setJSON([
|
||||
'is_delete' => 0,
|
||||
'show_text' => 'Invoice has items, empty the item list first',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ((float) ($invoice->T_Net_Amount ?? 0) > 0) {
|
||||
return $this->response->setJSON([
|
||||
'is_delete' => 0,
|
||||
'show_text' => 'Cannot delete invoice with amount',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->db->table('purchase_invoice_stock')->where('id', $invId)->delete();
|
||||
|
||||
return $this->response->setJSON([
|
||||
'is_delete' => 1,
|
||||
'show_text' => 'Deleted Successfully',
|
||||
]);
|
||||
}
|
||||
|
||||
public function print_purchase(int $invId)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$invoice = $this->db->query(
|
||||
'SELECT p.id, p.Invoice_no, p.date_of_invoice, DATE_FORMAT(p.date_of_invoice,\'%d/%m/%Y\') AS str_date_of_invoice, p.sid, IFNULL(s.name_supplier,\'-\') AS name_supplier, IFNULL(s.short_name,\'-\') AS short_name, IFNULL(s.gst_no,\'-\') AS gst_no
|
||||
FROM purchase_invoice_stock p JOIN stock_supplier s ON p.sid=s.sid WHERE p.id = ?',
|
||||
[$invId]
|
||||
)->getRow();
|
||||
|
||||
if (! $invoice) {
|
||||
return $this->response->setStatusCode(404)->setBody('Purchase invoice not found');
|
||||
}
|
||||
|
||||
$items = $this->db->query('SELECT * FROM purchase_invoice_item_stock WHERE purchase_id = ? ORDER BY id ASC', [$invId])->getResult();
|
||||
|
||||
return view('storestock/purchase_print', [
|
||||
'purchase_invoice' => $invoice,
|
||||
'purchase_invoice_item' => $items,
|
||||
]);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Purchase Return
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function Purchase_return()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
return view('storestock/purchase_return');
|
||||
}
|
||||
|
||||
public function PurchaseReturnNew()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$supplierData = [];
|
||||
if ($this->db->tableExists('med_supplier')) {
|
||||
$supplierData = $this->db->query('SELECT * FROM med_supplier ORDER BY name_supplier')->getResult();
|
||||
} elseif ($this->db->tableExists('stock_supplier')) {
|
||||
$supplierData = $this->db->query('SELECT * FROM stock_supplier ORDER BY name_supplier')->getResult();
|
||||
}
|
||||
|
||||
return view('storestock/purchase_return_new', [
|
||||
'supplier_data' => $supplierData,
|
||||
]);
|
||||
}
|
||||
|
||||
public function PurchaseReturnInvoice()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
if ($this->db->tableExists('purchase_return_invoice')) {
|
||||
$searchRaw = (string) ($this->request->getPost('txtsearch') ?? $this->request->getGet('txtsearch') ?? '');
|
||||
$search = preg_replace('/[^A-Za-z0-9_ \-]/', '', trim($searchRaw));
|
||||
$invNoCol = $this->purchaseReturnInvoiceNoColumn();
|
||||
|
||||
$builder = $this->db->table('purchase_return_invoice p')
|
||||
->select("p.id, p.{$invNoCol} AS p_r_invoice_no, p.date_of_invoice, DATE_FORMAT(p.date_of_invoice,'%d-%m-%Y') AS str_date_of_invoice, p.sid, IFNULL(s.name_supplier,'-') AS name_supplier, IFNULL(s.short_name,'-') AS short_name", false)
|
||||
->join('med_supplier s', 'p.sid=s.sid', 'left');
|
||||
|
||||
if ($search !== '') {
|
||||
$builder->groupStart();
|
||||
$builder->like("p.{$invNoCol}", $search);
|
||||
if (ctype_digit($search)) {
|
||||
$builder->orWhere('p.id', (int) $search);
|
||||
}
|
||||
$builder->groupEnd();
|
||||
}
|
||||
|
||||
$rows = $builder->orderBy('p.id', 'DESC')->limit(50)->get()->getResult();
|
||||
}
|
||||
|
||||
return view('storestock/purchase_return_supplier_list', [
|
||||
'purchase_return_invoice' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function PurchaseReturnInvoiceEdit(int $invId)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
if ($invId <= 0 || ! $this->db->tableExists('purchase_return_invoice')) {
|
||||
return $this->response->setStatusCode(404)->setBody('<div class="alert alert-warning m-3">Purchase return invoice not found.</div>');
|
||||
}
|
||||
|
||||
$invNoCol = $this->purchaseReturnInvoiceNoColumn();
|
||||
$invoice = $this->db->table('purchase_return_invoice p')
|
||||
->select("p.id, p.{$invNoCol} AS Invoice_no, p.date_of_invoice, DATE_FORMAT(p.date_of_invoice,'%d/%m/%Y') AS str_date_of_invoice, p.sid, p.status AS inv_status, IFNULL(s.name_supplier,'-') AS name_supplier, IFNULL(s.short_name,'-') AS short_name, IFNULL(s.gst_no,'-') AS gst_no", false)
|
||||
->join('med_supplier s', 'p.sid=s.sid', 'left')
|
||||
->where('p.id', $invId)
|
||||
->get()
|
||||
->getRow();
|
||||
|
||||
if (! $invoice) {
|
||||
return $this->response->setStatusCode(404)->setBody('<div class="alert alert-warning m-3">Purchase return invoice not found.</div>');
|
||||
}
|
||||
|
||||
$items = $this->fetchPurchaseReturnItems($invId);
|
||||
|
||||
return view('storestock/purchase_return_invoice_edit', [
|
||||
'purchase_return_invoice' => $invoice,
|
||||
'purchase_return_invoice_item' => $items,
|
||||
'content' => $this->buildPurchaseReturnItemList($invId),
|
||||
]);
|
||||
}
|
||||
|
||||
public function PurchaseReturn_invoice_item_list(int $invId)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$invoice = null;
|
||||
if ($invId > 0 && $this->db->tableExists('purchase_return_invoice')) {
|
||||
$invoice = $this->db->table('purchase_return_invoice')->where('id', $invId)->get()->getRow();
|
||||
}
|
||||
|
||||
return view('storestock/purchase_return_invoice_item', [
|
||||
'purchase_return_invoice_item' => $this->fetchPurchaseReturnItems($invId),
|
||||
'purchase_return_invoice' => $invoice ? [$invoice] : [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function create_purchase_return()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
if (! $this->request->isAJAX() || ! $this->db->tableExists('purchase_return_invoice')) {
|
||||
return $this->response->setJSON([
|
||||
'insertid' => 0,
|
||||
'show_text' => 'Invalid request',
|
||||
]);
|
||||
}
|
||||
|
||||
$sid = (int) ($this->request->getPost('input_supplier') ?? 0);
|
||||
$invoiceDate = $this->normalizeUiDate((string) ($this->request->getPost('datepicker_invoice') ?? ''));
|
||||
|
||||
if ($sid <= 0 || $invoiceDate === '') {
|
||||
return $this->response->setJSON([
|
||||
'insertid' => 0,
|
||||
'show_text' => 'Supplier and date are required',
|
||||
]);
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('purchase_return_invoice') ?? [];
|
||||
$insert = [];
|
||||
if (in_array('sid', $fields, true)) {
|
||||
$insert['sid'] = $sid;
|
||||
}
|
||||
if (in_array('date_of_invoice', $fields, true)) {
|
||||
$insert['date_of_invoice'] = $invoiceDate;
|
||||
}
|
||||
if (in_array('status', $fields, true)) {
|
||||
$insert['status'] = 0;
|
||||
}
|
||||
|
||||
$this->db->table('purchase_return_invoice')->insert($insert);
|
||||
$insertId = (int) $this->db->insertID();
|
||||
|
||||
if ($insertId > 0 && in_array('p_r_invoice_no', $fields, true)) {
|
||||
$invoiceNo = 'PR' . date('ym') . str_pad((string) ($insertId % 1000), 3, '0', STR_PAD_LEFT);
|
||||
$this->db->table('purchase_return_invoice')->where('id', $insertId)->update(['p_r_invoice_no' => $invoiceNo]);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'insertid' => $insertId,
|
||||
'show_text' => $insertId > 0 ? 'Added Successfully' : 'Unable to create purchase return invoice',
|
||||
]);
|
||||
}
|
||||
|
||||
public function add_remove_item()
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$invId = (int) ($this->request->getPost('inv_id') ?? 0);
|
||||
$itemId = (int) ($this->request->getPost('itemid') ?? 0);
|
||||
$qty = (float) ($this->request->getPost('rqty') ?? 0);
|
||||
$batchNo = trim((string) ($this->request->getPost('rbatch_no') ?? ''));
|
||||
$expiry = trim((string) ($this->request->getPost('rexpiry_dt') ?? ''));
|
||||
|
||||
if ($invId <= 0 || $itemId <= 0 || $qty <= 0 || ! $this->db->tableExists('purchase_return_invoice_item') || ! $this->db->tableExists('purchase_invoice_item')) {
|
||||
return $this->response->setJSON(['update' => 0, 'msg_text' => 'Invalid return input', 'content' => '']);
|
||||
}
|
||||
|
||||
$sourceItem = $this->db->table('purchase_invoice_item')->where('id', $itemId)->get()->getRow();
|
||||
if (! $sourceItem) {
|
||||
return $this->response->setJSON(['update' => 0, 'msg_text' => 'No item found', 'content' => '']);
|
||||
}
|
||||
|
||||
$insert = [];
|
||||
$fields = $this->db->getFieldNames('purchase_return_invoice_item') ?? [];
|
||||
if (in_array('purchase_inv_id', $fields, true)) {
|
||||
$insert['purchase_inv_id'] = $invId;
|
||||
}
|
||||
if (in_array('purchase_item_id', $fields, true)) {
|
||||
$insert['purchase_item_id'] = $itemId;
|
||||
}
|
||||
if (in_array('item_code', $fields, true)) {
|
||||
$insert['item_code'] = $sourceItem->item_code ?? 0;
|
||||
}
|
||||
if (in_array('Item_name', $fields, true)) {
|
||||
$insert['Item_name'] = $sourceItem->Item_name ?? ($sourceItem->item_name ?? '');
|
||||
}
|
||||
if (in_array('batch_no_r', $fields, true)) {
|
||||
$insert['batch_no_r'] = $batchNo;
|
||||
}
|
||||
if (in_array('expiry_date_r', $fields, true)) {
|
||||
$insert['expiry_date_r'] = $expiry !== '' ? $expiry : null;
|
||||
}
|
||||
if (in_array('qty', $fields, true)) {
|
||||
$insert['qty'] = $qty;
|
||||
}
|
||||
|
||||
$this->db->table('purchase_return_invoice_item')->insert($insert);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'update' => 1,
|
||||
'msg_text' => 'Item Added',
|
||||
'content' => $this->buildPurchaseReturnItemList($invId),
|
||||
]);
|
||||
}
|
||||
|
||||
public function remove_item_invoice(int $itemId)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
$row = null;
|
||||
if ($itemId > 0 && $this->db->tableExists('purchase_return_invoice_item')) {
|
||||
$row = $this->db->table('purchase_return_invoice_item')->where('id', $itemId)->get()->getRow();
|
||||
}
|
||||
|
||||
if (! $row) {
|
||||
return $this->response->setJSON(['update' => 0, 'msg_text' => 'No item found', 'content' => '']);
|
||||
}
|
||||
|
||||
$invId = (int) ($row->purchase_inv_id ?? 0);
|
||||
$this->db->table('purchase_return_invoice_item')->where('id', $itemId)->delete();
|
||||
|
||||
return $this->response->setJSON([
|
||||
'update' => 1,
|
||||
'msg_text' => 'Item Removed',
|
||||
'content' => $this->buildPurchaseReturnItemList($invId),
|
||||
]);
|
||||
}
|
||||
|
||||
public function print_purchase_return(int $invId)
|
||||
{
|
||||
if ($err = $this->ensureStoreAccess()) {
|
||||
return $err;
|
||||
}
|
||||
|
||||
if ($invId <= 0 || ! $this->db->tableExists('purchase_return_invoice')) {
|
||||
return $this->response->setStatusCode(404)->setBody('Purchase return invoice not found');
|
||||
}
|
||||
|
||||
$invNoCol = $this->purchaseReturnInvoiceNoColumn();
|
||||
$invoice = $this->db->table('purchase_return_invoice p')
|
||||
->select("p.id, p.{$invNoCol} AS Invoice_no, p.date_of_invoice, DATE_FORMAT(p.date_of_invoice,'%d/%m/%Y') AS str_date_of_invoice, p.sid, IFNULL(s.name_supplier,'-') AS name_supplier, IFNULL(s.short_name,'-') AS short_name, IFNULL(s.gst_no,'-') AS gst_no", false)
|
||||
->join('med_supplier s', 'p.sid=s.sid', 'left')
|
||||
->where('p.id', $invId)
|
||||
->get()
|
||||
->getRow();
|
||||
|
||||
if (! $invoice) {
|
||||
return $this->response->setStatusCode(404)->setBody('Purchase return invoice not found');
|
||||
}
|
||||
|
||||
$items = $this->fetchPurchaseReturnItems($invId);
|
||||
$html = view('storestock/purchase_return_invoice_item_print', [
|
||||
'purchase_return_invoice' => [$invoice],
|
||||
'purchase_return_invoice_item' => $items,
|
||||
]);
|
||||
|
||||
$mpdf = new Mpdf([
|
||||
'mode' => 'utf-8',
|
||||
'format' => 'A4',
|
||||
'tempDir' => WRITEPATH . 'cache/mpdf',
|
||||
]);
|
||||
$mpdf->showWatermarkText = false;
|
||||
$mpdf->WriteHTML($html);
|
||||
|
||||
$filename = 'Return_Invoice-' . $invId . '-' . date('YmdHis') . '.pdf';
|
||||
return $this->response
|
||||
->setHeader('Content-Type', 'application/pdf')
|
||||
->setBody($mpdf->Output($filename, 'S'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
$config = config('AbdmConnector');
|
||||
|
||||
$this->baseUrl = rtrim((string) ($config->eatriaBridgeUrl ?? 'https://abdm-bridge.e-atria.in/api'), '/');
|
||||
$this->token = (string) ($config->eatriaBridgeToken ?? '');
|
||||
$this->token = $this->sanitizeBearerToken((string) ($config->eatriaBridgeToken ?? ''));
|
||||
$this->hfrId = '';
|
||||
$this->timeoutSec = (int) ($config->eatriaBridgeTimeoutSec ?? 30);
|
||||
|
||||
|
|
@ -41,14 +41,19 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
if ($db->tableExists('hospital_setting')) {
|
||||
$rows = $db->table('hospital_setting')
|
||||
->select('s_name, s_value')
|
||||
->whereIn('s_name', ['EATRIA_BRIDGE_TOKEN', 'EATRIA_BRIDGE_URL', 'ABDM_HFR_ID'])
|
||||
->whereIn('s_name', ['EATRIA_BRIDGE_TOKEN', 'ABDM_BRIDGE_TOKEN', 'EATRIA_BRIDGE_URL', 'ABDM_HFR_ID'])
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$dbSettings = array_column($rows, 's_value', 's_name');
|
||||
|
||||
if (! empty($dbSettings['EATRIA_BRIDGE_TOKEN'])) {
|
||||
$this->token = trim($dbSettings['EATRIA_BRIDGE_TOKEN']);
|
||||
$dbToken = (string) ($dbSettings['EATRIA_BRIDGE_TOKEN'] ?? '');
|
||||
if ($dbToken === '') {
|
||||
// Backward compatibility: older setups may store the key under ABDM_BRIDGE_TOKEN.
|
||||
$dbToken = (string) ($dbSettings['ABDM_BRIDGE_TOKEN'] ?? '');
|
||||
}
|
||||
if ($dbToken !== '') {
|
||||
$this->token = $this->sanitizeBearerToken($dbToken);
|
||||
}
|
||||
if (! empty($dbSettings['EATRIA_BRIDGE_URL'])) {
|
||||
$this->baseUrl = rtrim(trim($dbSettings['EATRIA_BRIDGE_URL']), '/');
|
||||
|
|
@ -67,6 +72,21 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
return 'eatria_bridge';
|
||||
}
|
||||
|
||||
private function sanitizeBearerToken(string $token): string
|
||||
{
|
||||
$token = trim($token);
|
||||
if ($token === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Operators sometimes paste "Bearer <token>" into settings.
|
||||
if (stripos($token, 'Bearer ') === 0) {
|
||||
$token = trim(substr($token, 7));
|
||||
}
|
||||
|
||||
return trim($token, " \t\n\r\0\x0B\"'");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Internal HTTP helper
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
@ -221,6 +241,9 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
public function validateAbha(string $abhaId, array $fullPayload = []): array
|
||||
{
|
||||
$body = $fullPayload !== [] ? $fullPayload : ['abha_id' => $abhaId];
|
||||
if ($this->hfrId !== '' && empty($body['hfr_id'])) {
|
||||
$body['hfr_id'] = $this->hfrId;
|
||||
}
|
||||
return $this->post('/v3/abha/validate', $body);
|
||||
}
|
||||
|
||||
|
|
@ -234,6 +257,9 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
$body = [
|
||||
'aadhaar' => (string) ($payload['aadhaar'] ?? $payload['loginId'] ?? ''),
|
||||
];
|
||||
if ($this->hfrId !== '' && empty($body['hfr_id'])) {
|
||||
$body['hfr_id'] = $this->hfrId;
|
||||
}
|
||||
return $this->post('/v3/abha/aadhaar/generate-otp', $body);
|
||||
}
|
||||
|
||||
|
|
@ -250,6 +276,9 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
if (preg_match('/^\d{10}$/', $mobile)) {
|
||||
$body['mobile'] = $mobile;
|
||||
}
|
||||
if ($this->hfrId !== '' && empty($body['hfr_id'])) {
|
||||
$body['hfr_id'] = $this->hfrId;
|
||||
}
|
||||
return $this->post('/v3/abha/aadhaar/verify-otp', $body);
|
||||
}
|
||||
|
||||
|
|
@ -260,6 +289,9 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
$body = [
|
||||
'mobile' => (string) ($payload['mobile'] ?? $payload['loginId'] ?? ''),
|
||||
];
|
||||
if ($this->hfrId !== '' && empty($body['hfr_id'])) {
|
||||
$body['hfr_id'] = $this->hfrId;
|
||||
}
|
||||
return $this->post('/v3/abha/mobile/generate-otp', $body);
|
||||
}
|
||||
|
||||
|
|
@ -270,6 +302,9 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
'txnId' => (string) ($payload['txnId'] ?? $payload['txn_id'] ?? ''),
|
||||
'otp' => (string) ($payload['otp'] ?? ''),
|
||||
];
|
||||
if ($this->hfrId !== '' && empty($body['hfr_id'])) {
|
||||
$body['hfr_id'] = $this->hfrId;
|
||||
}
|
||||
return $this->post('/v3/abha/mobile/verify-otp', $body);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -296,6 +296,12 @@ $paymentHistoryRows = $payment_history_rows ?? [];
|
|||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
$appointmentDateRaw = (string) ($opd_master[0]->apointment_date ?? '');
|
||||
$appointmentTimestamp = $appointmentDateRaw !== '' ? strtotime($appointmentDateRaw) : false;
|
||||
$appointmentDateValue = $appointmentTimestamp !== false ? date('Y-m-d', $appointmentTimestamp) : '';
|
||||
$appointmentDateDisplay = $appointmentTimestamp !== false ? date('d/m/Y', $appointmentTimestamp) : '';
|
||||
?>
|
||||
<div class="row g-3 mt-2">
|
||||
<div class="col-md-3">
|
||||
<label>Change Doctor</label>
|
||||
|
|
@ -306,8 +312,8 @@ $paymentHistoryRows = $payment_history_rows ?? [];
|
|||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label>Date <?= esc(MysqlDate_to_str($opd_master[0]->apointment_date ?? '')) ?></label>
|
||||
<input class="form-control datepicker" id="datepicker_opddate" name="datepicker_opddate" type="text" value="<?= esc(MysqlDate_to_str($opd_master[0]->apointment_date ?? '')) ?>" />
|
||||
<label>Date <?= esc($appointmentDateDisplay) ?></label>
|
||||
<input class="form-control" id="datepicker_opddate" name="datepicker_opddate" type="date" value="<?= esc($appointmentDateValue) ?>" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label>OPD Fee <?= esc($opd_master[0]->opd_fee_gross_amount ?? '') ?> / <?= esc($opd_master[0]->opd_fee_amount ?? '') ?></label>
|
||||
|
|
|
|||
|
|
@ -1576,6 +1576,8 @@
|
|||
var legacyInvestigationProfileList = [];
|
||||
var legacyInvestigationShortTests = [];
|
||||
var investigationBatchActive = false;
|
||||
var investigationListLoading = false;
|
||||
var investigationListPending = false;
|
||||
var activeMedicineScope = 'all';
|
||||
var medicineSuggestRows = [];
|
||||
var medicineSearchCache = {};
|
||||
|
|
@ -6589,37 +6591,6 @@
|
|||
return (name || '').toString().trim().toLowerCase();
|
||||
}
|
||||
|
||||
function addInvestigationEntry(name, code, done) {
|
||||
name = (name || '').trim();
|
||||
code = (code || '').trim();
|
||||
if (!name) {
|
||||
if (typeof done === 'function') {
|
||||
done(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ensureSession(function(sid) {
|
||||
apiPost('<?= base_url('Opd_prescription/investigation_add') ?>', {
|
||||
opd_id: $('#opd_id').val(),
|
||||
opd_session_id: sid,
|
||||
investigation_code: code,
|
||||
investigation_name: name
|
||||
}, function(data) {
|
||||
if (data.update == 1) {
|
||||
$('#opd_session_id').val(data.opd_session_id || sid);
|
||||
if (typeof done === 'function') {
|
||||
done(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (typeof done === 'function') {
|
||||
done(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function batchAddInvestigations(tests) {
|
||||
tests = Array.isArray(tests) ? tests : [];
|
||||
if (!tests.length) {
|
||||
|
|
@ -6663,41 +6634,56 @@
|
|||
}
|
||||
|
||||
investigationBatchActive = true;
|
||||
|
||||
var idx = 0;
|
||||
var added = 0;
|
||||
function next() {
|
||||
if (idx >= queue.length) {
|
||||
ensureSession(function(sid) {
|
||||
apiPost('<?= base_url('Opd_prescription/investigation_add_bulk') ?>', {
|
||||
opd_id: $('#opd_id').val(),
|
||||
opd_session_id: sid,
|
||||
tests: queue
|
||||
}, function(data) {
|
||||
investigationBatchActive = false;
|
||||
loadInvestigationList();
|
||||
$('.jsError').removeClass('text-danger text-muted').addClass('text-success').text(added + ' test(s) added.');
|
||||
markDirty('Investigation shortcuts applied');
|
||||
return;
|
||||
}
|
||||
var row = queue[idx++] || {};
|
||||
var key = normalizeInvestigationKey(row.name || '');
|
||||
addInvestigationEntry((row.name || '').toString(), (row.code || '').toString(), function(ok) {
|
||||
if (ok) {
|
||||
added++;
|
||||
if (key) {
|
||||
existing[key] = true;
|
||||
if (data.update == 1) {
|
||||
$('#opd_session_id').val(data.opd_session_id || sid);
|
||||
loadInvestigationList();
|
||||
var addedCount = parseInt(data.added_count || '0', 10);
|
||||
var skippedCount = parseInt(data.skipped_count || '0', 10);
|
||||
var msg = addedCount + ' test(s) added.';
|
||||
if (skippedCount > 0) {
|
||||
msg += ' ' + skippedCount + ' duplicate/empty skipped.';
|
||||
}
|
||||
$('.jsError').removeClass('text-danger text-muted').addClass('text-success').text(msg);
|
||||
if (addedCount > 0) {
|
||||
markDirty('Investigation shortcuts applied');
|
||||
}
|
||||
return;
|
||||
}
|
||||
next();
|
||||
$('.jsError').removeClass('text-success text-muted').addClass('text-danger').text(data.error_text || 'Unable to add investigations.');
|
||||
});
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
function loadInvestigationList() {
|
||||
var opdId = $('#opd_id').val();
|
||||
var sid = parseInt($('#opd_session_id').val() || '0', 10);
|
||||
if (sid <= 0) {
|
||||
investigationListLoading = false;
|
||||
investigationListPending = false;
|
||||
renderInvestigation([]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (investigationListLoading) {
|
||||
investigationListPending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
investigationListLoading = true;
|
||||
apiGet('<?= base_url('Opd_prescription/investigation_list') ?>/' + opdId + '/' + sid, function(data) {
|
||||
investigationListLoading = false;
|
||||
renderInvestigation(data.rows || []);
|
||||
if (investigationListPending) {
|
||||
investigationListPending = false;
|
||||
loadInvestigationList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -6753,15 +6739,11 @@
|
|||
return;
|
||||
}
|
||||
|
||||
addInvestigationEntry(name, code, function(ok) {
|
||||
if (ok) {
|
||||
$('#investigation_name').val('');
|
||||
if ($.fn && $.fn.select2 && $('#investigation_name_select2').length) {
|
||||
$('#investigation_name_select2').val(null).trigger('change');
|
||||
}
|
||||
loadInvestigationList();
|
||||
}
|
||||
});
|
||||
batchAddInvestigationRows([{ name: name, code: code }]);
|
||||
$('#investigation_name').val('');
|
||||
if ($.fn && $.fn.select2 && $('#investigation_name_select2').length) {
|
||||
$('#investigation_name_select2').val(null).trigger('change');
|
||||
}
|
||||
});
|
||||
|
||||
$('#btn_clear_investigation').on('click', function() {
|
||||
|
|
@ -6777,24 +6759,23 @@
|
|||
return;
|
||||
}
|
||||
|
||||
var index = 0;
|
||||
function removeNext() {
|
||||
if (index >= ids.length) {
|
||||
apiPost('<?= base_url('Opd_prescription/investigation_remove_bulk') ?>', {
|
||||
ids: ids
|
||||
}, function(data) {
|
||||
if (data.update == 1) {
|
||||
loadInvestigationList();
|
||||
$('.jsError').removeClass('text-danger text-muted').addClass('text-success').text('All investigations removed.');
|
||||
markDirty('Investigation list cleared');
|
||||
return;
|
||||
}
|
||||
|
||||
var id = ids[index++];
|
||||
apiPost('<?= base_url('Opd_prescription/investigation_remove') ?>/' + id, {}, function() {
|
||||
removeNext();
|
||||
});
|
||||
}
|
||||
removeNext();
|
||||
$('.jsError').removeClass('text-success text-muted').addClass('text-danger').text(data.error_text || 'Unable to remove investigations.');
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.inv-quick-chip', function() {
|
||||
$(document).off('click', '.inv-quick-chip');
|
||||
$(document).off('click', '.inv-profile-chip');
|
||||
|
||||
$(document).off('click.invQuickChip', '.inv-quick-chip').on('click.invQuickChip', '.inv-quick-chip', function() {
|
||||
var test = ($(this).data('test') || '').toString().trim();
|
||||
var code = ($(this).data('code') || '').toString().trim();
|
||||
if (!test) {
|
||||
|
|
@ -6807,7 +6788,7 @@
|
|||
batchAddInvestigations([test]);
|
||||
});
|
||||
|
||||
$(document).on('click', '.inv-profile-chip', function() {
|
||||
$(document).off('click.invProfileChip', '.inv-profile-chip').on('click.invProfileChip', '.inv-profile-chip', function() {
|
||||
var key = ($(this).data('profile') || '').toString().trim();
|
||||
if (legacyInvestigationProfiles[key] && legacyInvestigationProfiles[key].length) {
|
||||
batchAddInvestigationRows(legacyInvestigationProfiles[key]);
|
||||
|
|
@ -6947,7 +6928,7 @@
|
|||
loadMedicineDoseMasters();
|
||||
$('#advise_investigation_notes').val($('#investigation').val() || '');
|
||||
|
||||
$(document).on('click', '.btn-del-invest', function() {
|
||||
$(document).off('click.investRemoveSingle', '.btn-del-invest').on('click.investRemoveSingle', '.btn-del-invest', function() {
|
||||
var id = $(this).data('id');
|
||||
apiPost('<?= base_url('Opd_prescription/investigation_remove') ?>/' + id, {}, function() {
|
||||
loadInvestigationList();
|
||||
|
|
|
|||
|
|
@ -170,11 +170,37 @@
|
|||
}
|
||||
function clearMsg(id) { document.getElementById(id).innerHTML = ''; }
|
||||
|
||||
function parseXhrJson(xhr) {
|
||||
if (!xhr) return {};
|
||||
if (xhr.responseJSON && typeof xhr.responseJSON === 'object') {
|
||||
return xhr.responseJSON;
|
||||
}
|
||||
var raw = xhr.responseText || '';
|
||||
if (raw === '') return {};
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function pickErrorMessage(res, fallbackText) {
|
||||
var payload = (res && (res.data || res)) || {};
|
||||
var profile = payload.profile || payload.ABHAProfile || {};
|
||||
return (
|
||||
(res && (res.error_text || res.message || res.error)) ||
|
||||
payload.message ||
|
||||
profile.message ||
|
||||
profile.code ||
|
||||
fallbackText
|
||||
);
|
||||
}
|
||||
|
||||
function postAbdm(url, data, cb) {
|
||||
var csrf = csrfPair();
|
||||
data[csrf.name] = csrf.value;
|
||||
$.post(url, data, function (res) { updateCsrf(res); cb(res || {}); }, 'json')
|
||||
.fail(function (xhr) { cb(xhr && xhr.responseJSON ? xhr.responseJSON : {}); });
|
||||
.fail(function (xhr) { cb(parseXhrJson(xhr)); });
|
||||
}
|
||||
|
||||
function disableBtn(id) { $('#' + id).prop('disabled', true); }
|
||||
|
|
@ -256,7 +282,7 @@
|
|||
AADHAAR OTP FLOW
|
||||
================================================================ */
|
||||
|
||||
$('#btn_aadhaar_send_otp').on('click', function () {
|
||||
$('#btn_aadhaar_send_otp').off('click.abhaOtp').on('click.abhaOtp', function () {
|
||||
clearMsg('aadhaar_step1_msg');
|
||||
var aadhaar = $('#abha_aadhaar_input').val().trim();
|
||||
if (!/^\d{12}$/.test(aadhaar)) {
|
||||
|
|
@ -284,19 +310,19 @@
|
|||
$('#aadhaar-step2').show();
|
||||
setTimeout(function () { $('#abha_aadhaar_otp').trigger('focus'); }, 100);
|
||||
} else {
|
||||
var errMsg = res.error_text || res.message || res.error || 'Failed to send OTP. Please try again.';
|
||||
var errMsg = pickErrorMessage(res, 'Failed to send OTP. Please try again.');
|
||||
showMsg('aadhaar_step1_msg', 'danger', '<i class="bi bi-x-circle me-1"></i>' + $('<div>').text(errMsg).html());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#btn_aadhaar_back').on('click', function () {
|
||||
$('#btn_aadhaar_back').off('click.abhaOtp').on('click.abhaOtp', function () {
|
||||
$('#aadhaar-step2').hide();
|
||||
$('#aadhaar-step1').show();
|
||||
clearMsg('aadhaar_step2_msg');
|
||||
});
|
||||
|
||||
$('#btn_aadhaar_resend').on('click', function () {
|
||||
$('#btn_aadhaar_resend').off('click.abhaOtp').on('click.abhaOtp', function () {
|
||||
var aadhaar = $('#abha_aadhaar_input').val().trim();
|
||||
if (!/^\d{12}$/.test(aadhaar)) {
|
||||
showMsg('aadhaar_step2_msg', 'warning', 'Go back and re-enter the Aadhaar number.');
|
||||
|
|
@ -320,13 +346,13 @@
|
|||
showMsg('aadhaar_step2_msg', 'success', '<i class="bi bi-check-circle me-1"></i>OTP resent. Enter the new OTP above.');
|
||||
setTimeout(function () { $('#abha_aadhaar_otp').trigger('focus'); }, 100);
|
||||
} else {
|
||||
var errMsg = res.error_text || res.message || res.error || 'Resend failed.';
|
||||
var errMsg = pickErrorMessage(res, 'Resend failed.');
|
||||
showMsg('aadhaar_step2_msg', 'danger', '<i class="bi bi-x-circle me-1"></i>' + $('<div>').text(errMsg).html());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#btn_aadhaar_verify_otp').on('click', function () {
|
||||
$('#btn_aadhaar_verify_otp').off('click.abhaOtp').on('click.abhaOtp', function () {
|
||||
clearMsg('aadhaar_step2_msg');
|
||||
var otp = $('#abha_aadhaar_otp').val().trim();
|
||||
var mobile = $('#abha_aadhaar_mobile').val().trim();
|
||||
|
|
@ -345,7 +371,7 @@
|
|||
disableBtn('btn_aadhaar_verify_otp');
|
||||
$('#btn_aadhaar_verify_otp').html('<span class="spinner-border spinner-border-sm me-1"></span>Verifying…');
|
||||
|
||||
postAbdm('<?= base_url('billing/patient/abha_aadhaar_verify_otp') ?>', { txnId: _aadhaarTxnId, otp: otp, mobile: mobile }, function (res) {
|
||||
postAbdm('<?= base_url('billing/patient/abha_aadhaar_verify_otp') ?>', { txnId: _aadhaarTxnId, otp: otp, mobile: mobile, p_id: _patientId }, function (res) {
|
||||
enableBtn('btn_aadhaar_verify_otp');
|
||||
$('#btn_aadhaar_verify_otp').html('<i class="bi bi-check-circle me-1"></i>Verify OTP & Link ABHA');
|
||||
|
||||
|
|
@ -360,14 +386,14 @@
|
|||
$('#btn_aadhaar_verify_otp').prop('disabled', true).text('Linked ✓');
|
||||
setTimeout(function () { bootstrap.Modal.getInstance(document.getElementById('abhaOtpModal')).hide(); }, 1800);
|
||||
} else {
|
||||
var errMsg = res.error_text || res.message || res.error || 'OTP verification failed.';
|
||||
var errMsg = pickErrorMessage(res, 'OTP verification failed.');
|
||||
showMsg('aadhaar_step2_msg', 'danger', '<i class="bi bi-x-circle me-1"></i>' + $('<div>').text(errMsg).html());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* Allow Enter key in OTP field */
|
||||
$('#abha_aadhaar_otp').on('keydown', function (e) {
|
||||
$('#abha_aadhaar_otp').off('keydown.abhaOtp').on('keydown.abhaOtp', function (e) {
|
||||
if (e.key === 'Enter') $('#btn_aadhaar_verify_otp').trigger('click');
|
||||
});
|
||||
|
||||
|
|
@ -375,7 +401,7 @@
|
|||
MOBILE OTP FLOW
|
||||
================================================================ */
|
||||
|
||||
$('#btn_mobile_send_otp').on('click', function () {
|
||||
$('#btn_mobile_send_otp').off('click.abhaOtp').on('click.abhaOtp', function () {
|
||||
clearMsg('mobile_step1_msg');
|
||||
var mobile = $('#abha_mobile_input').val().trim();
|
||||
if (!/^\d{10}$/.test(mobile)) {
|
||||
|
|
@ -397,19 +423,19 @@
|
|||
$('#mobile-step2').show();
|
||||
setTimeout(function () { $('#abha_mobile_otp').trigger('focus'); }, 100);
|
||||
} else {
|
||||
var errMsg = res.error_text || res.message || res.error || 'Failed to send OTP.';
|
||||
var errMsg = pickErrorMessage(res, 'Failed to send OTP.');
|
||||
showMsg('mobile_step1_msg', 'danger', '<i class="bi bi-x-circle me-1"></i>' + $('<div>').text(errMsg).html());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#btn_mobile_back').on('click', function () {
|
||||
$('#btn_mobile_back').off('click.abhaOtp').on('click.abhaOtp', function () {
|
||||
$('#mobile-step2').hide();
|
||||
$('#mobile-step1').show();
|
||||
clearMsg('mobile_step2_msg');
|
||||
});
|
||||
|
||||
$('#btn_mobile_verify_otp').on('click', function () {
|
||||
$('#btn_mobile_verify_otp').off('click.abhaOtp').on('click.abhaOtp', function () {
|
||||
clearMsg('mobile_step2_msg');
|
||||
var otp = $('#abha_mobile_otp').val().trim();
|
||||
if (!/^\d{6}$/.test(otp)) {
|
||||
|
|
@ -423,7 +449,7 @@
|
|||
disableBtn('btn_mobile_verify_otp');
|
||||
$('#btn_mobile_verify_otp').html('<span class="spinner-border spinner-border-sm me-1"></span>Verifying…');
|
||||
|
||||
postAbdm('<?= base_url('billing/patient/abha_mobile_verify_otp') ?>', { txnId: _mobileTxnId, otp: otp }, function (res) {
|
||||
postAbdm('<?= base_url('billing/patient/abha_mobile_verify_otp') ?>', { txnId: _mobileTxnId, otp: otp, p_id: _patientId }, function (res) {
|
||||
enableBtn('btn_mobile_verify_otp');
|
||||
$('#btn_mobile_verify_otp').html('<i class="bi bi-check-circle me-1"></i>Verify OTP & Link ABHA');
|
||||
|
||||
|
|
@ -438,13 +464,13 @@
|
|||
$('#btn_mobile_verify_otp').prop('disabled', true).text('Linked ✓');
|
||||
setTimeout(function () { bootstrap.Modal.getInstance(document.getElementById('abhaOtpModal')).hide(); }, 1800);
|
||||
} else {
|
||||
var errMsg = res.error_text || res.message || res.error || 'OTP verification failed.';
|
||||
var errMsg = pickErrorMessage(res, 'OTP verification failed.');
|
||||
showMsg('mobile_step2_msg', 'danger', '<i class="bi bi-x-circle me-1"></i>' + $('<div>').text(errMsg).html());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#abha_mobile_otp').on('keydown', function (e) {
|
||||
$('#abha_mobile_otp').off('keydown.abhaOtp').on('keydown.abhaOtp', function (e) {
|
||||
if (e.key === 'Enter') $('#btn_mobile_verify_otp').trigger('click');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,30 @@ $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-shell-head">
|
||||
<div class="module-shell-title">
|
||||
<span class="main">Store Stock</span>
|
||||
<span class="sub" id="subtitle">Dashboard</span>
|
||||
</div>
|
||||
<div class="module-nav-tabs">
|
||||
<a class="tab active" href="javascript:load_form('<?= base_url('Storestock') ?>','Hospital Stock');">
|
||||
<i class="fa fa-home"></i> Dashboard
|
||||
</a>
|
||||
<a class="tab" href="javascript:load_form('<?= base_url('Storestock/Indent_List') ?>','Store : Indent');">
|
||||
<i class="fa fa-shopping-cart"></i> Indent
|
||||
</a>
|
||||
<a class="tab" href="javascript:load_form('<?= base_url('Storestock/Report_2') ?>','Day Report :Store');">
|
||||
<i class="fa fa-line-chart"></i> Day Report
|
||||
</a>
|
||||
<a class="tab" href="javascript:load_form('<?= base_url('Storestock/store_stock') ?>','Store Stock : Store');">
|
||||
<i class="fa fa-barcode"></i> Store Stock
|
||||
</a>
|
||||
<a class="tab" href="javascript:load_form('<?= base_url('Storestock/main_store') ?>','Store Main : Store');">
|
||||
<i class="fa fa-desktop"></i> Store Main
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
|
|
@ -67,14 +87,7 @@ $stockValue = (float) ($stats['stock_value'] ?? 0);
|
|||
</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">
|
||||
|
|
@ -147,5 +160,6 @@ $stockValue = (float) ($stats['stock_value'] ?? 0);
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
131
app/Views/storestock/dashboard_sub.php
Normal file
131
app/Views/storestock/dashboard_sub.php
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<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="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>
|
||||
|
|
@ -1,40 +1,66 @@
|
|||
<?= $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>
|
||||
<section class="content">
|
||||
<div class="module-shell-head">
|
||||
<div class="module-shell-title">
|
||||
<span class="main">Store Stock</span>
|
||||
<span class="sub">Day Report</span>
|
||||
</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 class="module-nav-tabs">
|
||||
<a class="tab" href="javascript:load_form('<?= base_url('Storestock') ?>','Hospital Stock');">
|
||||
<i class="fa fa-home"></i> Dashboard
|
||||
</a>
|
||||
<a class="tab " href="javascript:load_form('<?= base_url('Storestock/Indent_List') ?>','Store : Indent');">
|
||||
<i class="fa fa-shopping-cart"></i> Indent
|
||||
</a>
|
||||
<a class="tab active" href="javascript:load_form('<?= base_url('Storestock/Report_2') ?>','Day Report :Store');">
|
||||
<i class="fa fa-line-chart"></i> Day Report
|
||||
</a>
|
||||
<a class="tab" href="javascript:load_form('<?= base_url('Storestock/store_stock') ?>','Store Stock : Store');">
|
||||
<i class="fa fa-barcode"></i> Store Stock
|
||||
</a>
|
||||
<a class="tab" href="javascript:load_form('<?= base_url('Storestock/main_store') ?>','Store Main : Store');">
|
||||
<i class="fa fa-desktop"></i> Store Main
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-shell">
|
||||
<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>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('form.form-day-report').on('submit', function (e) {
|
||||
|
|
|
|||
|
|
@ -1,45 +1,79 @@
|
|||
<?= $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>
|
||||
<section class="content">
|
||||
<div class="module-shell-head">
|
||||
<div class="module-shell-title">
|
||||
<span class="main">Store Stock</span>
|
||||
<span class="sub">Indent</span>
|
||||
</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 class="module-nav-tabs">
|
||||
<a class="tab" href="javascript:load_form('<?= base_url('Storestock') ?>','Hospital Stock');">
|
||||
<i class="fa fa-home"></i> Dashboard
|
||||
</a>
|
||||
<a class="tab active" href="javascript:load_form('<?= base_url('Storestock/Indent_List') ?>','Store : Indent');">
|
||||
<i class="fa fa-shopping-cart"></i> Indent
|
||||
</a>
|
||||
<a class="tab" href="javascript:load_form('<?= base_url('Storestock/Report_2') ?>','Day Report :Store');">
|
||||
<i class="fa fa-line-chart"></i> Day Report
|
||||
</a>
|
||||
<a class="tab" href="javascript:load_form('<?= base_url('Storestock/store_stock') ?>','Store Stock : Store');">
|
||||
<i class="fa fa-barcode"></i> Store Stock
|
||||
</a>
|
||||
<a class="tab" href="javascript:load_form('<?= base_url('Storestock/main_store') ?>','Store Main : Store');">
|
||||
<i class="fa fa-desktop"></i> Store Main
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="workspace-shell">
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Workspace</h3>
|
||||
</div>
|
||||
<div class="box-body" id="maindiv">
|
||||
<div class="box" style="margin-bottom:0;">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
|
@ -50,7 +84,7 @@ $(document).ready(function () {
|
|||
"processing": true,
|
||||
"serverSide": true,
|
||||
"ajax": {
|
||||
url: "Storestock/getIndentTable",
|
||||
url: "<?= base_url('Storestock/getIndentTable') ?>",
|
||||
dataType: "json",
|
||||
type: "post",
|
||||
data: function (d) {
|
||||
|
|
|
|||
97
app/Views/storestock/purchase.php
Normal file
97
app/Views/storestock/purchase.php
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?= $this->include('storestock/_niceadmin_theme') ?>
|
||||
<div class="storestock-ui">
|
||||
<section class="content-header">
|
||||
<h1>
|
||||
Purchase
|
||||
<small>Store</small>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<section class="content">
|
||||
<div class="module-hero">
|
||||
<h4>Store Purchase Control Center</h4>
|
||||
<p>Track supplier invoices, open edits in a persistent sub panel, and keep the invoice list visible while you work.</p>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<div class="box-header d-flex justify-content-between align-items-center">
|
||||
<h3 class="box-title mb-0">Purchase Invoices</h3>
|
||||
<button type="button" class="btn btn-warning btn-sm" id="btnNewPurchase">New Purchase / Challan Invoice</button>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<form id="purchase-search-form" class="row g-2 align-items-center" method="post" action="javascript:void(0)">
|
||||
<?= csrf_field() ?>
|
||||
<div class="col-md-6">
|
||||
<input class="form-control" type="text" id="txtsearch" name="txtsearch" placeholder="Search by invoice no. or supplier">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-info">Search Purchase Invoice</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="button" id="btnResetPurchase" class="btn btn-outline-secondary">Reset</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Recent Purchase Invoices</h3>
|
||||
</div>
|
||||
<div class="box-body" id="purchase-main-panel">
|
||||
<?= $this->include('storestock/purchase_supp_list') ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="purchase-sub-main" style="display:none;" class="mt-3">
|
||||
<div id="searchresult"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
window.openStorestockPurchaseSubView = function (url, title) {
|
||||
var $root = $('#purchase-sub-main');
|
||||
var $listPanel = $('#purchase-main-panel');
|
||||
|
||||
if ($root.length) {
|
||||
$listPanel.hide();
|
||||
$root.show();
|
||||
load_form_div(url, 'searchresult', title || 'Purchase : Store');
|
||||
return;
|
||||
}
|
||||
|
||||
load_form_div(url, 'maindiv', title || 'Purchase : Store');
|
||||
};
|
||||
|
||||
window.closeStorestockPurchaseSubView = function () {
|
||||
var $root = $('#purchase-sub-main');
|
||||
var $listPanel = $('#purchase-main-panel');
|
||||
|
||||
if ($root.length) {
|
||||
$('#searchresult').empty();
|
||||
$root.hide();
|
||||
$listPanel.show();
|
||||
}
|
||||
};
|
||||
|
||||
$('#btnNewPurchase').off('click').on('click', function () {
|
||||
openStorestockPurchaseSubView('<?= base_url('Storestock/PurchaseNew') ?>', 'New Purchase / Challan Invoice : Store');
|
||||
});
|
||||
|
||||
$('#btnResetPurchase').off('click').on('click', function () {
|
||||
$('#txtsearch').val('');
|
||||
$('#purchase-search-form').trigger('submit');
|
||||
});
|
||||
|
||||
$('#purchase-search-form').off('submit').on('submit', function (event) {
|
||||
event.preventDefault();
|
||||
$.post('<?= base_url('Storestock/PurchaseInvoice') ?>', $(this).serialize(), function (html) {
|
||||
$('#purchase-main-panel').html(html || '');
|
||||
}).fail(function () {
|
||||
$('#purchase-main-panel').html('<div class="alert alert-danger mb-0">Failed to load purchase list.</div>');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
136
app/Views/storestock/purchase_invoice_master_edit.php
Normal file
136
app/Views/storestock/purchase_invoice_master_edit.php
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<?php $invoice = $inv_master_data[0] ?? null; ?>
|
||||
|
||||
<?php if (! $invoice): ?>
|
||||
<div class="alert alert-warning mb-0">Purchase invoice not found.</div>
|
||||
<?php else: ?>
|
||||
<div class="box">
|
||||
<div class="box-header d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||||
<div>
|
||||
<h3 class="box-title mb-0">Purchase Invoice / Supplier : <?= esc((string) ($invoice->name_supplier ?? '-')) ?></h3>
|
||||
<small class="text-muted">
|
||||
Invoice No.: <?= esc((string) ($invoice->Invoice_no ?? '')) ?> |
|
||||
Invoice Date: <?= esc((string) ($invoice->str_date_of_invoice ?? '')) ?>
|
||||
</small>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="closeStorestockPurchaseSubView();">Back to List</button>
|
||||
<button type="button" class="btn btn-warning" onclick="openStorestockPurchaseSubView('<?= base_url('Storestock/PurchaseMasterEdit/' . (int) ($invoice->id ?? 0)) ?>','Purchase : Edit');">Reload Invoice</button>
|
||||
<a href="<?= base_url('Storestock/print_purchase/' . (int) ($invoice->id ?? 0)) ?>" target="_blank" class="btn btn-secondary"><i class="fa fa-print"></i> Print</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<form id="storestock-purchase-edit-form" class="row g-3" method="post" action="javascript:void(0)">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" id="hid_purchaseid" name="hid_purchaseid" value="<?= (int) ($invoice->id ?? 0) ?>">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Supplier</label>
|
||||
<select class="form-select" id="input_supplier" name="input_supplier" <?= ((int) ($invoice->inv_status ?? 0)) === 0 ? '' : 'disabled' ?>>
|
||||
<?php foreach (($supplier_data ?? []) as $row): ?>
|
||||
<option value="<?= (int) ($row->sid ?? 0) ?>" <?= ((int) ($row->sid ?? 0)) === (int) ($invoice->sid ?? 0) ? 'selected' : '' ?>><?= esc($row->name_supplier ?? '') ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Bill Type</label>
|
||||
<select class="form-select" name="cbo_billtype" id="cbo_billtype" <?= ((int) ($invoice->inv_status ?? 0)) === 0 ? '' : 'disabled' ?>>
|
||||
<option value="0" <?= ((int) ($invoice->ischallan ?? 0)) === 0 ? 'selected' : '' ?>>Invoice</option>
|
||||
<option value="1" <?= ((int) ($invoice->ischallan ?? 0)) === 1 ? 'selected' : '' ?>>Challan</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Invoice No.</label>
|
||||
<input class="form-control" name="input_invoicecode" type="text" value="<?= esc((string) ($invoice->Invoice_no ?? '')) ?>" <?= ((int) ($invoice->inv_status ?? 0)) === 0 ? '' : 'readonly' ?>>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Date of Invoice</label>
|
||||
<input class="form-control" name="datepicker_invoice" type="text" value="<?= esc((string) ($invoice->str_date_of_invoice ?? '')) ?>" <?= ((int) ($invoice->inv_status ?? 0)) === 0 ? '' : 'readonly' ?>>
|
||||
</div>
|
||||
<div class="col-md-2 d-flex align-items-end gap-2">
|
||||
<button type="button" class="btn btn-primary" id="btn_update_purchase" <?= ((int) ($invoice->inv_status ?? 0)) === 0 ? '' : 'disabled' ?>>Update</button>
|
||||
<button type="button" class="btn btn-outline-secondary" id="btn_toggle_status">Toggle Status</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive mt-4">
|
||||
<table class="table table-bordered table-striped table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Item Name</th>
|
||||
<th>Batch No</th>
|
||||
<th>Exp.</th>
|
||||
<th>MRP</th>
|
||||
<th>Qty.</th>
|
||||
<th>Rate</th>
|
||||
<th>Amount</th>
|
||||
<th>Disc.</th>
|
||||
<th>Tax Amount</th>
|
||||
<th>CGST</th>
|
||||
<th>SGST</th>
|
||||
<th>Net Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($purchase_item)): ?>
|
||||
<tr><td colspan="13" class="text-center text-muted">No items added yet.</td></tr>
|
||||
<?php endif; ?>
|
||||
<?php $srno = 0; foreach (($purchase_item ?? []) as $row): $srno++; ?>
|
||||
<tr>
|
||||
<td><?= $srno ?></td>
|
||||
<td><?= esc((string) ($row->Item_name ?? $row->item_name ?? '')) ?></td>
|
||||
<td><?= esc((string) ($row->batch_no ?? '-')) ?></td>
|
||||
<td><?= esc((string) ($row->expiry_date ?? '-')) ?></td>
|
||||
<td><?= esc((string) ($row->mrp ?? '0')) ?></td>
|
||||
<td><?= esc((string) ((float) ($row->qty ?? 0))) ?><?= ((float) ($row->qty_free ?? 0)) > 0 ? ' + ' . esc((string) ((float) ($row->qty_free ?? 0))) : '' ?></td>
|
||||
<td><?= esc((string) ($row->purchase_price ?? '0')) ?></td>
|
||||
<td><?= esc((string) ($row->amount ?? '0')) ?></td>
|
||||
<td><?= esc((string) ($row->discount ?? $row->disc_amount ?? '0')) ?></td>
|
||||
<td><?= esc((string) ($row->taxable_amount ?? '0')) ?></td>
|
||||
<td><?= esc((string) ($row->CGST_per ?? '0')) ?></td>
|
||||
<td><?= esc((string) ($row->SGST_per ?? '0')) ?></td>
|
||||
<td><?= esc((string) ($row->net_amount ?? '0')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="9" class="text-end">Totals</th>
|
||||
<th><?= esc((string) ($invoice->Taxable_Amt ?? '0')) ?></th>
|
||||
<th><?= esc((string) ($invoice->CGST_Amt ?? '0')) ?></th>
|
||||
<th><?= esc((string) ($invoice->SGST_Amt ?? '0')) ?></th>
|
||||
<th><?= esc((string) ($invoice->T_Net_Amount ?? '0')) ?></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
$('#btn_update_purchase').off('click').on('click', function () {
|
||||
$.post('<?= base_url('Storestock/UpdatePurchase') ?>', $('#storestock-purchase-edit-form').serialize(), function (data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
notify('error', 'Please Attention', 'Unexpected response');
|
||||
return;
|
||||
}
|
||||
|
||||
if ((data.insertid || 0) === 0) {
|
||||
notify('error', 'Please Attention', data.show_text || 'Please check input');
|
||||
return;
|
||||
}
|
||||
|
||||
notify('success', 'Update Success', data.show_text || 'Data Update');
|
||||
openStorestockPurchaseSubView('<?= base_url('Storestock/PurchaseMasterEdit/' . (int) ($invoice->id ?? 0)) ?>', 'Purchase : Edit');
|
||||
}, 'json').fail(function () {
|
||||
notify('error', 'Please Attention', 'Unable to update invoice');
|
||||
});
|
||||
});
|
||||
|
||||
$('#btn_toggle_status').off('click').on('click', function () {
|
||||
var nextStatus = <?= ((int) ($invoice->inv_status ?? 0)) === 0 ? 1 : 0 ?>;
|
||||
openStorestockPurchaseSubView('<?= base_url('Storestock/UpdatePurchaseInvoiceStatus/' . (int) ($invoice->id ?? 0)) ?>/' + nextStatus, 'Purchase : Edit');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
60
app/Views/storestock/purchase_new_invoice.php
Normal file
60
app/Views/storestock/purchase_new_invoice.php
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<div class="box">
|
||||
<div class="box-header d-flex justify-content-between align-items-center">
|
||||
<h3 class="box-title mb-0">New Purchase Invoice</h3>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="closeStorestockPurchaseSubView();">Back to List</button>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<form id="storestock-purchase-new-form" class="row g-3" method="post" action="javascript:void(0)">
|
||||
<?= csrf_field() ?>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Supplier</label>
|
||||
<select class="form-select" id="input_supplier" name="input_supplier">
|
||||
<?php foreach (($supplier_data ?? []) as $row): ?>
|
||||
<option value="<?= (int) ($row->sid ?? 0) ?>"><?= esc($row->name_supplier ?? '') ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Bill Type</label>
|
||||
<select class="form-select" name="cbo_billtype" id="cbo_billtype">
|
||||
<option value="0">Invoice</option>
|
||||
<option value="1">Challan</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Invoice / Challan No.</label>
|
||||
<input class="form-control" name="input_invoicecode" placeholder="Invoice No." type="text">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Date of Invoice</label>
|
||||
<input class="form-control" name="datepicker_invoice" id="datepicker_invoice" type="text" value="<?= date('d/m/Y') ?>">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="button" class="btn btn-primary" id="btn_create_purchase">Create</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
$('#btn_create_purchase').off('click').on('click', function () {
|
||||
$.post('<?= base_url('Storestock/CreatePurchase') ?>', $('#storestock-purchase-new-form').serialize(), function (data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
notify('error', 'Please Attention', 'Unexpected response');
|
||||
return;
|
||||
}
|
||||
|
||||
if ((data.insertid || 0) <= 0) {
|
||||
notify('error', 'Please Attention', data.show_text || 'Unable to create purchase invoice');
|
||||
return;
|
||||
}
|
||||
|
||||
notify('success', 'Please Attention', 'Invoice Added : ID->' + data.insertid);
|
||||
openStorestockPurchaseSubView('<?= base_url('Storestock/PurchaseMasterEdit') ?>/' + data.insertid, 'Purchase : Edit');
|
||||
}, 'json').fail(function () {
|
||||
notify('error', 'Please Attention', 'Unable to create purchase invoice');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
51
app/Views/storestock/purchase_print.php
Normal file
51
app/Views/storestock/purchase_print.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php $invoice = $purchase_invoice ?? null; ?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Purchase Invoice</title>
|
||||
<style>
|
||||
body { font-family: Arial, Helvetica, sans-serif; margin: 18px; color: #1f2937; }
|
||||
.title { font-size: 22px; font-weight: 700; color: #0f2f67; }
|
||||
.sub { margin-top: 4px; color: #6b7280; font-size: 13px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 14px; font-size: 13px; }
|
||||
th, td { border: 1px solid #d1d5db; padding: 6px 8px; text-align: left; }
|
||||
th { background: #f3f4f6; }
|
||||
.right { text-align: right; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="title">Purchase Invoice</div>
|
||||
<div class="sub">Supplier: <?= esc((string) ($invoice->name_supplier ?? '-')) ?> | Invoice No.: <?= esc((string) ($invoice->Invoice_no ?? '')) ?> | Date: <?= esc((string) ($invoice->str_date_of_invoice ?? '')) ?></div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Item</th>
|
||||
<th>Batch</th>
|
||||
<th>Exp.</th>
|
||||
<th class="right">Qty</th>
|
||||
<th class="right">Rate</th>
|
||||
<th class="right">Amount</th>
|
||||
<th class="right">Net</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $srno = 0; foreach (($purchase_invoice_item ?? []) as $row): $srno++; ?>
|
||||
<tr>
|
||||
<td><?= $srno ?></td>
|
||||
<td><?= esc((string) ($row->Item_name ?? $row->item_name ?? '')) ?></td>
|
||||
<td><?= esc((string) ($row->batch_no ?? '-')) ?></td>
|
||||
<td><?= esc((string) ($row->expiry_date ?? '-')) ?></td>
|
||||
<td class="right"><?= esc((string) ((float) ($row->qty ?? 0))) ?></td>
|
||||
<td class="right"><?= esc((string) ($row->purchase_price ?? '0')) ?></td>
|
||||
<td class="right"><?= esc((string) ($row->amount ?? '0')) ?></td>
|
||||
<td class="right"><?= esc((string) ($row->net_amount ?? '0')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
24
app/Views/storestock/purchase_return_invoice_edit.php
Normal file
24
app/Views/storestock/purchase_return_invoice_edit.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php $invoice = $purchase_return_invoice ?? null; ?>
|
||||
<?php if (! $invoice): ?>
|
||||
<div class="alert alert-warning mb-0">Purchase return invoice not found.</div>
|
||||
<?php else: ?>
|
||||
<div class="box">
|
||||
<div class="box-header d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||||
<div>
|
||||
<h3 class="box-title mb-0">Purchase Return Invoice / Supplier : <?= esc((string) ($invoice->name_supplier ?? '-')) ?></h3>
|
||||
<small class="text-muted">
|
||||
Invoice No.: <?= esc((string) ($invoice->Invoice_no ?? '')) ?> |
|
||||
Invoice Date: <?= esc((string) ($invoice->str_date_of_invoice ?? '')) ?>
|
||||
</small>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="load_form_div('<?= base_url('Storestock/Purchase_return') ?>','searchresult','Purchase Return');">Back</button>
|
||||
<button type="button" class="btn btn-warning" onclick="load_form_div('<?= base_url('Storestock/PurchaseReturnInvoiceEdit/' . (int) ($invoice->id ?? 0)) ?>','searchresult','Purchase Return Invoice Edit');">Reload Invoice</button>
|
||||
<a href="<?= base_url('Storestock/print_purchase_return/' . (int) ($invoice->id ?? 0)) ?>" target="_blank" class="btn btn-secondary"><i class="fa fa-print"></i> Print</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body pt-3">
|
||||
<?= $content ?? '' ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
28
app/Views/storestock/purchase_return_invoice_item.php
Normal file
28
app/Views/storestock/purchase_return_invoice_item.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php $rows = $purchase_return_invoice_item ?? []; ?>
|
||||
<div class="table-responsive">
|
||||
<table id="purchase_return_report_list" class="table table-bordered table-striped table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Item</th>
|
||||
<th>Batch</th>
|
||||
<th>Expiry</th>
|
||||
<th>Qty</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($rows)): ?>
|
||||
<tr><td colspan="5" class="text-center text-muted">No return items found.</td></tr>
|
||||
<?php endif; ?>
|
||||
<?php $srno = 0; foreach ($rows as $row): $srno++; ?>
|
||||
<tr>
|
||||
<td><?= $srno ?></td>
|
||||
<td><?= esc((string) ($row->Item_name ?? '')) ?></td>
|
||||
<td><?= esc((string) ($row->batch_no_r ?? $row->batch_no ?? '-')) ?></td>
|
||||
<td><?= esc((string) ($row->expiry_date_r ?? $row->expiry_date ?? '-')) ?></td>
|
||||
<td><?= esc((string) ($row->r_qty ?? $row->qty ?? '0')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
50
app/Views/storestock/purchase_return_new.php
Normal file
50
app/Views/storestock/purchase_return_new.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<div class="box">
|
||||
<div class="box-header d-flex justify-content-between align-items-center">
|
||||
<h3 class="box-title mb-0">New Purchase Return Invoice</h3>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="load_form_div('<?= base_url('Storestock/Purchase_return') ?>','searchresult','Purchase Return');">Back</button>
|
||||
</div>
|
||||
<div class="box-body pt-3">
|
||||
<form id="new-purchase-return-form" class="row g-2" method="post" action="javascript:void(0)">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Supplier</label>
|
||||
<select class="form-select" id="input_supplier" name="input_supplier">
|
||||
<?php foreach (($supplier_data ?? []) as $row): ?>
|
||||
<option value="<?= (int) ($row->sid ?? 0) ?>"><?= esc($row->name_supplier ?? '') ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Date of Invoice</label>
|
||||
<input class="form-control" name="datepicker_invoice" id="datepicker_invoice" type="text" value="<?= date('d/m/Y') ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<button type="button" class="btn btn-primary" id="btn_create_purchase_return" accesskey="C">Create</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
$('#btn_create_purchase_return').off('click').on('click', function () {
|
||||
$.post('<?= base_url('Storestock/create_purchase_return') ?>', $('#new-purchase-return-form').serialize(), function (data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
notify('error', 'Please Attention', 'Unexpected response');
|
||||
return;
|
||||
}
|
||||
if ((data.insertid || 0) <= 0) {
|
||||
notify('error', 'Please Attention', data.show_text || 'Unable to create purchase return invoice');
|
||||
return;
|
||||
}
|
||||
notify('success', 'Please Attention', 'Return Invoice : ID->' + data.insertid);
|
||||
load_form_div('<?= base_url('Storestock/PurchaseReturnInvoiceEdit') ?>/' + data.insertid, 'searchresult', 'Purchase Return Invoice Edit');
|
||||
}, 'json').fail(function () {
|
||||
notify('error', 'Please Attention', 'Unable to create purchase return invoice');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
51
app/Views/storestock/purchase_supp_list.php
Normal file
51
app/Views/storestock/purchase_supp_list.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php $rows = $purchase_list ?? []; ?>
|
||||
<div class="table-responsive">
|
||||
<table id="purchase-report-list" class="table table-bordered table-striped table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Invoice ID</th>
|
||||
<th>Supplier</th>
|
||||
<th>Date</th>
|
||||
<th>Amount</th>
|
||||
<th style="width:220px;">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($rows)): ?>
|
||||
<tr>
|
||||
<td class="text-center text-muted">No purchase invoices found.</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($rows as $row): ?>
|
||||
<tr class="<?= ((int) ($row->ischallan ?? 0)) === 1 ? 'text-danger' : '' ?>">
|
||||
<td><?= esc((string) ($row->Invoice_no ?? '')) ?></td>
|
||||
<td><?= esc((string) ($row->name_supplier ?? '')) ?></td>
|
||||
<td><?= esc((string) ($row->str_date_of_invoice ?? $row->date_of_invoice ?? '')) ?></td>
|
||||
<td><?= esc((string) number_format((float) ($row->tamount ?? 0), 2)) ?></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-warning btn-sm" onclick="openStorestockPurchaseSubView('<?= base_url('Storestock/PurchaseMasterEdit/' . (int) ($row->id ?? 0)) ?>','Purchase : Edit');">View & Edit</button>
|
||||
<a href="<?= base_url('Storestock/print_purchase/' . (int) ($row->id ?? 0)) ?>" target="_blank" class="btn btn-secondary btn-sm"><i class="fa fa-print"></i> Print</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
if (window.jQuery && $.fn.DataTable) {
|
||||
if ($.fn.DataTable.isDataTable('#purchase-report-list')) {
|
||||
$('#purchase-report-list').DataTable().destroy();
|
||||
}
|
||||
$('#purchase-report-list').DataTable({
|
||||
pageLength: 25,
|
||||
order: [[0, 'desc']]
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
Loading…
Reference in New Issue
Block a user