Skip to content

Commit 95d4eec

Browse files
feat: gerar número do termo automaticamente
1 parent a2cb7fd commit 95d4eec

9 files changed

Lines changed: 318 additions & 3 deletions

File tree

app/Enums/InstrumentType.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,22 @@ enum InstrumentType: string
1212
case PREMIACAO = 'PREMIAÇÃO';
1313
case AQUISICAO_CONTRATO = 'AQUISIÇÃO/CONTRATO';
1414
case PATROCINIO_CONTRATO = 'PATROCÍNIO/CONTRATO';
15+
case BOLSA_CULTURAL = 'TERMO DE BOLSA CULTURAL';
1516

1617
public static function values(): array
1718
{
1819
return array_column(self::cases(), 'value');
1920
}
21+
22+
public function initialBaseNumber(): int
23+
{
24+
return match ($this) {
25+
self::EXECUCAO_CULTURAL => 417,
26+
self::FOMENTO => 21,
27+
self::COLABORACAO => 29,
28+
self::PREMIACAO => 67,
29+
self::BOLSA_CULTURAL => 10,
30+
default => 0,
31+
};
32+
}
2033
}

app/Models/InstrumentSequence.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use App\Enums\InstrumentType;
6+
use Illuminate\Database\Eloquent\Factories\HasFactory;
7+
use Illuminate\Database\Eloquent\Model;
8+
9+
class InstrumentSequence extends Model
10+
{
11+
use HasFactory;
12+
13+
protected $fillable = [
14+
'instrument_type',
15+
'year',
16+
'current_number',
17+
'initial_number',
18+
];
19+
20+
protected $casts = [
21+
'instrument_type' => InstrumentType::class,
22+
'year' => 'integer',
23+
'current_number' => 'integer',
24+
'initial_number' => 'integer',
25+
];
26+
}

app/Services/Documents/DocumentPlaceholderResolver.php

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22

33
namespace App\Services\Documents;
44

5+
use App\Enums\InstrumentType;
56
use App\Models\BudgetAllocation;
67
use App\Models\Document;
8+
use App\Models\Formalization;
79
use App\Models\Notice;
810
use App\Models\Project;
911
use App\Services\BudgetAllocationResolver;
12+
use App\Services\InstrumentSequenceService;
13+
use Illuminate\Support\Collection;
1014

1115
class DocumentPlaceholderResolver
1216
{
@@ -18,10 +22,12 @@ class DocumentPlaceholderResolver
1822
'project.budgets.installments',
1923
'project.category',
2024
'project.budgets.installments.budgetAllocation',
25+
'project.formalizations',
2126
];
2227

2328
public function __construct(
2429
private readonly BudgetAllocationResolver $budgetAllocationResolver,
30+
private readonly InstrumentSequenceService $sequenceService,
2531
) {}
2632

2733
public function prepare(Document $document): Document
@@ -42,13 +48,15 @@ public function resolve(Document $document): string
4248
$currentInstallment = $document->project?->budgets?->installments
4349
?->firstWhere('installment_number', $document->project?->current_installment_cycle);
4450
$body = (string) $document->body;
45-
// Budget-opinion content uses the current installment, while notice-level documents
46-
// fall back to the notice's latest allocation for every allocation placeholder.
51+
52+
$termNumber = $this->resolveTermNumber($document->project, $body);
53+
4754
$budgetAllocation = str_contains($body, '[budget_allocation_data]') || ! $document->project
4855
? $this->budgetAllocationResolver->resolveForBudgetOpinion($document->project, $notice)
4956
: $this->budgetAllocationResolver->resolve($document->project);
5057

5158
$replacements = [
59+
'[numero_termo]' => $termNumber,
5260
'[notice_name]' => $notice?->name ?? '',
5361
'[nup_mother]' => $notice?->nup ?? '',
5462
'[project_nup]' => $opening?->opening_nup ?? '',
@@ -82,6 +90,50 @@ public function resolve(Document $document): string
8290
return $this->replaceBudgetAllocationsByRegionTable($body, $notice);
8391
}
8492

