55 lines
2.0 KiB
PHP
55 lines
2.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/** A narrow CLI execution scope, never a session, account or general authority bypass. */
|
|
final class ReplenishmentRun
|
|
{
|
|
private ?int $tenant = null;
|
|
|
|
private ?int $work = null;
|
|
|
|
private ?int $responsible = null;
|
|
|
|
public function tenantId(): ?int
|
|
{
|
|
return $this->tenant;
|
|
}
|
|
|
|
public function lockResponsible(User $user): User
|
|
{
|
|
abort_unless($this->tenant !== null && $user->tenant_id === $this->tenant && DB::transactionLevel() > 0 && ! auth()->check(), 403);
|
|
// A stock writer may have updated the signal while this worker waited for the company lock.
|
|
$responsible = DB::table('replenishment_work')->where('tenant_id', $this->tenant)->where('id', $this->work)->value('responsible_id');
|
|
abort_unless($user->id === $responsible, 403);
|
|
$this->responsible = $responsible;
|
|
|
|
return User::query()->where('tenant_id', $this->tenant)->whereKey($user->id)->lockForUpdate()->firstOrFail();
|
|
}
|
|
|
|
/** @return array<string,mixed> */
|
|
public function evidence(): array
|
|
{
|
|
return ['mode' => 'scheduled_replenishment', 'work_id' => $this->work, 'tenant_id' => $this->tenant, 'responsible_user_id' => $this->responsible, 'attribution' => 'responsible_for_trigger_or_rule; not_an_interactive_action'];
|
|
}
|
|
|
|
public function execute(int $id, callable $callback): mixed
|
|
{
|
|
abort_unless(config('work_context.composition') === 'sgc' && app()->runningInConsole() && ! auth()->check() && $this->tenant === null && (! request()->hasSession()), 403);
|
|
$work = DB::table('replenishment_work')->where('id', $id)->firstOrFail();
|
|
$this->tenant = $work->tenant_id;
|
|
$this->work = $work->id;
|
|
$this->responsible = $work->responsible_id;
|
|
try {
|
|
return $callback();
|
|
} finally {
|
|
$this->tenant = null;
|
|
$this->work = null;
|
|
$this->responsible = null;
|
|
}
|
|
}
|
|
}
|