['label' => 'Robust', 'column' => 'robust_qty'], 'mini' => ['label' => 'Mini', 'column' => 'mini_qty'], 'control_d' => ['label' => 'Control D', 'column' => 'control_d_qty'], ]; public function summaryForEmployee($empId) { $empId = $this->normalizeEmpId($empId); $allocation = $empId === '' ? null : DB::table('incept_neb_allocations')->where('emp_id', $empId)->first(); $usedByType = []; if ($empId !== '') { $rows = DB::table('incept_neb_usage') ->select('nebulizer_type', DB::raw('SUM(qty) as used_qty')) ->where('emp_id', $empId) ->groupBy('nebulizer_type') ->get(); foreach ($rows as $row) { $usedByType[$row->nebulizer_type] = (int) $row->used_qty; } } $types = []; foreach (self::TYPES as $key => $meta) { $allocated = $allocation ? (int) ($allocation->{$meta['column']} ?? 0) : 0; $used = (int) ($usedByType[$key] ?? 0); $types[$key] = [ 'label' => $meta['label'], 'allocated' => $allocated, 'used' => $used, 'balance' => max(0, $allocated - $used), ]; } return [ 'emp_id' => $allocation->emp_id ?? $empId, 'emp_name' => $allocation->emp_name ?? '', 'team' => $allocation->team ?? '', 'designation' => $allocation->designation ?? '', 'has_allocation' => (bool) $allocation, 'types' => $types, ]; } public function summaryAll() { $allocated = [ 'robust' => (int) DB::table('incept_neb_allocations')->sum('robust_qty'), 'mini' => (int) DB::table('incept_neb_allocations')->sum('mini_qty'), 'control_d' => (int) DB::table('incept_neb_allocations')->sum('control_d_qty'), ]; $usedByType = []; $rows = DB::table('incept_neb_usage') ->select('nebulizer_type', DB::raw('SUM(qty) as used_qty')) ->groupBy('nebulizer_type') ->get(); foreach ($rows as $row) { $usedByType[$row->nebulizer_type] = (int) $row->used_qty; } $types = []; foreach (self::TYPES as $key => $meta) { $qty = (int) ($allocated[$key] ?? 0); $used = (int) ($usedByType[$key] ?? 0); $types[$key] = [ 'label' => $meta['label'], 'allocated' => $qty, 'used' => $used, 'balance' => max(0, $qty - $used), ]; } return ['types' => $types]; } public function viewerSeesOrgTotals($formdata) { $allAccess = $this->formValue($formdata, 'all_data_access'); if ($allAccess === true || $allAccess === 1 || $allAccess === '1' || $allAccess === 'true') { return true; } $designation = ''; if (is_object($formdata) && isset($formdata->user_info->designation)) { $designation = (string) $formdata->user_info->designation; } if (trim($designation) === '') { $empId = $this->resolveEmpId([], is_object($formdata) ? $formdata : null); if ($empId !== '') { $allocation = DB::table('incept_neb_allocations')->where('emp_id', $empId)->first(); $designation = $allocation->designation ?? ''; } } return strtoupper(trim($designation)) === 'HO'; } public function withOrgTotals(array $summary, $formdata) { if (!$this->viewerSeesOrgTotals($formdata)) { return $summary; } $summary['show_org_totals'] = true; $summary['org_totals'] = $this->summaryAll(); return $summary; } public function mslUsage($mslCode) { $mslCode = trim((string) $mslCode); $used = 0; if ($mslCode !== '') { $used = (int) DB::table('incept_neb_usage')->where('msl_code', $mslCode)->sum('qty'); } return [ 'msl_code' => $mslCode, 'used' => $used, 'limit' => self::MSL_LIMIT, 'remaining' => max(0, self::MSL_LIMIT - $used), ]; } public function resolveEmpIdFromUser($userId) { $userId = trim((string) $userId); if ($userId === '' || !Schema::hasTable('users')) { return ''; } $query = DB::table('users'); $user = (clone $query)->where('user_id', $userId)->first(); if (!$user) { $user = DB::table('users')->where('id', $userId)->first(); } if (!$user) { return ''; } foreach (['emp_employee_code', 'employee_code', 'emp_id'] as $column) { if (isset($user->{$column}) && trim((string) $user->{$column}) !== '') { return $this->normalizeEmpId($user->{$column}); } } return ''; } public function resolveEmpId($post, $formdata = null) { $candidates = []; if (is_array($post)) { foreach (['incept_emp_id', 'emp_id', 'employee_code'] as $key) { if (!empty($post[$key])) { $candidates[] = $post[$key]; } } if (!empty($post['user_id'])) { $candidates[] = $this->resolveEmpIdFromUser($post['user_id']); } } if (is_object($formdata) && isset($formdata->user_info)) { foreach (['employee_code', 'emp_employee_code', 'emp_id'] as $key) { if (!empty($formdata->user_info->{$key})) { $candidates[] = $formdata->user_info->{$key}; } } } if (is_object($formdata) && !empty($formdata->employee_code)) { $candidates[] = $formdata->employee_code; } if (is_object($formdata) && !empty($formdata->user_id)) { $candidates[] = $this->resolveEmpIdFromUser($formdata->user_id); } foreach ($candidates as $candidate) { $empId = $this->normalizeEmpId($candidate); if ($empId !== '') { return $empId; } } return ''; } public function reserveFromSubmission(array $post, $formdata) { $type = $this->typeKey($post['type_of_nebulizer'] ?? ''); $qty = (int) ($post['no_of_nebulizer'] ?? 0); $mslCode = trim((string) ($post['msl_code'] ?? '')); $empId = $this->resolveEmpId($post, $formdata); if ($type === null) { throw new InceptNebLimitException('Select a nebulizer type.'); } if ($qty < 1) { throw new InceptNebLimitException('Enter the number of nebulizers.'); } if ($mslCode === '') { throw new InceptNebLimitException('MSL code is required.'); } if ($empId === '') { throw new InceptNebLimitException('Employee allocation could not be matched.'); } $locks = []; try { $locks[] = $this->acquireLock('incept_neb_emp_' . $empId); $locks[] = $this->acquireLock('incept_neb_msl_' . $mslCode); return DB::transaction(function () use ($empId, $type, $qty, $mslCode) { $allocation = DB::table('incept_neb_allocations')->where('emp_id', $empId)->lockForUpdate()->first(); if (!$allocation) { throw new InceptNebLimitException('No nebulizer allocation is set for employee ' . $empId . '.'); } $column = self::TYPES[$type]['column']; $allocated = (int) ($allocation->{$column} ?? 0); $used = (int) DB::table('incept_neb_usage') ->where('emp_id', $empId) ->where('nebulizer_type', $type) ->lockForUpdate() ->sum('qty'); $balance = $allocated - $used; if ($qty > $balance) { throw new InceptNebLimitException( 'Only ' . max(0, $balance) . ' ' . self::TYPES[$type]['label'] . ' nebulizer(s) are left in your allocation.' ); } $mslUsed = (int) DB::table('incept_neb_usage')->where('msl_code', $mslCode)->lockForUpdate()->sum('qty'); if (($mslUsed + $qty) > self::MSL_LIMIT) { $remaining = max(0, self::MSL_LIMIT - $mslUsed); throw new InceptNebLimitException( 'MSL ' . $mslCode . ' can take ' . $remaining . ' more nebulizer(s). The overall limit is ' . self::MSL_LIMIT . '.' ); } return DB::table('incept_neb_usage')->insertGetId([ 'emp_id' => $empId, 'msl_code' => $mslCode, 'nebulizer_type' => $type, 'qty' => $qty, 'submission_id' => null, 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ]); }); } finally { foreach (array_reverse($locks) as $lock) { DB::select('SELECT RELEASE_LOCK(?)', [$lock]); } } } public function attachSubmission($usageId, $submissionId) { if (!$usageId) { return; } DB::table('incept_neb_usage')->where('id', $usageId)->update([ 'submission_id' => (string) $submissionId, 'updated_at' => date('Y-m-d H:i:s'), ]); } public function release($usageId) { if (!$usageId) { return; } DB::table('incept_neb_usage')->where('id', $usageId)->delete(); } public function releaseBySubmission($submissionId) { $submissionId = trim((string) $submissionId); if ($submissionId === '') { return; } DB::table('incept_neb_usage')->where('submission_id', $submissionId)->delete(); } public function typeKey($label) { $value = strtolower(trim((string) $label)); $value = str_replace([' ', '-'], '_', $value); if ($value === 'robust') { return 'robust'; } if ($value === 'mini') { return 'mini'; } if ($value === 'control_d' || $value === 'controld') { return 'control_d'; } return null; } public function normalizeEmpId($value) { if (is_int($value) || is_float($value)) { if ((float) $value === floor((float) $value)) { return (string) (int) $value; } } $value = trim((string) $value); $value = str_replace("\xc2\xa0", ' ', $value); $value = trim($value); if ($value !== '' && is_numeric($value) && strpos($value, '.') !== false) { $value = (string) (int) $value; } return $value; } private function formValue($formdata, $key) { if (is_object($formdata) && isset($formdata->{$key})) { return $formdata->{$key}; } if (is_array($formdata) && array_key_exists($key, $formdata)) { return $formdata[$key]; } return null; } private function acquireLock($name) { $name = substr($name, 0, 64); $row = DB::selectOne('SELECT GET_LOCK(?, 10) AS locked', [$name]); if (!$row || (int) $row->locked !== 1) { throw new InceptNebLimitException('Nebulizer allocation is busy. Please try again.'); } return $name; } }