diff --git a/CHANGELOG.md b/CHANGELOG.md index ba72fd2..fd4068e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.3.1] - 2026-07-01 + +### Added + +- Add draft support methods for retentions: `retentions.updateDraft(String id, Map data)`, `retentions.copyToDraft(String id)`, `retentions.stampDraft(String id)`, and `retentions.cancel(String id)`. + ## [1.3.0] - 2026-06-07 ### Added diff --git a/README.es.md b/README.es.md index 0e1dc20..8c9d9a0 100644 --- a/README.es.md +++ b/README.es.md @@ -29,14 +29,14 @@ Maven: io.facturapi facturapi-java - 1.1.0 + 1.3.1 ``` Gradle: ```gradle -implementation("io.facturapi:facturapi-java:1.1.0") +implementation("io.facturapi:facturapi-java:1.3.1") ``` ## Inicio rĂ¡pido diff --git a/README.md b/README.md index fe0f67b..1ac1eea 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,14 @@ Maven: io.facturapi facturapi-java - 1.1.0 + 1.3.1 ``` Gradle: ```gradle -implementation("io.facturapi:facturapi-java:1.1.0") +implementation("io.facturapi:facturapi-java:1.3.1") ``` ## Quickstart diff --git a/pom.xml b/pom.xml index 4235f4a..fd402a8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.facturapi facturapi-java - 1.3.0 + 1.3.1 facturapi-java Official Java SDK for Facturapi https://github.com/facturapi/facturapi-java diff --git a/src/main/java/io/facturapi/resources/RetentionsResource.java b/src/main/java/io/facturapi/resources/RetentionsResource.java index 4fd50fc..5eafbd7 100644 --- a/src/main/java/io/facturapi/resources/RetentionsResource.java +++ b/src/main/java/io/facturapi/resources/RetentionsResource.java @@ -19,7 +19,7 @@ public RetentionsResource(FacturapiHttpClient client) { } /** - * Creates a new valid retention (CFDI). + * Creates a new retention (CFDI). * * @param data Retention payload. * @return Created retention. @@ -63,6 +63,51 @@ public Retention cancel(String id, Map params) { return delete("/retentions/" + id, params, Retention.class); } + /** + * Cancels a retention, or deletes it directly when it is a draft. + * + * @param id Retention id. + * @return Canceled or deleted retention. + * @see API reference + */ + public Retention cancel(String id) { + return delete("/retentions/" + id, null, Retention.class); + } + + /** + * Updates a draft retention. + * + * @param id Retention id. + * @param data Retention updates. + * @return Updated draft retention. + * @see API reference + */ + public Retention updateDraft(String id, Map data) { + return put("/retentions/" + id, data, null, Retention.class); + } + + /** + * Creates a draft copy from an existing retention. + * + * @param id Retention id. + * @return Draft retention. + * @see API reference + */ + public Retention copyToDraft(String id) { + return post("/retentions/" + id + "/copy", null, null, Retention.class); + } + + /** + * Stamps an existing draft retention. + * + * @param id Retention id. + * @return Stamped retention. + * @see API reference + */ + public Retention stampDraft(String id) { + return post("/retentions/" + id + "/stamp", null, null, Retention.class); + } + /** * Sends the retention to the customer's email. * diff --git a/src/test/java/io/facturapi/FacturapiResourcesTest.java b/src/test/java/io/facturapi/FacturapiResourcesTest.java index b523184..30fdeab 100644 --- a/src/test/java/io/facturapi/FacturapiResourcesTest.java +++ b/src/test/java/io/facturapi/FacturapiResourcesTest.java @@ -112,6 +112,100 @@ void invoicePdfCanBeStreamed() throws Exception { assertEquals("/v2/invoices/inv_1/pdf", request.uri().getPath()); } + @Test + void retentionDraftCreateSupportsDraftStatus() { + StubHttpClient httpClient = new StubHttpClient(); + httpClient.enqueueJson(200, "{\"id\":\"ret_draft_1\",\"status\":\"draft\"}"); + + Facturapi sdk = new Facturapi( + FacturapiConfig.builder("sk_test") + .httpClient(httpClient.client()) + .build() + ); + + Map payload = new java.util.HashMap<>(); + payload.put("status", "draft"); + payload.put("customer", null); + + var response = sdk.retentions().create(payload); + + assertEquals("ret_draft_1", response.getId()); + assertEquals("draft", response.getStatus()); + var request = httpClient.requests().get(0); + assertEquals("POST", request.method()); + assertEquals("/v2/retentions", request.uri().getPath()); + assertTrue(request.bodyUtf8().contains("\"status\":\"draft\"")); + assertTrue(request.bodyUtf8().contains("\"customer\":null")); + } + + @Test + void retentionDraftUpdateUsesExpectedPath() { + StubHttpClient httpClient = new StubHttpClient(); + httpClient.enqueueJson(200, "{\"id\":\"ret_1\",\"folio_int\":\"R-2026-001\"}"); + + Facturapi sdk = new Facturapi( + FacturapiConfig.builder("sk_test") + .httpClient(httpClient.client()) + .build() + ); + + var response = sdk.retentions().updateDraft("ret_1", Map.of("folio_int", "R-2026-001")); + + assertEquals("ret_1", response.getId()); + assertEquals("R-2026-001", response.getFolioInt()); + var request = httpClient.requests().get(0); + assertEquals("PUT", request.method()); + assertEquals("/v2/retentions/ret_1", request.uri().getPath()); + assertTrue(request.bodyUtf8().contains("\"folio_int\":\"R-2026-001\"")); + } + + @Test + void retentionDraftCopyAndStampUseExpectedPaths() { + StubHttpClient httpClient = new StubHttpClient(); + httpClient.enqueueJson(200, "{\"id\":\"ret_copy_1\",\"status\":\"draft\"}"); + httpClient.enqueueJson(200, "{\"id\":\"ret_1\",\"status\":\"valid\"}"); + + Facturapi sdk = new Facturapi( + FacturapiConfig.builder("sk_test") + .httpClient(httpClient.client()) + .build() + ); + + var draft = sdk.retentions().copyToDraft("ret_1"); + var stamped = sdk.retentions().stampDraft("ret_copy_1"); + + assertEquals("ret_copy_1", draft.getId()); + assertEquals("ret_1", stamped.getId()); + assertEquals("POST", httpClient.requests().get(0).method()); + assertEquals("/v2/retentions/ret_1/copy", httpClient.requests().get(0).uri().getPath()); + assertEquals("POST", httpClient.requests().get(1).method()); + assertEquals("/v2/retentions/ret_copy_1/stamp", httpClient.requests().get(1).uri().getPath()); + } + + @Test + void retentionDraftCancelAndListUseExpectedPaths() { + StubHttpClient httpClient = new StubHttpClient(); + httpClient.enqueueJson(200, "{\"id\":\"ret_draft_1\",\"status\":\"canceled\"}"); + httpClient.enqueueJson(200, "{\"data\":[{\"id\":\"ret_draft_2\",\"status\":\"draft\"}]}"); + + Facturapi sdk = new Facturapi( + FacturapiConfig.builder("sk_test") + .httpClient(httpClient.client()) + .build() + ); + + var deleted = sdk.retentions().cancel("ret_draft_1"); + var drafts = sdk.retentions().list(Map.of("status", "draft")); + + assertEquals("ret_draft_1", deleted.getId()); + assertEquals(1, drafts.getData().size()); + assertEquals("DELETE", httpClient.requests().get(0).method()); + assertEquals("/v2/retentions/ret_draft_1", httpClient.requests().get(0).uri().getPath()); + var listRequest = httpClient.requests().get(1); + assertEquals("GET", listRequest.method()); + assertEquals("/v2/retentions?status=draft", listRequest.uri().getPath() + "?" + listRequest.uri().getQuery()); + } + @Test void organizationUploadsAcceptBytes() { StubHttpClient httpClient = new StubHttpClient();