83 lines
4.3 KiB
PHP
83 lines
4.3 KiB
PHP
<?php
|
|
|
|
namespace App\Modules\Purchasing;
|
|
|
|
use App\Models\User;
|
|
use App\Modules\Identity\Services\Permissions;
|
|
use App\Shared\Context\CompanyContext;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Tempo\Finance\Budget\BudgetContext;
|
|
use Tempo\Finance\Budget\BudgetControl;
|
|
|
|
/** SGF composition adapter; synthetic-only until schema/rights are explicitly deployed. */
|
|
final class BudgetAdapter
|
|
{
|
|
public static function guard(): void
|
|
{
|
|
abort_unless(app()->environment('budget-testing'), 403, 'Controlo orçamental comum disponível apenas no ensaio isolado.');
|
|
$c = DB::connection();
|
|
$name = $c->getDatabaseName();
|
|
if ($c->getDriverName() !== 'pgsql' || ! preg_match('/^budget_core_sgf_[a-f0-9]{12}$/D', $name)
|
|
|| (string) $c->getConfig('host') !== '127.0.0.1' || ! empty($c->getConfig('url'))
|
|
|| $c->selectOne('select current_database() as db')->db !== $name) {
|
|
throw new \LogicException('COMMON_BUDGET_QA_DATABASE_REFUSED');
|
|
}
|
|
}
|
|
|
|
public function for(User $user): BudgetControl
|
|
{
|
|
self::guard();
|
|
$selection = app(CompanyContext::class);
|
|
abort_unless(auth()->id() === $user->id && $selection->userId() === $user->id && $selection->unitId() !== null, 403);
|
|
$scope = $selection->identifiers() + ['unit_id' => $selection->unitId()];
|
|
$context = new BudgetContext($user->id, $scope, (int) now()->year, 'MZN');
|
|
|
|
return new BudgetControl(DB::connection(), $context, 'cb_',
|
|
function (string $action, BudgetContext $ctx): void {
|
|
self::guard();
|
|
$s = $ctx->scope;
|
|
DB::table('companies')->where('tenant_id', $s['tenant_id'])->where('id', $s['company_id'])->lockForUpdate()->firstOrFail();
|
|
DB::table('users')->where('id', $ctx->actorId)->lockForUpdate()->firstOrFail();
|
|
abort_unless(auth()->id() === $ctx->actorId, 403);
|
|
$current = app(CompanyContext::class);
|
|
abort_unless($current->identifiers() === array_intersect_key($s, array_flip(['tenant_id', 'company_id'])) && $current->unitId() === $s['unit_id'], 409, 'O contexto mudou.');
|
|
$current->selectFor($ctx->actorId, $s['tenant_id'], $s['company_id'], $s['unit_id']);
|
|
app(Permissions::class)->require($ctx->actorId, $s['tenant_id'], $s['company_id'], 'purchasing_budget.'.$action, $s['unit_id']);
|
|
},
|
|
function (string $action, array $data, ?int $origin, BudgetContext $ctx): void {
|
|
DB::table('cb_audit')->insert($ctx->scope + ['actor_id' => $ctx->actorId, 'action' => $action, 'origin_id' => $origin, 'payload' => json_encode($data, JSON_THROW_ON_ERROR), 'created_at' => now()]);
|
|
},
|
|
function (int $author, BudgetContext $ctx): bool {
|
|
$s = $ctx->scope;
|
|
DB::table('users')->where('id', $author)->lockForUpdate()->firstOrFail();
|
|
|
|
return DB::table('company_memberships')->where(['user_id' => $author, 'tenant_id' => $s['tenant_id'], 'company_id' => $s['company_id']])->exists()
|
|
&& app(Permissions::class)->has($author, $s['tenant_id'], $s['company_id'], 'purchasing_budget.reinforcement.propose', $s['unit_id']);
|
|
},
|
|
);
|
|
}
|
|
|
|
/** A mapped procurement reference, never an industrial lot or a department inferred as a unit. */
|
|
public function record(User $user, int $origin, string $kind, int $amount = 0, ?int $subtotal = null): ?array
|
|
{
|
|
self::guard();
|
|
|
|
return DB::transaction(function () use ($user, $origin, $kind, $amount, $subtotal) {
|
|
$engine = $this->for($user);
|
|
$source = DB::table('cb_origins')->where($engine->context->scope)->where('id', $origin)->first();
|
|
abort_unless($source, 403, 'Origem de compras não mapeada neste contexto.');
|
|
|
|
return $engine->record($origin, (int) $source->cost_center_id, $source->required_kind, $kind, $amount, $subtotal);
|
|
});
|
|
}
|
|
|
|
public function create(User $user, array $data): int
|
|
{
|
|
self::guard();
|
|
$engine = $this->for($user);
|
|
abort_unless(DB::table('cb_centres')->where($engine->context->scope)->where('id', $data['cost_center_id'])->exists(), 403, 'Centro não mapeado neste contexto.');
|
|
|
|
return $engine->create($data);
|
|
}
|
|
}
|