diff --git a/VERSION.md b/VERSION.md index ff3a77f..d4a4663 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1,3 +1,9 @@ +4.6.0 + +## Added +- Add draft support methods for retentions: `updateDraft()`, `copyToDraft()`, and `stampDraft()`. +- Allow `Retentions::cancel()` to be called without query parameters for deleting draft retentions. + 4.5.0 ## Added diff --git a/retention.pdf b/retention.pdf new file mode 100644 index 0000000..17bd6da Binary files /dev/null and b/retention.pdf differ diff --git a/src/Resources/Retentions.php b/src/Resources/Retentions.php index 63f7d47..10f9a18 100644 --- a/src/Resources/Retentions.php +++ b/src/Resources/Retentions.php @@ -66,12 +66,12 @@ public function create($params): mixed * Cancels a Retention * * @param string $id Retention ID. - * @param array $query URL query parameters. + * @param array|null $query URL query parameters. * @return mixed JSON-decoded response. * * @throws FacturapiException */ - public function cancel($id, $query): mixed + public function cancel($id, $query = null): mixed { try { return json_decode($this->executeDeleteRequest($this->getRequestUrl($id, $query), null)); @@ -80,6 +80,85 @@ public function cancel($id, $query): mixed } } + /** + * Updates a Retention with "draft" status + * + * @param string $id Retention ID. + * @param array $body Draft payload. + * @return mixed JSON-decoded response. + * + * @throws FacturapiException + */ + public function updateDraft($id, $body): mixed + { + try { + return json_decode($this->executeJsonPutRequest($this->getRequestUrl($id), $body)); + } catch (FacturapiException $e) { + throw $e; + } + } + + /** + * @deprecated Use updateDraft() instead. Will be removed in v5. + */ + public function update_draft($id, $body): mixed + { + trigger_error('Retentions::update_draft() is deprecated and will be removed in v5. Use updateDraft() instead.', E_USER_DEPRECATED); + return $this->updateDraft($id, $body); + } + + /** + * Creates a new draft Retention copying the information from the specified Retention + * + * @param string $id Retention ID. + * @return mixed JSON-decoded response. + * + * @throws FacturapiException + */ + public function copyToDraft($id): mixed + { + try { + return json_decode($this->executeJsonPostRequest($this->getRequestUrl($id . "/copy"), null)); + } catch (FacturapiException $e) { + throw $e; + } + } + + /** + * @deprecated Use copyToDraft() instead. Will be removed in v5. + */ + public function copy_to_draft($id): mixed + { + trigger_error('Retentions::copy_to_draft() is deprecated and will be removed in v5. Use copyToDraft() instead.', E_USER_DEPRECATED); + return $this->copyToDraft($id); + } + + /** + * Stamps a draft Retention + * + * @param string $id Retention ID. + * @return mixed JSON-decoded response. + * + * @throws FacturapiException + */ + public function stampDraft($id): mixed + { + try { + return json_decode($this->executeJsonPostRequest($this->getRequestUrl($id . "/stamp"), null)); + } catch (FacturapiException $e) { + throw $e; + } + } + + /** + * @deprecated Use stampDraft() instead. Will be removed in v5. + */ + public function stamp_draft($id): mixed + { + trigger_error('Retentions::stamp_draft() is deprecated and will be removed in v5. Use stampDraft() instead.', E_USER_DEPRECATED); + return $this->stampDraft($id); + } + /** * Sends the retention to the customer's email * diff --git a/tests/Resources/RetentionsTest.php b/tests/Resources/RetentionsTest.php new file mode 100644 index 0000000..ae1d6bf --- /dev/null +++ b/tests/Resources/RetentionsTest.php @@ -0,0 +1,102 @@ + $httpClient]); + + $retentions->create([ + 'status' => 'draft', + 'customer' => null, + ]); + + $request = $httpClient->requests()[0]; + self::assertSame('POST', $request->getMethod()); + self::assertSame('https://www.facturapi.io/v2/retentions', (string) $request->getUri()); + self::assertJsonStringEqualsJsonString( + '{"status":"draft","customer":null}', + (string) $request->getBody() + ); + } + + public function testUpdateDraftUsesPutOnRetentionId(): void + { + $httpClient = new FakeHttpClient(new Response(200, [], '{"id":"ret_123"}')); + $retentions = new Retentions('sk_test_abc123', ['httpClient' => $httpClient]); + + $retentions->updateDraft('ret_123', [ + 'folio_int' => 'R-2026-001', + ]); + + $request = $httpClient->requests()[0]; + self::assertSame('PUT', $request->getMethod()); + self::assertSame('https://www.facturapi.io/v2/retentions/ret_123', (string) $request->getUri()); + self::assertJsonStringEqualsJsonString( + '{"folio_int":"R-2026-001"}', + (string) $request->getBody() + ); + } + + public function testCopyToDraftUsesCopyEndpoint(): void + { + $httpClient = new FakeHttpClient(new Response(200, [], '{"id":"ret_draft"}')); + $retentions = new Retentions('sk_test_abc123', ['httpClient' => $httpClient]); + + $retentions->copyToDraft('ret_123'); + + $request = $httpClient->requests()[0]; + self::assertSame('POST', $request->getMethod()); + self::assertSame('https://www.facturapi.io/v2/retentions/ret_123/copy', (string) $request->getUri()); + self::assertSame('', (string) $request->getBody()); + } + + public function testStampDraftUsesStampEndpoint(): void + { + $httpClient = new FakeHttpClient(new Response(200, [], '{"id":"ret_123","status":"valid"}')); + $retentions = new Retentions('sk_test_abc123', ['httpClient' => $httpClient]); + + $retentions->stampDraft('ret_123'); + + $request = $httpClient->requests()[0]; + self::assertSame('POST', $request->getMethod()); + self::assertSame('https://www.facturapi.io/v2/retentions/ret_123/stamp', (string) $request->getUri()); + self::assertSame('', (string) $request->getBody()); + } + + public function testCancelCanDeleteDraftWithoutQueryParameters(): void + { + $httpClient = new FakeHttpClient(new Response(200, [], '{"id":"ret_123","deleted":true}')); + $retentions = new Retentions('sk_test_abc123', ['httpClient' => $httpClient]); + + $retentions->cancel('ret_123'); + + $request = $httpClient->requests()[0]; + self::assertSame('DELETE', $request->getMethod()); + self::assertSame('https://www.facturapi.io/v2/retentions/ret_123', (string) $request->getUri()); + } + + public function testAllCanFilterByDraftStatus(): void + { + $httpClient = new FakeHttpClient(new Response(200, [], '{"data":[]}')); + $retentions = new Retentions('sk_test_abc123', ['httpClient' => $httpClient]); + + $retentions->all([ + 'status' => 'draft', + ]); + + $request = $httpClient->requests()[0]; + self::assertSame('GET', $request->getMethod()); + self::assertSame('https://www.facturapi.io/v2/retentions?status=draft', (string) $request->getUri()); + } +}