Preserve shared budget adapter and limited technical acceptance
This commit is contained in:
@@ -2,68 +2,25 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Budget;
|
||||
use App\Models\BudgetConsumption;
|
||||
use App\Models\BudgetEntry;
|
||||
use App\Models\Requisition;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class BudgetWorkflow
|
||||
{
|
||||
/** Called inside the procurement transaction, after the company lock.
|
||||
* @return array<string,int>|null
|
||||
*/
|
||||
/** @return array<string,int>|null */
|
||||
public function record(User $user, Requisition $request, string $kind, int $amount = 0, ?int $subtotal = null): ?array
|
||||
{
|
||||
abort_unless(auth()->id() === $user->id && $user->tenant_id !== null && $request->tenant_id === $user->tenant_id, 403);
|
||||
if (DB::connection()->transactionLevel() === 0) {
|
||||
throw new \LogicException('Movimentos de orçamento exigem transacção.');
|
||||
}
|
||||
app(ConflictGuard::class)->lockCompany($user);
|
||||
$existing = BudgetEntry::query()->where('requisition_id', $request->id)->orderBy('id')->first();
|
||||
$budget = $existing
|
||||
? Budget::query()->lockForUpdate()->findOrFail($existing->budget_id)
|
||||
: Budget::query()->where('cost_center_id', $request->cost_center_id)->where('year', now()->year)->lockForUpdate()->first();
|
||||
// Unconfigured legacy processes remain development documents, explicitly unchecked.
|
||||
if (! $budget) {
|
||||
return null;
|
||||
}
|
||||
$requiredKind = $request->items()->where('purchase_type', 'equipment')->exists() ? 'investment' : 'operating';
|
||||
if ($budget->kind !== $requiredKind) {
|
||||
throw ValidationException::withMessages(['budget' => 'A dotação deve ter a classificação explícita adequada: investimento para processos com equipamento; corrente para os restantes.']);
|
||||
}
|
||||
if (! $budget->approved_by || ($kind !== 'release' && (int) $budget->year !== now()->year)) {
|
||||
throw ValidationException::withMessages(['budget' => 'Dotação pendente de aprovação ou exercício encerrado.']);
|
||||
}
|
||||
if (! $budget->includes_tax && in_array($kind, ['reserve', 'award', 'commit'])) {
|
||||
$amount = $subtotal ?? throw new \LogicException('Subtotal necessário para a base sem IVA.');
|
||||
}
|
||||
if (BudgetEntry::query()->where('requisition_id', $request->id)->where('kind', $kind)->exists()) {
|
||||
return ['budget_id' => $budget->id, 'year' => (int) $budget->year];
|
||||
}
|
||||
$own = BudgetEntry::query()->where('requisition_id', $request->id);
|
||||
$reserved = (int) (clone $own)->sum('reserved_minor');
|
||||
$committed = (int) $own->sum('committed_minor');
|
||||
if ($committed !== 0 || ($kind !== 'reserve' && ! $existing)) {
|
||||
throw ValidationException::withMessages(['budget' => 'Processo sem reserva válida ou já comprometido.']);
|
||||
}
|
||||
[$reserveDelta, $commitDelta] = match ($kind) {
|
||||
'reserve', 'award' => [$amount - $reserved, 0],
|
||||
'release' => [-$reserved, 0],
|
||||
'commit' => [-$reserved, $amount],
|
||||
default => throw new \LogicException('Movimento orçamental inválido.'),
|
||||
};
|
||||
$ledger = BudgetEntry::query()->where('budget_id', $budget->id);
|
||||
$used = (int) (clone $ledger)->sum('reserved_minor') + (int) $ledger->sum('committed_minor');
|
||||
$used += (int) BudgetConsumption::query()->where('budget_id', $budget->id)->sum('amount_minor');
|
||||
if ($used + $reserveDelta + $commitDelta > $budget->approvedTotalMinor()) {
|
||||
throw ValidationException::withMessages(['budget' => 'Saldo orçamental insuficiente. Não são permitidos excessos.']);
|
||||
}
|
||||
BudgetEntry::query()->create(['tenant_id' => $user->tenant_id, 'budget_id' => $budget->id, 'requisition_id' => $request->id, 'created_by' => $user->id, 'kind' => $kind, 'reserved_minor' => $reserveDelta, 'committed_minor' => $commitDelta, 'created_at' => now()]);
|
||||
app(RequisitionWorkflow::class)->audit($user, 'budget.'.$kind, $request, null, ['budget_id' => $budget->id, 'reserved_minor' => $reserveDelta, 'committed_minor' => $commitDelta]);
|
||||
$adapter = app(CommonBudget::class);
|
||||
|
||||
return ['budget_id' => $budget->id, 'year' => (int) $budget->year];
|
||||
return $adapter->execute(fn () => $adapter->for($user)->record(
|
||||
$request->id, $request->cost_center_id,
|
||||
$request->items()->where('purchase_type', 'equipment')->exists() ? 'investment' : 'operating',
|
||||
$kind, $amount, $subtotal,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user