175 lines
8.8 KiB
PHP
175 lines
8.8 KiB
PHP
<?php
|
|
|
|
namespace Tempo\Purchasing\SgfCandidate;
|
|
|
|
use App\Models\User;
|
|
use App\Modules\Identity\Services\Permissions;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
use Tempo\Purchasing\Internal\InternalRequisitions;
|
|
use Tempo\Purchasing\Internal\RequisitionFailure;
|
|
|
|
final class Controller
|
|
{
|
|
private function project(Port $port, array $record): array
|
|
{
|
|
$actions = [];
|
|
if ($record['status'] === 'draft' && $record['requester_id'] === $port->actorId()) {
|
|
try {
|
|
$port->authorize('save', $record);
|
|
$actions = ['edit', 'submit'];
|
|
} catch (HttpException $e) {
|
|
if (! in_array($e->getStatusCode(), [403, 404], true)) {
|
|
throw $e;
|
|
}
|
|
}
|
|
} elseif (($next = InternalRequisitions::nextStep($record, $port->actorId())) !== null) {
|
|
try {
|
|
$port->authorize('step:'.$next['role'], $record);
|
|
$actions = ['approved'];
|
|
$s = $port->scope();
|
|
if (app(Permissions::class)->has($port->actorId(), $s['tenant_id'], $s['company_id'], 'purchasing_budget.record', $s['unit_id'])) {
|
|
$actions[] = 'rejected';
|
|
}
|
|
} catch (HttpException $e) {
|
|
if (! in_array($e->getStatusCode(), [403, 404], true)) {
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $record + ['allowed_actions' => $actions];
|
|
}
|
|
|
|
private function port(Request $request, string $key = ''): Port
|
|
{
|
|
$actor = $request->user();
|
|
abort_unless($actor instanceof User, 403);
|
|
|
|
return new Port($actor, $key);
|
|
}
|
|
|
|
public function index(Request $request)
|
|
{
|
|
$port = $this->port($request);
|
|
$data = $port->atomic(function () use ($port) {
|
|
if (! $port->query('policies')->exists()) {
|
|
$port->authorize('configure', null);
|
|
|
|
return ['context' => 'procurement.internal', 'scope' => $port->scope(), 'records' => [], 'centres' => [], 'configurationSources' => $port->configurationSources(), 'policy' => null, 'permissions' => ['configure' => true, 'prepare' => false]];
|
|
}
|
|
$port->authorize('view', null);
|
|
$records = [];
|
|
foreach ($port->query('requests')->orderByDesc('id')->limit(100)->pluck('id') as $id) {
|
|
$record = $port->load((int) $id);
|
|
try {
|
|
$port->authorize('view', $record);
|
|
$records[] = $this->project($port, $record);
|
|
} catch (HttpException $e) {
|
|
if ($e->getStatusCode() !== 404) {
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|
|
$policy = $port->setup();
|
|
|
|
return ['context' => 'procurement.internal', 'scope' => $port->scope(), 'records' => $records, 'centres' => $port->allowedCentres(), 'configurationSources' => $port->configurationSources(), 'policy' => $port->has('configure') ? $policy : array_intersect_key($policy, array_flip(['revision'])),
|
|
'permissions' => array_combine(['prepare', 'configure', 'department_head', 'procurement', 'finance'], array_map(fn ($p) => $port->has($p), ['prepare', 'configure', 'approve.department_head', 'approve.procurement', 'approve.finance']))];
|
|
});
|
|
|
|
return response()->json($data)->header('Cache-Control', 'private, no-store');
|
|
}
|
|
|
|
public function show(Request $request, int $id)
|
|
{
|
|
$port = $this->port($request);
|
|
$result = $port->atomic(function () use ($port, $id) {
|
|
$record = $port->load($id);
|
|
$port->authorize('view', $record);
|
|
|
|
return ['record' => $this->project($port, $record), 'history' => $port->query('events')->where('request_id', $id)->orderBy('version')->get()];
|
|
});
|
|
|
|
return response()->json($result)->header('Cache-Control', 'private, no-store');
|
|
}
|
|
|
|
public function command(Request $request, string $action, ?int $id = null)
|
|
{
|
|
$meta = $request->validate(['operation_key' => 'required|uuid', 'version' => ($id === null ? 'nullable' : 'required').'|integer|min:1']);
|
|
$port = $this->port($request, $meta['operation_key']);
|
|
$input = $request->except(['operation_key', 'version']);
|
|
if ($action === 'submit') {
|
|
abort_unless($input === [], 422, 'Campo protegido.');
|
|
}
|
|
if ($action === 'decide') {
|
|
$input = Validator::make($input, ['decision' => 'required|in:approved,rejected', 'comment' => 'nullable|required_if:decision,rejected|string|max:2000'])->validate();
|
|
}
|
|
try {
|
|
$result = (new InternalRequisitions($port))->command($action, $id, $meta['version'] ?? null, $meta['operation_key'], $input);
|
|
} catch (RequisitionFailure $e) {
|
|
return response()->json(['message' => $e->getMessage()], $e->status);
|
|
}
|
|
|
|
return response()->json($result)->header('Cache-Control', 'private, no-store');
|
|
}
|
|
|
|
public function configure(Request $request)
|
|
{
|
|
$port = $this->port($request);
|
|
$data = $request->validate(['version' => 'required|integer|min:0', 'reason' => 'required|string|min:10|max:4000', 'enabled' => 'required|boolean', 'budget_required' => 'required|boolean',
|
|
'first_limit_minor' => 'required|integer|min:0|max:100000000000000', 'second_limit_minor' => 'required|integer|min:0|max:100000000000000', 'approval_includes_tax' => 'required|boolean',
|
|
'centres' => 'required|array|min:1', 'centres.*.id' => 'required|integer|distinct|min:1', 'centres.*.department_ref' => 'required|string|max:100',
|
|
'members' => 'required|array|min:1', 'members.*.user_id' => 'required|integer|distinct|min:1', 'members.*.department_ref' => 'required|string|max:100']);
|
|
abort_if(array_diff(array_keys($request->all()), array_keys($data)) !== [], 422, 'Campo protegido.');
|
|
$port->atomic(function () use ($port, $data) {
|
|
$port->authorize('configure', null);
|
|
$scope = $port->scope();
|
|
$old = $port->query('policies')->lockForUpdate()->first();
|
|
abort_unless((int) ($old->version ?? 0) === $data['version'], 409, 'Política alterada.');
|
|
abort_unless($data['second_limit_minor'] >= $data['first_limit_minor'], 422, 'Limites inválidos.');
|
|
foreach ($data['centres'] as $c) {
|
|
abort_unless(DB::table('cb_centres')->where($scope)->where('id', $c['id'])->exists(), 403, 'Centro não autorizado.');
|
|
}
|
|
foreach ($data['members'] as $m) {
|
|
abort_unless(DB::table('company_memberships')->where(['tenant_id' => $scope['tenant_id'], 'company_id' => $scope['company_id'], 'user_id' => $m['user_id']])->exists(), 403, 'Pessoa não autorizada.');
|
|
abort_unless(app(Permissions::class)->has($m['user_id'], $scope['tenant_id'], $scope['company_id'], 'unit.view', $scope['unit_id']), 403, 'Unidade não autorizada.');
|
|
}
|
|
$payload = $data;
|
|
unset($payload['version']);
|
|
$payload['rules_provisional'] = false;
|
|
$values = ['version' => $data['version'] + 1, 'actor_id' => $port->actorId(), 'payload' => json_encode($payload, JSON_THROW_ON_ERROR), 'created_at' => now()];
|
|
if ($old) {
|
|
$port->query('policies')->update($values);
|
|
} else {
|
|
DB::table('ir_policies')->insert($scope + $values);
|
|
}
|
|
DB::table('ir_policy_versions')->insert($scope + $values);
|
|
});
|
|
|
|
return response()->json(['configured' => true, 'version' => $data['version'] + 1]);
|
|
}
|
|
|
|
public function page(Request $request)
|
|
{
|
|
$request->headers->set('X-Purchasing-Context', 'procurement.internal');
|
|
$port = $this->port($request);
|
|
$port->atomic(fn () => $port->authorize($port->query('policies')->exists() ? 'view' : 'configure', null));
|
|
$selection = $request->session()->get('company_context', []);
|
|
$scope = $port->scope();
|
|
$contract = ['csrf' => csrf_token(), 'token' => $selection['token'] ?? '', 'scope' => $scope, 'context' => 'procurement.internal', 'actor_id' => $port->actorId(),
|
|
'company' => DB::table('companies')->where('id', $scope['company_id'])->value('name'), 'unit' => DB::table('units')->where('id', $scope['unit_id'])->value('name')];
|
|
ob_start();
|
|
require __DIR__.'/screen.php';
|
|
$html = ob_get_clean();
|
|
|
|
return response($html)->header('Cache-Control', 'private, no-store');
|
|
}
|
|
|
|
public function script(Request $request)
|
|
{
|
|
return response(file_get_contents(__DIR__.'/screen.mjs'))->header('Content-Type', 'text/javascript')->header('Cache-Control', 'private, no-store');
|
|
}
|
|
}
|