Rapport-Laravel/app/Services/InceptNebAllocation.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;
}
}