id === $next['preparer_id'], 'Preparador diferente da proposta.'); $purchase = Requisition::query()->findOrFail($next['purchase_id']); $check($purchase->status === 'approved' && $purchase->number === $next['purchase_number'] && $purchase->items()->sole()->quantity === '2.000' && $purchase->total_minor === 2320, 'Compra diferente da proposta.'); $check(Quotation::query()->where('requisition_id', $purchase->id)->count() === 0, 'Já existem propostas: não duplicar.'); $existing = Supplier::query()->findOrFail($next['existing_eligible_supplier_id']); $check($existing->eligible(), 'O fornecedor existente deixou de estar elegível.'); $protected = ['users', 'organization_access', 'organization_profiles', 'organization_memberships', 'purchase_orders', 'stock_movements', 'requisitions', 'requisition_items', 'budget_entries', 'purchase_awards']; $fingerprint = fn () => collect($protected)->mapWithKeys(fn ($table) => [$table => hash('sha256', json_encode(DB::table($table)->orderBy('id')->get(), JSON_THROW_ON_ERROR))])->all(); $baseline = $fingerprint(); $suppliersBefore = DB::table('suppliers')->orderBy('id')->get()->toArray(); $supplierIds = ['existing' => $existing->id]; $workflow = app(RequisitionWorkflow::class); foreach ($next['new_synthetic_suppliers'] as $index => $values) { $preparer = $actor('prepare'); $form = SaveSupplierRequest::create($proposal['environment'].'/fornecedores', 'POST', [...$next['synthetic_supplier_fields'], ...$values]); $form->setContainer($app)->setRedirector($app['redirect']); $form->setUserResolver(fn () => $preparer); $form->validateResolved(); app(SupplierController::class)->store($form, $workflow); $supplier = Supplier::query()->where('nuit', $values['nuit'])->sole(); $reviewer = $actor('review'); $check($reviewer->id === $next['supplier_reviewer_id'] && $supplier->created_by !== $reviewer->id, 'Conferência sem independência.'); $review = Request::create($proposal['environment'].'/fornecedores/'.$supplier->id.'/validar', 'POST', ['version' => $supplier->version, 'status' => 'active', 'comment' => 'Activação exclusivamente sintética para o piloto local; não representa validação comercial real.']); $review->setUserResolver(fn () => $reviewer); app(SupplierController::class)->review($review, $supplier, $workflow); $supplierIds[$index === 0 ? 'alternative_a' : 'alternative_b'] = $supplier->id; } $preparer = $actor('prepare'); foreach ($next['quotations'] as $quote) { $input = Request::create($proposal['environment'].'/requisicoes/'.$purchase->id.'/cotacoes', 'POST', ['supplier_id' => $supplierIds[$quote['supplier']], 'reference' => $quote['reference'], 'delivery_days' => $quote['delivery_days'], 'valid_until' => $next['quotation_valid_until'], 'payment_terms' => $next['payment_terms'], 'no_conflict' => true, 'items' => [['requisition_item_id' => $purchase->items()->sole()->id, 'unit_price' => $quote['unit_price']]]]); $input->setUserResolver(fn () => $preparer); app(QuotationController::class)->store($input, $purchase, $workflow); $saved = Quotation::query()->where('requisition_id', $purchase->id)->where('reference', $quote['reference'])->sole(); $check($saved->total_minor === $quote['expected_total_minor'], 'Totais diferentes da proposta sintética.'); } $check($fingerprint() === $baseline && Quotation::query()->where('requisition_id', $purchase->id)->count() === 3, 'A consulta alterou dados fora do âmbito.'); $check(DB::table('suppliers')->whereNotIn('id', [$supplierIds['alternative_a'], $supplierIds['alternative_b']])->orderBy('id')->get()->toArray() == $suppliersBefore, 'Cadastro anterior alterado.'); $check(Quotation::query()->where('requisition_id', $purchase->id)->withCount('evaluations')->get()->sum('evaluations_count') === 0, 'Não avaliar automaticamente.'); return ['supplier_ids' => $supplierIds, 'quotation_ids' => Quotation::query()->where('requisition_id', $purchase->id)->orderBy('id')->pluck('id')->all(), 'protected_tables' => $protected]; }