59 lines
2.6 KiB
PHP
59 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Enums\PurchasingRole;
|
|
use App\Models\Requisition;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Tempo\Finance\Budget\BudgetContext;
|
|
use Tempo\Finance\Budget\BudgetControl;
|
|
use Tempo\Finance\Budget\BudgetFailure;
|
|
|
|
/** Local host adapter: auth, tenant lock and audit; no budget arithmetic. */
|
|
final class CommonBudget
|
|
{
|
|
public function for(User $user): BudgetControl
|
|
{
|
|
abort_unless(auth()->id() === $user->id && $user->tenant_id !== null, 403);
|
|
abort_unless(DB::table('tenants')->where('id', $user->tenant_id)->value('currency') === 'MZN', 422, 'Controlo comum desta entrega suporta apenas MZN; câmbio permanece pendente.');
|
|
$context = new BudgetContext($user->id, ['tenant_id' => (int) $user->tenant_id], (int) now()->year, 'MZN');
|
|
|
|
return new BudgetControl(DB::connection(), $context, '',
|
|
function (string $action) use ($user): void {
|
|
app(ConflictGuard::class)->lockCompany($user);
|
|
abort_unless(DB::table('tenants')->where('id', $user->tenant_id)->value('currency') === 'MZN', 422, 'Moeda da organização incompatível com esta entrega.');
|
|
$roles = in_array($action, ['create', 'approve', 'reinforcement.propose', 'reinforcement.review', 'view'], true)
|
|
? [PurchasingRole::Admin, PurchasingRole::Finance]
|
|
: ($action === 'consume' ? [PurchasingRole::Finance] : null);
|
|
app(CurrentActor::class)->lock($user, $roles);
|
|
},
|
|
function (string $action, array $data, ?int $origin) use ($user): void {
|
|
// InvoiceWorkflow retains invoice.validated in the enclosing transaction.
|
|
if ($action !== 'budget.consumed') {
|
|
$request = $origin ? Requisition::query()->findOrFail($origin) : null;
|
|
app(RequisitionWorkflow::class)->audit($user, $action, $request, null, $data);
|
|
}
|
|
},
|
|
fn (int $id): bool => User::query()->where('tenant_id', $user->tenant_id)->whereKey($id)->lockForUpdate()->whereIn('purchasing_role', [PurchasingRole::Admin->value, PurchasingRole::Finance->value])->exists(),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @template T
|
|
*
|
|
* @param callable(): T $command
|
|
* @return T
|
|
*/
|
|
public function execute(callable $command): mixed
|
|
{
|
|
try {
|
|
return $command();
|
|
} catch (BudgetFailure $e) {
|
|
abort_if($e->status !== 422, $e->status, $e->getMessage());
|
|
throw ValidationException::withMessages([$e->field => $e->getMessage()]);
|
|
}
|
|
}
|
|
}
|