diff --git a/CHANGELOG.md b/CHANGELOG.md index 51793a5..ea2c44e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ no [contrato de saída](docs/guia/contrato-de-saida.md). ## [Unreleased] +### Fixed + +- **`financeiro alteracoes` não paginava.** A API aceita `pagina` e + `tamanho_pagina` (iguais às outras listagens financeiras), mas o comando + não expunha `--pagina`/`--tamanho-pagina` e o cliente nem mandava os + parâmetros. Passa a enviar os dois, com o mesmo default do restante do + CLI (`1` / `50`). + ### Changed - **Requisito mínimo de PHP subiu de 8.4 para 8.4.1.** O código do CLI não diff --git a/docs/_data/commands/financeiro.yaml b/docs/_data/commands/financeiro.yaml index 8147689..75d3c92 100644 --- a/docs/_data/commands/financeiro.yaml +++ b/docs/_data/commands/financeiro.yaml @@ -5,7 +5,7 @@ sections: - 'financeiro alteracoes' status: verified endpoints: - 'financeiro alteracoes': 'ISO 8601 **sem timezone**' + 'financeiro alteracoes': 'GET /v1/financeiro/eventos-financeiros/alteracoes' params: data-inicio: label: '`--data-inicio`' @@ -17,6 +17,16 @@ sections: required: não¹ default: 'fim do mês corrente' description: 'ISO 8601 **sem timezone**' + pagina: + label: '`--pagina`' + required: não + default: '`1`' + description: 'Número da página' + tamanho-pagina: + label: '`--tamanho-pagina`' + required: não + default: '`50`' + description: 'Itens por página' body: | Feed de alterações no período — o caminho para reconciliar escritas que terminaram em `ambiguous`. diff --git a/docs/commands.json b/docs/commands.json index 326f931..7db69fb 100644 --- a/docs/commands.json +++ b/docs/commands.json @@ -744,7 +744,7 @@ { "arguments": [], "description": "Lista alterações de eventos financeiros num intervalo (útil para reconciliação)", - "endpoint": "ISO 8601 **sem timezone**", + "endpoint": "GET /v1/financeiro/eventos-financeiros/alteracoes", "group": "financeiro", "name": "financeiro alteracoes", "options": [ @@ -761,6 +761,20 @@ "multiple": false, "name": "--data-fim", "takes_value": true + }, + { + "default": 1, + "description": "Número da página", + "multiple": false, + "name": "--pagina", + "takes_value": true + }, + { + "default": 50, + "description": "Itens por página", + "multiple": false, + "name": "--tamanho-pagina", + "takes_value": true } ], "verified": true diff --git a/docs/financial-apis-openapi.yaml b/docs/financial-apis-openapi.yaml index 4c47e8e..beb8521 100644 --- a/docs/financial-apis-openapi.yaml +++ b/docs/financial-apis-openapi.yaml @@ -47,7 +47,7 @@ paths: /v1/financeiro/eventos-financeiros/{id_evento}/parcelas: get: { summary: "Parcelas de um evento financeiro (sem comando no CLI)" } /v1/financeiro/eventos-financeiros/alteracoes: - get: { summary: "Feed de alterações; params data_inicio e data_fim em ISO 8601 SEM timezone" } + get: { summary: "Feed de alterações; params data_inicio e data_fim em ISO 8601 SEM timezone, pagina e tamanho_pagina" } /v1/financeiro/eventos-financeiros/saldo-inicial: get: { summary: "Saldo inicial no período; params data_inicio e data_fim em ISO 8601 SEM timezone" } /v1/protocolo/{id}: diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 2985510..30d3298 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -559,12 +559,14 @@ uma baixa: não existe "estornar".** ## `financeiro alteracoes` ✅ { #financeiro-alteracoes } -`ISO 8601 **sem timezone**` +`GET /v1/financeiro/eventos-financeiros/alteracoes` | Parâmetro | Obrig. | Padrão | Descrição | |---|---|---|---| | `--data-inicio` | não¹ | início do mês corrente | ISO 8601 **sem timezone** | | `--data-fim` | não¹ | fim do mês corrente | ISO 8601 **sem timezone** | +| `--pagina` | não | `1` | Número da página | +| `--tamanho-pagina` | não | `50` | Itens por página | Feed de alterações no período — o caminho para reconciliar escritas que terminaram em `ambiguous`. diff --git a/docs/referencia/financeiro.md b/docs/referencia/financeiro.md index 682a205..e253acb 100644 --- a/docs/referencia/financeiro.md +++ b/docs/referencia/financeiro.md @@ -5,12 +5,14 @@ ## `financeiro alteracoes` ✅ { #financeiro-alteracoes } -`ISO 8601 **sem timezone**` +`GET /v1/financeiro/eventos-financeiros/alteracoes` | Parâmetro | Obrig. | Padrão | Descrição | |---|---|---|---| | `--data-inicio` | não¹ | início do mês corrente | ISO 8601 **sem timezone** | | `--data-fim` | não¹ | fim do mês corrente | ISO 8601 **sem timezone** | +| `--pagina` | não | `1` | Número da página | +| `--tamanho-pagina` | não | `50` | Itens por página | Feed de alterações no período — o caminho para reconciliar escritas que terminaram em `ambiguous`. diff --git a/docs/referencia/index.md b/docs/referencia/index.md index c3f16f0..91b1706 100644 --- a/docs/referencia/index.md +++ b/docs/referencia/index.md @@ -68,7 +68,7 @@ Cada endpoint traz uma marca de confiança: | [`baixa get`](baixa.md#baixa-get) | `GET /v1/financeiro/eventos-financeiros/parcelas/baixa/{id}` | ✅ | | [`baixa update`](baixa.md#baixa-update) | `PATCH /v1/financeiro/eventos-financeiros/parcelas/baixa/{id}` | ✅ | | [`baixa delete`](baixa.md#baixa-delete) | `DELETE /v1/financeiro/eventos-financeiros/parcelas/baixa/{id}` | ✅ | -| [`financeiro alteracoes`](financeiro.md#financeiro-alteracoes) | `ISO 8601 **sem timezone**` | ✅ | +| [`financeiro alteracoes`](financeiro.md#financeiro-alteracoes) | `GET /v1/financeiro/eventos-financeiros/alteracoes` | ✅ | | [`financeiro saldo-inicial`](financeiro.md#financeiro-saldo-inicial) | `GET /v1/financeiro/eventos-financeiros/saldo-inicial` | ✅ | | [`protocolo get`](protocolo.md#protocolo-get) | `GET /v1/protocolo/{id}` | ✅ | | [`contrato list`](contrato.md#contrato-list) | `GET /v1/contratos` | ✅ | diff --git a/src/Api/FinanceiroClient.php b/src/Api/FinanceiroClient.php index 91e0bef..39dbf89 100644 --- a/src/Api/FinanceiroClient.php +++ b/src/Api/FinanceiroClient.php @@ -425,22 +425,24 @@ public function createCentroDeCusto(array $payload): array { * As datas vão em ISO 8601 **sem timezone** (`2026-08-01T00:00:00`). Com * sufixo `Z` ou offset a API responde 400. * - * @param array $filters - * * @return array */ - public function getAlteracoes(string $dataInicio, string $dataFim, array $filters = []): array { + public function getAlteracoes( + string $dataInicio, + string $dataFim, + int $pagina = 1, + int $tamanhoPagina = 50, + ): array { return $this->support->request( 'GET', '/v1/financeiro/eventos-financeiros/alteracoes', [ - 'query' => array_merge( - [ - 'data_fim' => $dataFim, - 'data_inicio' => $dataInicio, - ], - $filters, - ), + 'query' => [ + 'data_fim' => $dataFim, + 'data_inicio' => $dataInicio, + 'pagina' => $pagina, + 'tamanho_pagina' => $tamanhoPagina, + ], ], ); } diff --git a/src/Command/Financeiro/AlteracoesCommand.php b/src/Command/Financeiro/AlteracoesCommand.php index 91fb150..c1757d1 100644 --- a/src/Command/Financeiro/AlteracoesCommand.php +++ b/src/Command/Financeiro/AlteracoesCommand.php @@ -5,7 +5,9 @@ namespace ContaAzulCli\Command\Financeiro; use ContaAzulCli\Api\FinanceiroClient; +use ContaAzulCli\Api\PaginationValidator; use ContaAzulCli\Command\Support\CommandExecutor; +use ContaAzulCli\Command\Support\PaginationOptions; use ContaAzulCli\Command\Support\PeriodoPadrao; use ContaAzulCli\Output\ErrorEnvelope; use ContaAzulCli\Output\ResponseRenderer; @@ -28,6 +30,7 @@ public function __construct( private readonly FinanceiroClient $client, private readonly ErrorEnvelope $errorEnvelope, private readonly ResponseRenderer $responseRenderer, + private readonly PaginationValidator $paginationValidator, private readonly WarningEnvelope $warningEnvelope, private readonly PeriodoPadrao $periodoPadrao, CommandExecutor|null $commandExecutor = null, @@ -49,9 +52,13 @@ public function __invoke( description: 'Fim em ISO 8601 sem timezone (ex: 2026-08-31T23:59:59). Padrão: fim do mês corrente', )] string|null $dataFim = null, + #[Option(description: 'Número da página')] + int $pagina = 1, + #[Option(name: 'tamanho-pagina', description: 'Itens por página')] + int $tamanhoPagina = 50, ): int { return $this->commandExecutor->execute( - function () use ($dataInicio, $dataFim): void { + function () use ($dataInicio, $dataFim, $pagina, $tamanhoPagina): void { $inicio = $dataInicio !== null && $dataInicio !== '' ? $dataInicio : $this->periodoPadrao->primeiroInstante(); $fim = $dataFim !== null && $dataFim !== '' ? $dataFim : $this->periodoPadrao->ultimoInstante(); @@ -62,7 +69,11 @@ function () use ($dataInicio, $dataFim): void { ); } - $this->responseRenderer->render($this->client->getAlteracoes($inicio, $fim)); + $pagination = PaginationOptions::fromValues($pagina, $tamanhoPagina, $this->paginationValidator); + + $this->responseRenderer->render( + $this->client->getAlteracoes($inicio, $fim, $pagination->page(), $pagination->pageSize()), + ); }, ); } diff --git a/src/Command/Module/FinanceiroCommandModule.php b/src/Command/Module/FinanceiroCommandModule.php index ef0b072..34ab9ce 100644 --- a/src/Command/Module/FinanceiroCommandModule.php +++ b/src/Command/Module/FinanceiroCommandModule.php @@ -188,6 +188,7 @@ public function commands(): array { $this->client, $this->errorEnvelope, $this->responseRenderer, + $this->paginationValidator, $this->warningEnvelope, $this->periodoPadrao, ), diff --git a/tests/Integration/Command/Financeiro/AlteracoesCommandTest.php b/tests/Integration/Command/Financeiro/AlteracoesCommandTest.php index b993013..469cd36 100644 --- a/tests/Integration/Command/Financeiro/AlteracoesCommandTest.php +++ b/tests/Integration/Command/Financeiro/AlteracoesCommandTest.php @@ -4,6 +4,7 @@ namespace ContaAzulCli\Tests\Integration\Command\Financeiro; +use ContaAzulCli\Api\PaginationValidator; use ContaAzulCli\Command\Financeiro\AlteracoesCommand; use ContaAzulCli\Command\Support\PeriodoPadrao; use ContaAzulCli\Output\ErrorEnvelope; @@ -13,6 +14,11 @@ use DateTimeImmutable; use Symfony\Component\Console\Command\Command; +use function parse_str; +use function parse_url; + +use const PHP_URL_QUERY; + final class AlteracoesCommandTest extends CommandTestCase { public function testListsChangesWithExplicitIntervalAndNoWarning(): void { @@ -21,6 +27,7 @@ public function testListsChangesWithExplicitIntervalAndNoWarning(): void { $this->financeiroClient([$this->jsonResponse(['itens' => []])]), new ErrorEnvelope($output), new ResponseRenderer($output), + new PaginationValidator(), new WarningEnvelope($output), new PeriodoPadrao(), ); @@ -41,6 +48,7 @@ public function testMissingIntervalFallsBackToCurrentMonthWithAWarning(): void { $this->financeiroClient([$this->jsonResponse(['itens' => []])]), new ErrorEnvelope($output), new ResponseRenderer($output), + new PaginationValidator(), new WarningEnvelope($output), new PeriodoPadrao(new DateTimeImmutable('2026-08-16')), ); @@ -51,6 +59,89 @@ public function testMissingIntervalFallsBackToCurrentMonthWithAWarning(): void { $warning = self::decodeEnvelope($output->stderr()); self::assertSame('warning', $warning['kind']); + self::assertIsString($warning['message']); self::assertStringContainsString('2026-08-01T00:00:00', $warning['message']); } + + public function testForwardsPaginationOptionsAsQueryParameters(): void { + $captured = null; + $output = $this->newOutput(); + $command = new AlteracoesCommand( + $this->financeiroClientRecording($captured), + new ErrorEnvelope($output), + new ResponseRenderer($output), + new PaginationValidator(), + new WarningEnvelope($output), + new PeriodoPadrao(), + ); + + $tester = $this->runCommand( + $command, + [ + '--data-fim' => '2026-08-31T23:59:59', + '--data-inicio' => '2026-08-01T00:00:00', + '--pagina' => '3', + '--tamanho-pagina' => '10', + ], + ); + + self::assertSame(Command::SUCCESS, $tester->getStatusCode()); + self::assertNotNull($captured); + parse_str((string) parse_url($captured['url'], PHP_URL_QUERY), $query); + self::assertSame('2026-08-01T00:00:00', $query['data_inicio'] ?? null); + self::assertSame('2026-08-31T23:59:59', $query['data_fim'] ?? null); + self::assertSame('3', $query['pagina'] ?? null); + self::assertSame('10', $query['tamanho_pagina'] ?? null); + } + + public function testSendsDefaultPaginationWhenTheFlagsAreOmitted(): void { + $captured = null; + $output = $this->newOutput(); + $command = new AlteracoesCommand( + $this->financeiroClientRecording($captured), + new ErrorEnvelope($output), + new ResponseRenderer($output), + new PaginationValidator(), + new WarningEnvelope($output), + new PeriodoPadrao(), + ); + + $this->runCommand( + $command, + ['--data-inicio' => '2026-08-01T00:00:00', '--data-fim' => '2026-08-31T23:59:59'], + ); + + self::assertNotNull($captured); + parse_str((string) parse_url($captured['url'], PHP_URL_QUERY), $query); + self::assertSame('1', $query['pagina'] ?? null); + self::assertSame('50', $query['tamanho_pagina'] ?? null); + } + + public function testUnsupportedPageSizeFailsBeforeCallingTheApi(): void { + $captured = null; + $output = $this->newOutput(); + $command = new AlteracoesCommand( + $this->financeiroClientRecording($captured), + new ErrorEnvelope($output), + new ResponseRenderer($output), + new PaginationValidator(), + new WarningEnvelope($output), + new PeriodoPadrao(), + ); + + $tester = $this->runCommand( + $command, + [ + '--data-fim' => '2026-08-31T23:59:59', + '--data-inicio' => '2026-08-01T00:00:00', + '--tamanho-pagina' => '15', + ], + ); + + self::assertSame(Command::FAILURE, $tester->getStatusCode()); + self::assertNull($captured); + + $envelope = self::decodeEnvelope($output->stderr()); + self::assertSame('client_error', $envelope['kind']); + } } diff --git a/tests/Integration/ContaAzulApplicationTest.php b/tests/Integration/ContaAzulApplicationTest.php index d3faa0a..404abd5 100644 --- a/tests/Integration/ContaAzulApplicationTest.php +++ b/tests/Integration/ContaAzulApplicationTest.php @@ -114,10 +114,12 @@ public function testApplicationCommandsExposeFormatButNotRaw(): void { /** Registering the shared --format option must not crowd out an invokable command's own #[Option] attributes. */ public function testInvokableOptionsSurviveFormatRegistration(): void { $application = new ContaAzulApplication(); - $definition = $application->get('categoria list')->getDefinition(); - self::assertTrue($definition->hasOption('pagina')); - self::assertTrue($definition->hasOption('tamanho-pagina')); + foreach (['categoria list', 'financeiro alteracoes', 'financeiro saldo-inicial'] as $name) { + $definition = $application->get($name)->getDefinition(); + self::assertTrue($definition->hasOption('pagina'), $name); + self::assertTrue($definition->hasOption('tamanho-pagina'), $name); + } } /** The removed JSON alias is rejected on project commands before any API request. */ diff --git a/tests/Unit/Api/FinanceiroClientTest.php b/tests/Unit/Api/FinanceiroClientTest.php index cece252..93ee358 100644 --- a/tests/Unit/Api/FinanceiroClientTest.php +++ b/tests/Unit/Api/FinanceiroClientTest.php @@ -393,9 +393,24 @@ public function testAlteracoesSendsDataInicioAndDataFim(): void { parse_str((string) parse_url($captured['url'], PHP_URL_QUERY), $query); self::assertSame('2026-08-01T00:00:00', $query['data_inicio'] ?? null); self::assertSame('2026-08-31T23:59:59', $query['data_fim'] ?? null); + self::assertSame('1', $query['pagina'] ?? null); + self::assertSame('50', $query['tamanho_pagina'] ?? null); self::assertArrayNotHasKey('desde', $query); } + /** Paginação vai na query, iguais às outras listagens financeiras. */ + public function testAlteracoesSendsPagination(): void { + $captured = null; + $client = $this->clientRecording($captured); + + $client->getAlteracoes('2026-08-01T00:00:00', '2026-08-31T23:59:59', 2, 25); + + self::assertNotNull($captured); + parse_str((string) parse_url($captured['url'], PHP_URL_QUERY), $query); + self::assertSame('2', $query['pagina'] ?? null); + self::assertSame('25', $query['tamanho_pagina'] ?? null); + } + /** Assim como `getAlteracoes`, o instante ISO 8601 vai sem timezone. */ public function testListSaldoInicialSendsDataInicioDataFimAndPagination(): void { $captured = null;