355 lines
12 KiB
PHP
355 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Exceptions\InceptNebLimitException;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
class InceptNebAllocation
|
|
{
|
|
public const FORM_ID = '1786346682-7ed41cf4-f562-4eb7-8d53-efedd0133f41';
|
|
public const MSL_LIMIT = 10;
|
|
|
|
public const TYPES = [
|
|
'robust' => ['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;
|
|
}
|
|
}
|