Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,10 @@ O comando `php artisan db:seed` (inclusive via `migrate:fresh --seed`) respeita
| Valor | Dados criados |
|-------|---------------|
| `none` | Nenhum (padrao) |
| `users` | Permissoes, perfis e usuarios |
| `all` | Todos os dados de desenvolvimento |
| `users` | Seeders comuns, permissoes, perfis e usuarios |
| `all` | Seeders comuns e todos os dados de desenvolvimento |

Os seeders comuns sao definidos em `config/seeding.php` (`common`) e incluem o catalogo de bancos. O modo `none` nao executa esses seeders.

Depois de alterar `SEED_MODE` em um ambiente com configuracao em cache, execute
`php artisan config:clear` antes de rodar o seeder.
Expand Down
1 change: 1 addition & 0 deletions app/Http/Controllers/AgentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public function show(Request $request, Agent $agent)
'notice',
'category',
'opening',
'opening.bank',
'currentStage',
'budgets.installments',
])
Expand Down
8 changes: 8 additions & 0 deletions app/Http/Controllers/ProjectController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use App\Http\Requests\Project\CreateProjectDocumentRequest;
use App\Http\Resources\DocumentResource;
use App\Http\Resources\ProjectResource;
use App\Models\Bank;
use App\Models\Notice;
use App\Models\Project;
use App\Models\ProjectStage;
Expand All @@ -41,13 +42,15 @@ public function index(Request $request, Notice $notice)
'agent.latestSnapshot',
'category',
'opening',
'opening.bank',
'opening.supervisors',
'opening.principalSupervisor.user',
'documents',
'documents.project.agent.latestSnapshot',
'documents.project.notice',
'documents.project.notice.budgetAllocations',
'documents.project.opening.principalSupervisor.user',
'documents.project.opening.bank',
'documents.project.budgets.installments.budgetAllocation',
'currentStage',
'monitoring',
Expand Down Expand Up @@ -131,6 +134,7 @@ public function projectDetail(Request $request, Notice $notice, Project $project
'agent',
'category',
'opening',
'opening.bank',
'opening.supervisors' => function ($q) {
$q->whereNull('removed_at')
->orderByRaw("CASE type WHEN 'principal' THEN 0 WHEN 'alternate' THEN 1 ELSE 2 END");
Expand Down Expand Up @@ -169,6 +173,10 @@ public function projectDetail(Request $request, Notice $notice, Project $project
'usersAvailableForFormalization' => $usersAvailableForFormalization,
'agentStatus' => AgentStatus::options(),
'accountType' => AccountType::options(),
'banks' => Bank::orderByRaw('code IS NULL')
->orderBy('code')
->orderBy('name')
->get(['id', 'code', 'name', 'short_name']),
'reportStatus' => ReportStatus::options(),
'deliberation' => DeliberationType::options(),
'cgeAtendeStatus' => CgeAtendeStatus::options(),
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Requests/Opening/OpeningStoreRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function rules(): array

'creditor_number' => ['nullable', 'string', 'max:255'],

'bank' => ['nullable', 'string', 'max:255'],
'bank_id' => ['nullable', 'integer', 'exists:banks,id'],
'account_type' => ['nullable', 'string', 'max:100'],
'branch' => ['nullable', 'string', 'max:50'],
'account' => ['nullable', 'string', 'max:50'],
Expand Down
4 changes: 2 additions & 2 deletions app/Http/Requests/Opening/OpeningUpdateRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public function rules(): array
'opening.creditor_number' => ['nullable', 'string', 'max:255'],

// Dados Bancários
'opening.bank' => ['nullable', 'string', 'max:255'],
'opening.bank_id' => ['nullable', 'integer', 'exists:banks,id'],
'opening.account_type' => ['nullable', 'string', 'max:255'],
'opening.branch' => ['nullable', 'string', 'max:255'],
'opening.account' => ['nullable', 'string', 'max:255'],
Expand Down Expand Up @@ -117,7 +117,7 @@ public function attributes(): array
'opening.agent_status' => 'status do agente cultural',
'opening.opened_by' => 'responsável por abrir o processo',
'opening.creditor_number' => 'número do cadastro do credor',
'opening.bank' => 'banco',
'opening.bank_id' => 'banco',
Comment thread
Junior-Shyko marked this conversation as resolved.
'opening.account_type' => 'tipo de conta',
'opening.branch' => 'agência',
'opening.account' => 'conta',
Expand Down
3 changes: 2 additions & 1 deletion app/Http/Resources/OpeningResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public function toArray(Request $request): array

'creditor_number' => $this->creditor_number,

'bank' => $this->bank,
'bank_id' => $this->bank_id,
'bank' => $this->whenLoaded('bank'),
'account_type' => $this->account_type?->value,
'branch' => $this->branch,
'account' => $this->account,
Expand Down
18 changes: 18 additions & 0 deletions app/Models/Bank.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Bank extends Model
{
public $timestamps = false;

protected $fillable = ['ispb', 'code', 'name', 'short_name'];

public function openings(): HasMany
{
return $this->hasMany(Opening::class);
}
}
8 changes: 7 additions & 1 deletion app/Models/Opening.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use App\Traits\HasFiles;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
Expand All @@ -29,7 +30,7 @@ class Opening extends Model implements Auditable
'opened_by',
'created_by',
'creditor_number',
'bank',
'bank_id',
'account_type',
'branch',
'account',
Expand Down Expand Up @@ -71,6 +72,11 @@ public function project()
return $this->belongsTo(Project::class);
}

public function bank(): BelongsTo
{
return $this->belongsTo(Bank::class);
}

public function user()
{
return $this->belongsTo(User::class);
Expand Down
80 changes: 80 additions & 0 deletions app/Services/BankResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace App\Services;

use App\Models\Bank;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class BankResolver
{
private bool $loaded = false;

/** @var array<string, int> */
private array $codes = [];

/** @var array<string, array<int, int>> */
private array $names = [];

public function resolveBankId(string $value): ?int
{
$value = trim($value);
if ($value === '') {
return null;
}

$this->loadCatalog();

if (ctype_digit($value)) {
$id = $this->codes[str_pad($value, 3, '0', STR_PAD_LEFT)] ?? null;
} else {
$matches = array_values($this->names[$this->normalize($value)] ?? []);
if (count($matches) > 1) {
Log::warning('spreadsheet.import.bank_ambiguous', [
'bank' => $value,
'candidate_ids' => array_slice($matches, 0, 2),
]);

return null;
}

$id = $matches[0] ?? null;
}

if ($id === null) {
Log::warning('spreadsheet.import.bank_not_found', ['bank' => $value]);
}

return $id;
}

private function loadCatalog(): void
{
if ($this->loaded) {
return;
}

foreach (Bank::query()->get(['id', 'code', 'name', 'short_name']) as $bank) {
if ($bank->code !== null) {
$this->codes[$bank->code] = $bank->id;
}

foreach ([$bank->name, $bank->short_name] as $name) {
// Deduplicate aliases of the same institution, not distinct banks.
$this->names[$this->normalize($name)][$bank->id] = $bank->id;
}
}

$this->loaded = true;
}

private function normalize(string $name): string
{
return (string) Str::of($name)
->ascii()->lower()
->replaceMatches('/[^a-z0-9]+/', ' ')
->replaceMatches('/\bbco\b/', 'banco')
->replaceMatches('/\s+s\s*a\s*$/', '')
->trim();
}
}
3 changes: 2 additions & 1 deletion app/Services/Documents/DocumentPlaceholderResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class DocumentPlaceholderResolver
'project.agent.latestSnapshot',
'project.notice.budgetAllocations',
'project.opening.principalSupervisor.user',
'project.opening.bank',
'project.budgets.installments',
'project.category',
'project.budgets.installments.budgetAllocation',
Expand Down Expand Up @@ -76,7 +77,7 @@ public function resolve(Document $document): string
'[allocation_code]' => $budgetAllocation?->allocation_code ?? '',
'[allocation_number]' => $budgetAllocation?->allocation_number ?? '',
'[notice_installment_number]' => $currentInstallment?->notice_installment_number ?? '',
'[bank]' => $opening?->bank ?? '',
'[bank]' => $opening?->bank?->name ?? '',
'[account_type]' => $opening?->account_type?->label() ?? '',
'[branch]' => $opening?->branch ?? '',
'[account]' => $opening?->account ?? '',
Expand Down
2 changes: 1 addition & 1 deletion app/Services/OpeningUpdateService.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public function ensureCanAdvance(Project $project): void

$requiredOpeningFields = [
'creditor_number' => 'Número do cadastro do credor',
'bank' => 'Banco',
'bank_id' => 'Banco',
'account_type' => 'Tipo de conta',
'branch' => 'Agência',
'account' => 'Conta',
Expand Down
3 changes: 2 additions & 1 deletion app/Services/SpreadsheetImportService.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public function __construct(
private readonly CategoryService $categoryService,
private readonly AgentService $agentService,
private readonly UserService $userService,
private readonly BankResolver $bankResolver,
) {}

/**
Expand Down Expand Up @@ -160,7 +161,7 @@ private function resolveOpening(array $row, int $projectId, int $userId, ?User $
'opening_date' => $this->parseDate(trim((string) ($row['DATA ABERTURA DE PROCESSO'] ?? ''))),
'agent_status' => $this->mapAgentStatus(trim((string) ($row['STATUS'] ?? ''))),
'opened_by' => trim((string) ($row['RESPONSÁVEL POR ABRIR PROCESSO'] ?? '')),
'bank' => trim((string) ($row['BANCO'] ?? '')),
'bank_id' => $this->bankResolver->resolveBankId(trim((string) ($row['BANCO'] ?? ''))),
'account_type' => $this->mapAccountType(trim((string) ($row['TIPO DE CONTA'] ?? ''))),
'branch' => trim((string) ($row['AGÊNCIA'] ?? '')),
'account' => trim((string) ($row['CONTA'] ?? '')),
Expand Down
7 changes: 7 additions & 0 deletions config/seeding.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use Database\Seeders\BankSeeder;

return [

/*
Expand All @@ -13,4 +15,9 @@

'mode' => env('SEED_MODE', 'none'),

// Shared seeders for config tables
'common' => [
BankSeeder::class,
],
Comment thread
coderabbitai[bot] marked this conversation as resolved.

];
49 changes: 49 additions & 0 deletions cypress/e2e/efomento/projects/bank.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import ProjectWorkflow from '../../../support/workflows/ProjectWorkflow.js';
import LoginWorkflow from '../../../support/workflows/LoginWorkflow.js';

describe('Opening bank selection', () => {
it('searches the local catalog and preserves the selected bank after saving', () => {
// Vuetify overlays can defer resize notifications until the next frame.
cy.on('uncaught:exception', (error) => {
if (error.message.includes('ResizeObserver loop completed with undelivered notifications.')) return false;
});

cy.intercept('https://brasilapi.com.br/**', { forceNetworkError: true }).as('externalBanks');
cy.fixture('users').then(({ fomentation }) => {
LoginWorkflow.goToLoginPage();
LoginWorkflow.loginWithTwoFactorAndTrustDevice(fomentation.email, fomentation.password);
cy.location('pathname').should('eq', '/editais');
});

cy.fixture('notices').then(({ notice }) => {
cy.fixture('projects').then(({ opening }) => {
ProjectWorkflow.accessProjectByNup({ notice, project: { projectNup: opening.nup } });
});
});

cy.get('[data-cy="bank-select"] input').clear();
cy.get('[data-cy="bank-select"] input').type('banco do brasil');
cy.contains('[role="option"]', '001 - Banco do Brasil S.A.').should('be.visible');

cy.get('[data-cy="bank-select"] input').clear();
cy.get('[data-cy="bank-select"] input').type('itau');
cy.contains('[role="option"]', '341 - ITAÚ UNIBANCO S.A.').should('be.visible');

cy.get('[data-cy="bank-select"] input').clear();
cy.get('[data-cy="bank-select"] input').type('banco inexistente xyz');
cy.contains('Nenhum banco encontrado').should('be.visible');

cy.get('[data-cy="bank-select"] input').clear();
cy.get('[data-cy="bank-select"] input').type('001');
cy.contains('[role="option"]', '001 - Banco do Brasil S.A.').click();

cy.intercept('PATCH', '**/abertura/*/atualizar').as('saveOpening');
cy.contains('button', 'Salvar Alterações').click();
cy.wait('@saveOpening').its('request.body.opening.bank_id').should('be.a', 'number');
cy.contains('Abertura atualizada com sucesso').should('be.visible');
cy.reload();
cy.get('[data-cy="bank-select"]').should('contain', '001 - Banco do Brasil S.A.');

cy.get('@externalBanks.all').should('have.length', 0);
});
});
11 changes: 3 additions & 8 deletions database/factories/OpeningFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Enums\AccountType;
use App\Enums\AgentStatus;
use App\Enums\OpeningStatus;
use App\Models\Bank;
use App\Models\Opening;
use App\Models\Project;
use App\Models\User;
Expand Down Expand Up @@ -38,14 +39,8 @@ public function definition(): array

'creditor_number' => $this->faker->numerify(),

'bank' => $this->faker->randomElement([
'001',
'237',
'341',
'104',
'033',
'745',
]),
'bank_id' => Bank::whereIn('code', ['001', '237', '341', '104', '033', '745'])
->inRandomOrder()->value('id'),

'account_type' => $this->faker->randomElement(AccountType::cases())->value,

Expand Down
Loading
Loading