93+
private function resolveTermNumber(?Project $project, string $body): string
94+
{
95+
if (! $project) {
96+
return '';
97+
}
98+
99+
$formalization = $project->formalizations instanceof Collection
100+
? $project->formalizations->first()
101+
: $project->formalizations;
102+
103+
if ($formalization && ! empty($formalization->term_number)) {
104+
return $formalization->term_number;
105+
}
106+
107+
if (! str_contains($body, '[numero_termo]')) {
108+
return $formalization?->term_number ?? '';
109+
}
110+
111+
$notice = $project->notice;
112+
$instrumentTypeString = $notice?->instrument_type;
113+
114+
$instrumentType = $instrumentTypeString
115+
? InstrumentType::tryFrom($instrumentTypeString)
116+
: null;
117+
118+
if (! $instrumentType) {
119+
$instrumentType = InstrumentType::EXECUCAO_CULTURAL;
120+
}
121+
122+
if (! $formalization) {
123+
$formalization = Formalization::create([
124+
'project_id' => $project->id,
125+
]);
126+
}
127+
128+
$termNumber = $this->sequenceService->generateNextTermNumber($instrumentType);
129+
130+
$formalization->update([
131+
'term_number' => $termNumber,
132+
]);
133+
134+
return $termNumber;
135+
}
136+
85137
public function replaceBudgetAllocationData(string $content, ?Project $project, ?Notice $notice = null): string
86138
{
87139
if (! str_contains($content, '[budget_allocation_data]')) {

app/Services/Documents/DocumentService.php

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55
use App\Enums\DocumentPhase;
66
use App\Enums\DocumentStatus;
77
use App\Enums\DocumentType;
8+
use App\Enums\InstrumentType;
89
use App\Models\Document;
910
use App\Models\DocumentImage;
11+
use App\Models\Formalization;
12+
use App\Models\Project;
13+
use App\Services\InstrumentSequenceService;
1014
use Illuminate\Support\Collection;
1115

1216
class DocumentService
@@ -19,6 +23,7 @@ class DocumentService
1923
public function __construct(
2024
private readonly DocumentTypeRegistry $registry,
2125
private readonly DocumentPlaceholderResolver $placeholderResolver,
26+
private readonly InstrumentSequenceService $sequenceService
2227
) {}
2328

2429
public function create(array $data, int $createdBy): Document
@@ -28,6 +33,9 @@ public function create(array $data, int $createdBy): Document
2833
DocumentPhase::from($data['phase']),
2934
);
3035

36+
// Gera número do termo e substitui a tag se for um TC
37+
$data['body'] = $this->resolveTermNumberAndReplacePlaceholder($data['project_id'] ?? null, $data['type'], $data['body']);
38+
3139
$document = Document::create([
3240
'notice_id' => $data['notice_id'],
3341
'project_id' => $data['project_id'] ?? null,
@@ -47,8 +55,14 @@ public function create(array $data, int $createdBy): Document
4755

4856
public function update(Document $document, array $data): Document
4957
{
58+
// Se houver atualização de conteúdo, garante que a tag (se inserida) vire número novamente
59+
$body = $data['body'] ?? $document->body;
60+
$typeValue = $document->type instanceof DocumentType ? $document->type->value : $document->type;
61+
62+
$data['body'] = $this->resolveTermNumberAndReplacePlaceholder($document->project_id, $typeValue, $body);
63+
5064
$document->update([
51-
'body' => $data['body'] ?? $document->body,
65+
'body' => $data['body'],
5266
'status' => $data['status'] ?? $document->status,
5367
]);
5468

@@ -59,6 +73,48 @@ public function update(Document $document, array $data): Document
5973
return $this->placeholderResolver->prepare($document->fresh());
6074
}
6175

76+
/**
77+
* Resolve o número do termo (gerando atomicamente, se necessário)
78+
* e substitui a placeholder no corpo do documento.
79+
*/
80+
private function resolveTermNumberAndReplacePlaceholder(?int $projectId, string $documentType, string $bodyContent): string
81+
{
82+
// Só aplicamos a lógica se for o Termo de Execução Cultural (TC) e se tivermos um projeto.
83+
if ($documentType !== DocumentType::TC->value || ! $projectId) {
84+
return $bodyContent;
85+
}
86+
87+
$project = Project::with(['notice'])->find($projectId);
88+
89+
if (! $project || ! $project->notice || ! $project->notice->instrument_type) {
90+
return $bodyContent; // Não é possível gerar sem o tipo de instrumento do edital
91+
}
92+
93+
// Recupera ou cria a Formalização vinculada ao Projeto
94+
$formalization = Formalization::firstOrCreate([
95+
'project_id' => $project->id,
96+
]);
97+
98+
$termNumber = $formalization->term_number;
99+
100+
// Se a Formalização ainda não tiver o número, nós o geramos de forma atômica
101+
if (! $termNumber) {
102+
// Assumimos que 'instrument_type' no Notice pode ser instanciado no nosso Enum.
103+
$instrumentType = InstrumentType::tryFrom($project->notice->instrument_type)
104+
?? InstrumentType::fromName($project->notice->instrument_type); // Ajuste conforme salva no Notice
105+
106+
$termNumber = $this->sequenceService->generateNextTermNumber($instrumentType);
107+
108+
// Salva o número recém gerado
109+
$formalization->update([
110+
'term_number' => $termNumber,
111+
]);
112+
}
113+
114+
// Substitui a tag customizada pelo número do termo (ex: "418/2026")
115+
return str_replace('[numero_termo]', $termNumber, $bodyContent);
116+
}
117+
62118
public function getByContext(
63119
?int $noticeId = null,
64120
?int $projectId = null,

app/Services/FormalizationService.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use App\Contracts\StageValidatorInterface;
66
use App\Enums\DocumentPhase;
77
use App\Enums\DocumentType;
8+
use App\Enums\InstrumentType;
89
use App\Models\File;
910
use App\Models\Formalization;
1011
use App\Models\Project;
@@ -14,6 +15,30 @@
1415

1516
class FormalizationService implements StageValidatorInterface
1617
{
18+
public function __construct(
19+
private readonly InstrumentSequenceService $sequenceService
20+
) {}
21+
22+
public function generateTermNumberForFormalization(Formalization $formalization, InstrumentType $instrumentType, ?int $year = null): string
23+
{
24+
if ($formalization->term_number) {
25+
return $formalization->term_number;
26+
}
27+
28+
$termNumber = $this->sequenceService->generateNextTermNumber($instrumentType, $year);
29+
30+
$formalization->update([
31+
'term_number' => $termNumber,
32+
]);
33+
34+
return $termNumber;
35+
}
36+
37+
public function replaceTermNumberPlaceholder(string $documentContent, string $termNumber): string
38+
{
39+
return str_replace('[numero_termo]', $termNumber, $documentContent);
40+
}
41+
1742
public function deleteFile(File $file): void
1843
{
1944
$disk = config('filesystems.default', 'local');
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace App\Services;
4+
5+
use App\Enums\InstrumentType;
6+
use App\Models\InstrumentSequence;
7+
use Illuminate\Support\Facades\DB;
8+
9+
class InstrumentSequenceService
10+
{
11+
public function generateNextTermNumber(InstrumentType $instrumentType, ?int $year = null): string
12+
{
13+
$year = $year ?? (int) now()->format('Y');
14+
15+
return DB::transaction(function () use ($instrumentType, $year) {
16+
$sequence = InstrumentSequence::where('instrument_type', $instrumentType->value)
17+
->where('year', $year)
18+
->lockForUpdate()
19+
->first();
20+
21+
if (! $sequence) {
22+
$baseNumber = $instrumentType->initialBaseNumber();
23+
24+
$sequence = InstrumentSequence::create([
25+
'instrument_type' => $instrumentType,
26+
'year' => $year,
27+
'initial_number' => $baseNumber,
28+
'current_number' => $baseNumber,
29+
]);
30+
}
31+
32+
$sequence->increment('current_number');
33+
34+
return "{$sequence->current_number}/{$year}";
35+
});
36+
}
37+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::create('instrument_sequences', function (Blueprint $table) {
15+
$table->id();
16+
$table->string('instrument_type', 100);
17+
$table->integer('year');
18+
$table->integer('current_number')->default(0);
19+
$table->integer('initial_number')->default(1);
20+
$table->timestamps();
21+
22+
$table->unique(['instrument_type', 'year'], 'unique_instrument_year_seq');
23+
});
24+
}
25+
26+
/**
27+
* Reverse the migrations.
28+
*/
29+
public function down(): void
30+
{
31+
Schema::dropIfExists('instrument_sequences');
32+
}
33+
};

resources/js/Schemas/Config/documentConfig.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const noticePlaceHoldersDocsSchema = [
4545

4646
const placeHoldersDocsSchema = [
4747
...noticePlaceHoldersDocsSchema,
48+
{ label: 'Número do Termo', value: 'numero_termo' },
4849
{ label: 'Nup Projeto', value: 'project_nup' },
4950
{ label: 'Nome do Projeto', value: 'project_name' },
5051
{ label: 'Nome do Agente', value: 'agent_name' },

0 commit comments

Comments
 (0)