From c08e5d4849180a30d8de356ff91df3a8afc99e01 Mon Sep 17 00:00:00 2001 From: Carlos Esteban Feria Vila <2582866+carlosthe19916@users.noreply.github.com> Date: Tue, 12 Oct 2021 07:35:26 +0200 Subject: [PATCH 1/8] Add changes --- .../xsender/idgenerator/IDGenerator.java | 12 +- .../xsender/idgenerator/IDGeneratorType.java | 3 +- .../generators/GeneratedIDGenerator.java | 146 ++++++++++++++ .../generators/NoneIDGenerator.java | 18 +- .../models/jpa/GeneratedIDRepository.java | 40 ++++ .../jpa/entities/GeneratedIDEntity.java | 60 ++++++ .../xsender/resources/DocumentResource.java | 35 ++-- .../db/migration/V1.0.0__initial_schema.sql | 20 ++ .../resources/DocumentResourceTest.java | 186 ++++++++++++++++++ 9 files changed, 490 insertions(+), 30 deletions(-) create mode 100644 src/main/java/io/github/project/openubl/xsender/idgenerator/generators/GeneratedIDGenerator.java create mode 100644 src/main/java/io/github/project/openubl/xsender/models/jpa/GeneratedIDRepository.java create mode 100644 src/main/java/io/github/project/openubl/xsender/models/jpa/entities/GeneratedIDEntity.java diff --git a/src/main/java/io/github/project/openubl/xsender/idgenerator/IDGenerator.java b/src/main/java/io/github/project/openubl/xsender/idgenerator/IDGenerator.java index 5f80828f..e8a8b1e6 100644 --- a/src/main/java/io/github/project/openubl/xsender/idgenerator/IDGenerator.java +++ b/src/main/java/io/github/project/openubl/xsender/idgenerator/IDGenerator.java @@ -21,19 +21,21 @@ import io.github.project.openubl.xmlbuilderlib.models.input.standard.note.debitNote.DebitNoteInputModel; import io.github.project.openubl.xmlbuilderlib.models.input.sunat.SummaryDocumentInputModel; import io.github.project.openubl.xmlbuilderlib.models.input.sunat.VoidedDocumentInputModel; +import io.github.project.openubl.xsender.models.jpa.entities.NamespaceEntity; +import io.smallrye.mutiny.Uni; import java.util.Map; public interface IDGenerator { - void enrichWithID(InvoiceInputModel invoice, Map config); + Uni enrichWithID(NamespaceEntity namespace, InvoiceInputModel invoice, Map config); - void enrichWithID(CreditNoteInputModel creditNote, Map config); + Uni enrichWithID(NamespaceEntity namespace, CreditNoteInputModel creditNote, Map config); - void enrichWithID(DebitNoteInputModel debitNote, Map config); + Uni enrichWithID(NamespaceEntity namespace, DebitNoteInputModel debitNote, Map config); - void enrichWithID(VoidedDocumentInputModel voidedDocument, Map config); + Uni enrichWithID(NamespaceEntity namespace, VoidedDocumentInputModel voidedDocument, Map config); - void enrichWithID(SummaryDocumentInputModel summaryDocument, Map config); + Uni enrichWithID(NamespaceEntity namespace, SummaryDocumentInputModel summaryDocument, Map config); } diff --git a/src/main/java/io/github/project/openubl/xsender/idgenerator/IDGeneratorType.java b/src/main/java/io/github/project/openubl/xsender/idgenerator/IDGeneratorType.java index 8973822f..518ee08f 100644 --- a/src/main/java/io/github/project/openubl/xsender/idgenerator/IDGeneratorType.java +++ b/src/main/java/io/github/project/openubl/xsender/idgenerator/IDGeneratorType.java @@ -17,5 +17,6 @@ package io.github.project.openubl.xsender.idgenerator; public enum IDGeneratorType { - none + none, + generated, } diff --git a/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/GeneratedIDGenerator.java b/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/GeneratedIDGenerator.java new file mode 100644 index 00000000..b01bc44a --- /dev/null +++ b/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/GeneratedIDGenerator.java @@ -0,0 +1,146 @@ +/* + * Copyright 2019 Project OpenUBL, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Eclipse Public License - v 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.project.openubl.xsender.idgenerator.generators; + +import io.github.project.openubl.xmlbuilderlib.models.input.standard.invoice.InvoiceInputModel; +import io.github.project.openubl.xmlbuilderlib.models.input.standard.note.creditNote.CreditNoteInputModel; +import io.github.project.openubl.xmlbuilderlib.models.input.standard.note.debitNote.DebitNoteInputModel; +import io.github.project.openubl.xmlbuilderlib.models.input.sunat.SummaryDocumentInputModel; +import io.github.project.openubl.xmlbuilderlib.models.input.sunat.VoidedDocumentInputModel; +import io.github.project.openubl.xsender.idgenerator.IDGenerator; +import io.github.project.openubl.xsender.idgenerator.IDGeneratorProvider; +import io.github.project.openubl.xsender.idgenerator.IDGeneratorType; +import io.github.project.openubl.xsender.models.jpa.GeneratedIDRepository; +import io.github.project.openubl.xsender.models.jpa.entities.GeneratedIDEntity; +import io.github.project.openubl.xsender.models.jpa.entities.NamespaceEntity; +import io.smallrye.mutiny.Uni; +import org.apache.commons.lang3.StringUtils; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +@ApplicationScoped +@IDGeneratorProvider(IDGeneratorType.generated) +public class GeneratedIDGenerator implements IDGenerator { + + public static final String SERIE = "serie"; + public static final String NUMERO = "numero"; + + @Inject + GeneratedIDRepository generatedIDRepository; + + private Uni generateNextID(NamespaceEntity namespace, String ruc, String documentType, Map config) { + Map generatorConfig = Objects.requireNonNullElseGet(config, HashMap::new); + + return generatedIDRepository.getCurrentID(namespace, ruc, documentType) + .onItem().ifNull().continueWith(() -> generateFirstEntity(namespace, ruc, documentType, generatorConfig)) + .chain(generatedIDEntity -> { + if (generatedIDEntity.numero > 99_999_999) { + generatedIDEntity.serie++; + generatedIDEntity.numero = 1; + } else { + generatedIDEntity.numero++; + } + + generatedIDEntity.serie = Integer.parseInt(generatorConfig.getOrDefault(SERIE, String.valueOf(generatedIDEntity.serie))); + generatedIDEntity.numero = Integer.parseInt(generatorConfig.getOrDefault(NUMERO, String.valueOf(generatedIDEntity.numero))); + + return generatedIDEntity.persist(); + }); + } + + private GeneratedIDEntity generateFirstEntity(NamespaceEntity namespace, String ruc, String documentType, Map config) { + GeneratedIDEntity entity = new GeneratedIDEntity(); + + entity.id = UUID.randomUUID().toString(); + entity.namespace = namespace; + entity.ruc = ruc; + entity.documentType = documentType; + entity.serie = 1; + entity.numero = 0; + + return entity; + } + + @Override + public Uni enrichWithID(NamespaceEntity namespace, InvoiceInputModel invoice, Map config) { + return generateNextID(namespace, invoice.getProveedor().getRuc(), "Invoice", config) + .map(generatedIDEntity -> { + invoice.setSerie("F" + StringUtils.leftPad(String.valueOf(generatedIDEntity.serie), 3, "0")); + invoice.setNumero(generatedIDEntity.numero); + + return invoice; + }); + } + + @Override + public Uni enrichWithID(NamespaceEntity namespace, CreditNoteInputModel creditNote, Map config) { + String documentType; + String idPrefix; + if (creditNote.getSerieNumeroComprobanteAfectado().toUpperCase().startsWith("F")) { + documentType = "CreditNote_Factura"; + idPrefix = "FC"; + } else { + documentType = "CreditNote_Boleta"; + idPrefix = "BC"; + } + + return generateNextID(namespace, creditNote.getProveedor().getRuc(), documentType, config) + .map(generatedIDEntity -> { + creditNote.setSerie(idPrefix + StringUtils.leftPad(String.valueOf(generatedIDEntity.serie), 2)); + creditNote.setNumero(generatedIDEntity.numero); + + return creditNote; + }); + } + + @Override + public Uni enrichWithID(NamespaceEntity namespace, DebitNoteInputModel debitNote, Map config) { + String documentType; + String idPrefix; + if (debitNote.getSerieNumeroComprobanteAfectado().toUpperCase().startsWith("F")) { + documentType = "DebitNote_Factura"; + idPrefix = "FD"; + } else { + documentType = "DebitNote_Boleta"; + idPrefix = "BD"; + } + + return generateNextID(namespace, debitNote.getProveedor().getRuc(), documentType, config) + .map(generatedIDEntity -> { + debitNote.setSerie(idPrefix + StringUtils.leftPad(String.valueOf(generatedIDEntity.serie), 2)); + debitNote.setNumero(generatedIDEntity.numero); + + return debitNote; + }); + } + + + @Override + public Uni enrichWithID(NamespaceEntity namespace, VoidedDocumentInputModel voidedDocument, Map config) { + return Uni.createFrom().item(voidedDocument); + } + + @Override + public Uni enrichWithID(NamespaceEntity namespace, SummaryDocumentInputModel summaryDocument, Map config) { + return Uni.createFrom().item(summaryDocument); + } +} diff --git a/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/NoneIDGenerator.java b/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/NoneIDGenerator.java index 945cb7d3..f956b26d 100644 --- a/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/NoneIDGenerator.java +++ b/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/NoneIDGenerator.java @@ -24,6 +24,8 @@ import io.github.project.openubl.xsender.idgenerator.IDGenerator; import io.github.project.openubl.xsender.idgenerator.IDGeneratorProvider; import io.github.project.openubl.xsender.idgenerator.IDGeneratorType; +import io.github.project.openubl.xsender.models.jpa.entities.NamespaceEntity; +import io.smallrye.mutiny.Uni; import javax.enterprise.context.ApplicationScoped; import java.util.Map; @@ -32,29 +34,33 @@ @IDGeneratorProvider(IDGeneratorType.none) public class NoneIDGenerator implements IDGenerator { - @Override - public void enrichWithID(InvoiceInputModel invoice, Map config) { + public Uni enrichWithID(NamespaceEntity namespace, InvoiceInputModel invoice, Map config) { // Nothing to do + return Uni.createFrom().item(invoice); } @Override - public void enrichWithID(CreditNoteInputModel creditNote, Map config) { + public Uni enrichWithID(NamespaceEntity namespace, CreditNoteInputModel creditNote, Map config) { // Nothing to do + return Uni.createFrom().item(creditNote); } @Override - public void enrichWithID(DebitNoteInputModel debitNote, Map config) { + public Uni enrichWithID(NamespaceEntity namespace, DebitNoteInputModel debitNote, Map config) { // Nothing to do + return Uni.createFrom().item(debitNote); } @Override - public void enrichWithID(VoidedDocumentInputModel voidedDocument, Map config) { + public Uni enrichWithID(NamespaceEntity namespace, VoidedDocumentInputModel voidedDocument, Map config) { // Nothing to do + return Uni.createFrom().item(voidedDocument); } @Override - public void enrichWithID(SummaryDocumentInputModel summaryDocument, Map config) { + public Uni enrichWithID(NamespaceEntity namespace, SummaryDocumentInputModel summaryDocument, Map config) { // Nothing to do + return Uni.createFrom().item(summaryDocument); } } diff --git a/src/main/java/io/github/project/openubl/xsender/models/jpa/GeneratedIDRepository.java b/src/main/java/io/github/project/openubl/xsender/models/jpa/GeneratedIDRepository.java new file mode 100644 index 00000000..7521eb00 --- /dev/null +++ b/src/main/java/io/github/project/openubl/xsender/models/jpa/GeneratedIDRepository.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Project OpenUBL, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Eclipse Public License - v 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.project.openubl.xsender.models.jpa; + +import io.github.project.openubl.xsender.models.jpa.entities.GeneratedIDEntity; +import io.github.project.openubl.xsender.models.jpa.entities.NamespaceEntity; +import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase; +import io.quarkus.panache.common.Parameters; +import io.smallrye.mutiny.Uni; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class GeneratedIDRepository implements PanacheRepositoryBase { + + public Uni getCurrentID(NamespaceEntity namespace, String ruc, String documentType) { + return + find("namespace.id = :namespaceId and ruc = :ruc and documentType = :documentType", Parameters + .with("namespaceId", namespace.id) + .and("ruc", ruc) + .and("documentType", documentType) + .map() + ).firstResult(); + } + +} diff --git a/src/main/java/io/github/project/openubl/xsender/models/jpa/entities/GeneratedIDEntity.java b/src/main/java/io/github/project/openubl/xsender/models/jpa/entities/GeneratedIDEntity.java new file mode 100644 index 00000000..757ea3b1 --- /dev/null +++ b/src/main/java/io/github/project/openubl/xsender/models/jpa/entities/GeneratedIDEntity.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 Project OpenUBL, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Eclipse Public License - v 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.project.openubl.xsender.models.jpa.entities; + +import io.quarkus.hibernate.reactive.panache.PanacheEntityBase; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; + +@Entity +@Table(name = "generated_id", uniqueConstraints = { + @UniqueConstraint(columnNames = {"namespace_id", "ruc", "document_type"}) +}) +public class GeneratedIDEntity extends PanacheEntityBase { + + @Id + @Column(name = "id") + @Access(AccessType.PROPERTY) + public String id; + + @NotNull + @Column(name = "ruc") + public String ruc; + + @NotNull + @Column(name = "document_type") + public String documentType; + + @NotNull + @Column(name = "serie") + public int serie; + + @NotNull + @Column(name = "numero") + public int numero; + + @NotNull + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(foreignKey = @ForeignKey, name = "namespace_id") + public NamespaceEntity namespace; + + @Version + @Column(name = "version") + public int version; + +} diff --git a/src/main/java/io/github/project/openubl/xsender/resources/DocumentResource.java b/src/main/java/io/github/project/openubl/xsender/resources/DocumentResource.java index 92ec93f8..a327a264 100644 --- a/src/main/java/io/github/project/openubl/xsender/resources/DocumentResource.java +++ b/src/main/java/io/github/project/openubl/xsender/resources/DocumentResource.java @@ -108,7 +108,7 @@ public class DocumentResource { @Inject KeyManager keystore; - public String createXMLString(InputTemplateRepresentation inputTemplate) { + public Uni createXMLString(NamespaceEntity namespace, InputTemplateRepresentation inputTemplate) { KindRepresentation kind = inputTemplate.getKind(); SpecRepresentation spec = inputTemplate.getSpec(); @@ -116,29 +116,29 @@ public String createXMLString(InputTemplateRepresentation inputTemplate) { switch (kind) { case Invoice: InvoiceInputModel invoice = inputTemplate.getSpec().getDocument().mapTo(InvoiceInputModel.class); - idGenerator.enrichWithID(invoice, inputTemplate.getSpec().getIdGenerator().getConfig()); - - return DocumentManager.createXML(invoice, xBuilderConfig, xBuilderClock).getXml(); + return idGenerator + .enrichWithID(namespace, invoice, inputTemplate.getSpec().getIdGenerator().getConfig()) + .map(input -> DocumentManager.createXML(input, xBuilderConfig, xBuilderClock).getXml()); case CreditNote: CreditNoteInputModel creditNote = inputTemplate.getSpec().getDocument().mapTo(CreditNoteInputModel.class); - idGenerator.enrichWithID(creditNote, inputTemplate.getSpec().getIdGenerator().getConfig()); - - return DocumentManager.createXML(creditNote, xBuilderConfig, xBuilderClock).getXml(); + return idGenerator + .enrichWithID(namespace, creditNote, inputTemplate.getSpec().getIdGenerator().getConfig()) + .map(input -> DocumentManager.createXML(input, xBuilderConfig, xBuilderClock).getXml()); case DebitNote: DebitNoteInputModel debitNote = inputTemplate.getSpec().getDocument().mapTo(DebitNoteInputModel.class); - idGenerator.enrichWithID(debitNote, inputTemplate.getSpec().getIdGenerator().getConfig()); - - return DocumentManager.createXML(debitNote, xBuilderConfig, xBuilderClock).getXml(); + return idGenerator + .enrichWithID(namespace, debitNote, inputTemplate.getSpec().getIdGenerator().getConfig()) + .map(input -> DocumentManager.createXML(input, xBuilderConfig, xBuilderClock).getXml()); case VoidedDocument: VoidedDocumentInputModel voidedDocument = inputTemplate.getSpec().getDocument().mapTo(VoidedDocumentInputModel.class); - idGenerator.enrichWithID(voidedDocument, inputTemplate.getSpec().getIdGenerator().getConfig()); - - return DocumentManager.createXML(voidedDocument, xBuilderConfig, xBuilderClock).getXml(); + return idGenerator + .enrichWithID(namespace, voidedDocument, inputTemplate.getSpec().getIdGenerator().getConfig()) + .map(input -> DocumentManager.createXML(input, xBuilderConfig, xBuilderClock).getXml()); case SummaryDocument: SummaryDocumentInputModel summaryDocument = inputTemplate.getSpec().getDocument().mapTo(SummaryDocumentInputModel.class); - idGenerator.enrichWithID(summaryDocument, inputTemplate.getSpec().getIdGenerator().getConfig()); - - return DocumentManager.createXML(summaryDocument, xBuilderConfig, xBuilderClock).getXml(); + return idGenerator + .enrichWithID(namespace, summaryDocument, inputTemplate.getSpec().getIdGenerator().getConfig()) + .map(input -> DocumentManager.createXML(input, xBuilderConfig, xBuilderClock).getXml()); default: throw new IllegalStateException("Kind:" + kind + " not supported"); } @@ -179,8 +179,7 @@ public Uni createDocument( ) { return Panache .withTransaction(() -> namespaceRepository.findByIdAndOwner(namespaceId, userIdentity.getUsername()) - .onItem().ifNotNull().transformToUni(namespaceEntity -> Uni.createFrom() - .item(createXMLString(inputTemplate)) + .onItem().ifNotNull().transformToUni(namespaceEntity -> createXMLString(namespaceEntity, inputTemplate) .chain(xmlString -> keystore .getActiveKey(namespaceEntity, KeyUse.SIG, inputTemplate.getSpec().getSignature() != null ? inputTemplate.getSpec().getSignature().getAlgorithm() : Algorithm.RS256) .map(key -> { diff --git a/src/main/resources/db/migration/V1.0.0__initial_schema.sql b/src/main/resources/db/migration/V1.0.0__initial_schema.sql index bef75300..e5bfebf9 100644 --- a/src/main/resources/db/migration/V1.0.0__initial_schema.sql +++ b/src/main/resources/db/migration/V1.0.0__initial_schema.sql @@ -71,6 +71,18 @@ create table component_config primary key (id) ); +gicreate table generated_id +( + id varchar(255) not null, + ruc varchar(255) not null, + document_type varchar(255) not null, + serie int4 not null, + numero int4 not null, + version int4, + namespace_id varchar(255) not null, + primary key (id) +); + create table ubl_document_sunat_notes ( ubl_document_id varchar(255) not null, @@ -124,3 +136,11 @@ alter table if exists ubl_document_sunat_notes references ubl_document; +alter table if exists generated_id + add constraint FKuln89tn2t5teiufir1dsq1ka + foreign key (namespace_id) + references namespace; + +alter table if exists generated_id + add constraint UK4hs3cb8j320vu5apl2fb06dde + unique (namespace_id, ruc, document_type); diff --git a/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java b/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java index 70f13a8e..f26e40c2 100644 --- a/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java +++ b/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java @@ -24,14 +24,17 @@ import io.github.project.openubl.xsender.BaseAuthTest; import io.github.project.openubl.xsender.ProfileManager; import io.github.project.openubl.xsender.idgenerator.IDGeneratorType; +import io.github.project.openubl.xsender.idgenerator.generators.GeneratedIDGenerator; import io.github.project.openubl.xsender.idm.DocumentRepresentation; import io.github.project.openubl.xsender.idm.input.*; import io.github.project.openubl.xsender.models.ErrorType; +import io.github.project.openubl.xsender.idm.input.KindRepresentation; import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; import io.restassured.http.ContentType; import io.vertx.core.json.JsonObject; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.keycloak.crypto.Algorithm; @@ -40,6 +43,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; +import java.util.HashMap; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; @@ -416,6 +420,188 @@ public void createXMLWithCustomSignAlgorithm() throws URISyntaxException { ); } + @Test + public void createXMLWithAutoIDGenerator() throws URISyntaxException { + // Given + String nsId = "1"; + + InvoiceInputModel input = InvoiceInputModel.Builder.anInvoiceInputModel() + .withSerie("F001") + .withNumero(1) + .withProveedor(ProveedorInputModel.Builder.aProveedorInputModel() + .withRuc("12345678912") + .withRazonSocial("Softgreen S.A.C.") + .build() + ) + .withCliente(ClienteInputModel.Builder.aClienteInputModel() + .withNombre("Carlos Feria") + .withNumeroDocumentoIdentidad("12121212121") + .withTipoDocumentoIdentidad(Catalog6.RUC.toString()) + .build() + ) + .withDetalle(Arrays.asList( + DocumentLineInputModel.Builder.aDocumentLineInputModel() + .withDescripcion("Item1") + .withCantidad(new BigDecimal(10)) + .withPrecioUnitario(new BigDecimal(100)) + .withUnidadMedida("KGM") + .build(), + DocumentLineInputModel.Builder.aDocumentLineInputModel() + .withDescripcion("Item2") + .withCantidad(new BigDecimal(10)) + .withPrecioUnitario(new BigDecimal(100)) + .withUnidadMedida("KGM") + .build()) + ) + .build(); + + InputTemplateRepresentation template = InputTemplateRepresentation.Builder.anInputTemplateRepresentation() + .withKind(KindRepresentation.Invoice) + .withSpec(SpecRepresentation.Builder.aSpecRepresentation() + .withIdGenerator(IDGeneratorRepresentation.Builder.anIDGeneratorRepresentation() + .withName(IDGeneratorType.generated) + .build() + ) + .withDocument(JsonObject.mapFrom(input)) + .build() + ) + .build(); + + // When + DocumentRepresentation response = givenAuth("alice") + .contentType(ContentType.JSON) + .body(template) + .when() + .post("/" + nsId + "/documents") + .then() + .statusCode(201) + .body("id", is(notNullValue()), + "namespaceId", is("1"), + "inProgress", is(true) + ) + .extract().body().as(DocumentRepresentation.class); + + // Then + await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> { + DocumentRepresentation watchResponse = givenAuth("alice") + .contentType(ContentType.JSON) + .when() + + .get("/" + nsId + "/documents/" + response.getId()) + .then() + .statusCode(200) + .extract().body().as(DocumentRepresentation.class); + return !watchResponse.isInProgress(); + }); + + givenAuth("alice") + .contentType(ContentType.JSON) + .when() + .get("/" + nsId + "/documents/" + response.getId()) + .then() + .statusCode(200) + .body("inProgress", is(false), + "error", is(nullValue()), + "fileContentValid", is(true), + "fileContent.ruc", is("12345678912"), + "fileContent.documentID", is("F001-1"), + "fileContent.documentType", is("Invoice") + ); + } + + @Test + public void createXMLWithAutoIDGeneratorConfig() throws URISyntaxException { + // Given + String nsId = "1"; + + InvoiceInputModel input = InvoiceInputModel.Builder.anInvoiceInputModel() + .withSerie("F001") + .withNumero(1) + .withProveedor(ProveedorInputModel.Builder.aProveedorInputModel() + .withRuc("12345678912") + .withRazonSocial("Softgreen S.A.C.") + .build() + ) + .withCliente(ClienteInputModel.Builder.aClienteInputModel() + .withNombre("Carlos Feria") + .withNumeroDocumentoIdentidad("12121212121") + .withTipoDocumentoIdentidad(Catalog6.RUC.toString()) + .build() + ) + .withDetalle(Arrays.asList( + DocumentLineInputModel.Builder.aDocumentLineInputModel() + .withDescripcion("Item1") + .withCantidad(new BigDecimal(10)) + .withPrecioUnitario(new BigDecimal(100)) + .withUnidadMedida("KGM") + .build(), + DocumentLineInputModel.Builder.aDocumentLineInputModel() + .withDescripcion("Item2") + .withCantidad(new BigDecimal(10)) + .withPrecioUnitario(new BigDecimal(100)) + .withUnidadMedida("KGM") + .build()) + ) + .build(); + + InputTemplateRepresentation template = InputTemplateRepresentation.Builder.anInputTemplateRepresentation() + .withKind(KindRepresentation.Invoice) + .withSpec(SpecRepresentation.Builder.aSpecRepresentation() + .withIdGenerator(IDGeneratorRepresentation.Builder.anIDGeneratorRepresentation() + .withName(IDGeneratorType.generated) + .withConfig(new HashMap() {{ + put(GeneratedIDGenerator.SERIE, "2"); + put(GeneratedIDGenerator.NUMERO, "33"); + }}) + .build() + ) + .withDocument(JsonObject.mapFrom(input)) + .build() + ) + .build(); + + // When + DocumentRepresentation response = givenAuth("alice") + .contentType(ContentType.JSON) + .body(template) + .when() + .post("/" + nsId + "/documents") + .then() + .statusCode(201) + .body("id", is(notNullValue()), + "namespaceId", is("1"), + "inProgress", is(true) + ) + .extract().body().as(DocumentRepresentation.class); + + // Then + await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> { + DocumentRepresentation watchResponse = givenAuth("alice") + .contentType(ContentType.JSON) + .when() + + .get("/" + nsId + "/documents/" + response.getId()) + .then() + .statusCode(200) + .extract().body().as(DocumentRepresentation.class); + return !watchResponse.isInProgress(); + }); + + givenAuth("alice") + .contentType(ContentType.JSON) + .when() + .get("/" + nsId + "/documents/" + response.getId()) + .then() + .statusCode(200) + .body("inProgress", is(false), + "error", is(nullValue()), + "fileContentValid", is(true), + "fileContent.ruc", is("12345678912"), + "fileContent.documentID", is("F002-33"), + "fileContent.documentType", is("Invoice") + ); + } + @Test public void uploadXML_byNotNsOwnerShouldNotBeAllowed() throws URISyntaxException { // Given From 44440a1e0bb63ead0a49a625dbb173c6a48771e0 Mon Sep 17 00:00:00 2001 From: Carlos Esteban Feria Vila <2582866+carlosthe19916@users.noreply.github.com> Date: Tue, 12 Oct 2021 07:39:19 +0200 Subject: [PATCH 2/8] Fix sql --- src/main/resources/db/migration/V1.0.0__initial_schema.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/db/migration/V1.0.0__initial_schema.sql b/src/main/resources/db/migration/V1.0.0__initial_schema.sql index e5bfebf9..15ed200a 100644 --- a/src/main/resources/db/migration/V1.0.0__initial_schema.sql +++ b/src/main/resources/db/migration/V1.0.0__initial_schema.sql @@ -71,7 +71,7 @@ create table component_config primary key (id) ); -gicreate table generated_id +create table generated_id ( id varchar(255) not null, ruc varchar(255) not null, From c23040a6393299f52d5b6a4c80588d12e1452f8f Mon Sep 17 00:00:00 2001 From: Carlos Esteban Feria Vila <2582866+carlosthe19916@users.noreply.github.com> Date: Tue, 12 Oct 2021 09:15:40 +0200 Subject: [PATCH 3/8] Change retry --- .../scheduler/commons/EventManagerUtils.java | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/main/java/io/github/project/openubl/xsender/scheduler/commons/EventManagerUtils.java b/src/main/java/io/github/project/openubl/xsender/scheduler/commons/EventManagerUtils.java index 0307d13c..f399bcef 100644 --- a/src/main/java/io/github/project/openubl/xsender/scheduler/commons/EventManagerUtils.java +++ b/src/main/java/io/github/project/openubl/xsender/scheduler/commons/EventManagerUtils.java @@ -55,18 +55,20 @@ public class EventManagerUtils { @Inject CompanyRepository companyRepository; + public Uni findByIdWithRetry(String documentId) { + return documentRepository.findById(documentId) + .onItem().ifNull().failWith(() -> { + logger.warn("Document was not found. It will be try."); + return new IllegalStateException("Document id=" + documentId + " was not found for being sent"); + }) + .onFailure(throwable -> throwable instanceof IllegalStateException) + .retry().atMost(3); + } + public Uni initDocumentUniSend(String documentId) { return Panache - .withTransaction(() -> documentRepository - .findById(documentId) - .onItem().ifNull().failWith(() -> { - logger.warn("Document was not found to be processed in the consumer for sendFile. It will be try."); - return new IllegalStateException("Document id=" + documentId + " was not found for being sent"); - }) - .onFailure(throwable -> throwable instanceof IllegalStateException) - .retry().withBackOff(Duration.ofMillis(200), Duration.ofMillis(200)).atMost(3) - - .onItem().ifNotNull().transform(documentEntity -> DocumentUniSendBuilder.aDocumentUniSend() + .withTransaction(() -> findByIdWithRetry(documentId) + .map(documentEntity -> DocumentUniSendBuilder.aDocumentUniSend() .withNamespaceId(documentEntity.namespace.id) .withId(documentEntity.id) .withRetries(documentEntity.retries) @@ -85,15 +87,7 @@ public Uni initDocumentUniSend(String documentId) { public Uni initDocumentUniTicket(String documentId) { return Panache - .withTransaction(() -> documentRepository - .findById(documentId) - .onItem().ifNull().failWith(() -> { - logger.warn("Document was not found to be processed in the consumer for sendTicket. It will be try."); - return new IllegalStateException("Document id=" + documentId + " was not found"); - }) - .onFailure(throwable -> throwable instanceof IllegalStateException) - .retry().withBackOff(Duration.ofMillis(200), Duration.ofMillis(200)).atMost(3) - + .withTransaction(() -> findByIdWithRetry(documentId) .onItem().ifNotNull().transform(documentEntity -> { XmlContentModel xmlContent = XmlContentModel.Builder.aXmlContentModel() .withRuc(documentEntity.ruc) From 4bfef89364a9b693377c099974747fe3765bcd68 Mon Sep 17 00:00:00 2001 From: Carlos Esteban Feria Vila <2582866+carlosthe19916@users.noreply.github.com> Date: Tue, 12 Oct 2021 09:48:27 +0200 Subject: [PATCH 4/8] Change retry --- .../xsender/scheduler/commons/EventManagerUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/github/project/openubl/xsender/scheduler/commons/EventManagerUtils.java b/src/main/java/io/github/project/openubl/xsender/scheduler/commons/EventManagerUtils.java index f399bcef..efe21489 100644 --- a/src/main/java/io/github/project/openubl/xsender/scheduler/commons/EventManagerUtils.java +++ b/src/main/java/io/github/project/openubl/xsender/scheduler/commons/EventManagerUtils.java @@ -56,13 +56,13 @@ public class EventManagerUtils { CompanyRepository companyRepository; public Uni findByIdWithRetry(String documentId) { - return documentRepository.findById(documentId) + return documentRepository + .findById(documentId) .onItem().ifNull().failWith(() -> { logger.warn("Document was not found. It will be try."); return new IllegalStateException("Document id=" + documentId + " was not found for being sent"); }) - .onFailure(throwable -> throwable instanceof IllegalStateException) - .retry().atMost(3); + .onFailure(throwable -> throwable instanceof IllegalStateException).retry().atMost(10); } public Uni initDocumentUniSend(String documentId) { From adc3c75bf31346f0829e537d49d737811e953832 Mon Sep 17 00:00:00 2001 From: Carlos Esteban Feria Vila <2582866+carlosthe19916@users.noreply.github.com> Date: Tue, 12 Oct 2021 09:57:30 +0200 Subject: [PATCH 5/8] Add delay --- .../scheduler/vertx/VertxSchedulerConsumer.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/project/openubl/xsender/scheduler/vertx/VertxSchedulerConsumer.java b/src/main/java/io/github/project/openubl/xsender/scheduler/vertx/VertxSchedulerConsumer.java index 40ac4116..0e3a5e6d 100644 --- a/src/main/java/io/github/project/openubl/xsender/scheduler/vertx/VertxSchedulerConsumer.java +++ b/src/main/java/io/github/project/openubl/xsender/scheduler/vertx/VertxSchedulerConsumer.java @@ -25,10 +25,13 @@ import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; +import java.time.Duration; @ApplicationScoped public class VertxSchedulerConsumer { + static final int INITIAL_DELAY = 500; + @Inject EventManagerUtils eventManagerUtils; @@ -37,7 +40,12 @@ public class VertxSchedulerConsumer { @ConsumeEvent(VertxScheduler.VERTX_SEND_FILE_SCHEDULER_BUS_NAME) public Uni sendFile(String documentId) { - return eventManagerUtils.initDocumentUniSend(documentId) + return Uni.createFrom().item("Start") + .onItem().delayIt().by(Duration.ofMillis(INITIAL_DELAY)) + + // Fetch document from DB + .chain(() -> eventManagerUtils.initDocumentUniSend(documentId)) + // Process file .chain(documentUni -> eventManagerUtils.enrichWithFileAsBytes(documentUni) .chain(fileBytes -> eventManagerUtils.enrichWithFileContent(documentUni, fileBytes)) @@ -70,7 +78,12 @@ public Uni sendFile(String documentId) { @ConsumeEvent(VertxScheduler.VERTX_CHECK_TICKET_SCHEDULER_BUS_NAME) public Uni checkTicket(String documentId) { - return eventManagerUtils.initDocumentUniTicket(documentId) + return Uni.createFrom().item("Start") + .onItem().delayIt().by(Duration.ofMillis(INITIAL_DELAY)) + + // Fetch document from DB + .chain(() -> eventManagerUtils.initDocumentUniTicket(documentId)) + // Process ticket .chain(documentUniTicket -> eventManagerUtils.enrichWithWsConfig(documentUniTicket) .chain(() -> eventManagerUtils.enrichWithCheckingTicket(documentUniTicket, 1)) From 98eb04ea1c4b53ee40e0c6b7844dfb4b31382593 Mon Sep 17 00:00:00 2001 From: Carlos Esteban Feria Vila <2582866+carlosthe19916@users.noreply.github.com> Date: Tue, 12 Oct 2021 16:00:06 +0200 Subject: [PATCH 6/8] Add generator for summary and voided documents --- .../generators/GeneratedIDGenerator.java | 95 +++++++++++++++---- .../resources/DocumentResourceTest.java | 5 +- 2 files changed, 76 insertions(+), 24 deletions(-) diff --git a/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/GeneratedIDGenerator.java b/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/GeneratedIDGenerator.java index b01bc44a..bbc794fc 100644 --- a/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/GeneratedIDGenerator.java +++ b/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/GeneratedIDGenerator.java @@ -29,9 +29,13 @@ import io.github.project.openubl.xsender.models.jpa.entities.NamespaceEntity; import io.smallrye.mutiny.Uni; import org.apache.commons.lang3.StringUtils; +import org.eclipse.microprofile.config.inject.ConfigProperty; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -41,8 +45,19 @@ @IDGeneratorProvider(IDGeneratorType.generated) public class GeneratedIDGenerator implements IDGenerator { - public static final String SERIE = "serie"; - public static final String NUMERO = "numero"; + public static final String SERIE_PROPERTY = "serie"; + public static final String NUMERO_PROPERTY = "numero"; + + private static final String INVOICE_TYPE = "Invoice"; + private static final String CREDIT_NOTE_FOR_FACTURA_TYPE = "CreditNote_Factura"; + private static final String CREDIT_NOTE_FOR_BOLETA_TYPE = "CreditNote_Boleta"; + private static final String DEBIT_NOTE_FOR_FACTURA_TYPE = "DebitNote_Factura"; + private static final String DEBIT_NOTE_FOR_BOLETA_TYPE = "DebitNote_Boleta"; + private static final String VOIDED_DOCUMENT_TYPE = "VoidedDocument"; + private static final String SUMMARY_DOCUMENT_TYPE = "SummaryDocument"; + + @ConfigProperty(name = "openubl.xbuilder.timezone") + String timezone; @Inject GeneratedIDRepository generatedIDRepository; @@ -51,7 +66,18 @@ private Uni generateNextID(NamespaceEntity namespace, String Map generatorConfig = Objects.requireNonNullElseGet(config, HashMap::new); return generatedIDRepository.getCurrentID(namespace, ruc, documentType) - .onItem().ifNull().continueWith(() -> generateFirstEntity(namespace, ruc, documentType, generatorConfig)) + .onItem().ifNull().continueWith(() -> { + GeneratedIDEntity entity = new GeneratedIDEntity(); + + entity.id = UUID.randomUUID().toString(); + entity.namespace = namespace; + entity.ruc = ruc; + entity.documentType = documentType; + entity.serie = 1; + entity.numero = 0; + + return entity; + }) .chain(generatedIDEntity -> { if (generatedIDEntity.numero > 99_999_999) { generatedIDEntity.serie++; @@ -60,29 +86,47 @@ private Uni generateNextID(NamespaceEntity namespace, String generatedIDEntity.numero++; } - generatedIDEntity.serie = Integer.parseInt(generatorConfig.getOrDefault(SERIE, String.valueOf(generatedIDEntity.serie))); - generatedIDEntity.numero = Integer.parseInt(generatorConfig.getOrDefault(NUMERO, String.valueOf(generatedIDEntity.numero))); + generatedIDEntity.serie = Integer.parseInt(generatorConfig.getOrDefault(SERIE_PROPERTY, String.valueOf(generatedIDEntity.serie))); + generatedIDEntity.numero = Integer.parseInt(generatorConfig.getOrDefault(NUMERO_PROPERTY, String.valueOf(generatedIDEntity.numero))); return generatedIDEntity.persist(); }); } - private GeneratedIDEntity generateFirstEntity(NamespaceEntity namespace, String ruc, String documentType, Map config) { - GeneratedIDEntity entity = new GeneratedIDEntity(); + private Uni generateNextIDVoidedAndSummaryDocument(NamespaceEntity namespace, String ruc, String documentType, Map config) { + return generatedIDRepository.getCurrentID(namespace, ruc, documentType) + .onItem().ifNull().continueWith(() -> { + GeneratedIDEntity entity = new GeneratedIDEntity(); + + entity.id = UUID.randomUUID().toString(); + entity.namespace = namespace; + entity.ruc = ruc; + entity.documentType = documentType; + entity.serie = Integer.parseInt(LocalDateTime + .now(ZoneId.of(timezone)) + .format(DateTimeFormatter.ofPattern("yyyyMMdd")) + ); + entity.numero = 0; + + return entity; + }) + .chain(generatedIDEntity -> { + int yyyyMMdd = Integer.parseInt(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))); - entity.id = UUID.randomUUID().toString(); - entity.namespace = namespace; - entity.ruc = ruc; - entity.documentType = documentType; - entity.serie = 1; - entity.numero = 0; + if (generatedIDEntity.serie == yyyyMMdd) { + generatedIDEntity.numero++; + } else { + generatedIDEntity.serie = yyyyMMdd; + generatedIDEntity.numero = 1; + } - return entity; + return generatedIDEntity.persist(); + }); } @Override public Uni enrichWithID(NamespaceEntity namespace, InvoiceInputModel invoice, Map config) { - return generateNextID(namespace, invoice.getProveedor().getRuc(), "Invoice", config) + return generateNextID(namespace, invoice.getProveedor().getRuc(), INVOICE_TYPE, config) .map(generatedIDEntity -> { invoice.setSerie("F" + StringUtils.leftPad(String.valueOf(generatedIDEntity.serie), 3, "0")); invoice.setNumero(generatedIDEntity.numero); @@ -96,10 +140,10 @@ public Uni enrichWithID(NamespaceEntity namespace, CreditN String documentType; String idPrefix; if (creditNote.getSerieNumeroComprobanteAfectado().toUpperCase().startsWith("F")) { - documentType = "CreditNote_Factura"; + documentType = CREDIT_NOTE_FOR_FACTURA_TYPE; idPrefix = "FC"; } else { - documentType = "CreditNote_Boleta"; + documentType = CREDIT_NOTE_FOR_BOLETA_TYPE; idPrefix = "BC"; } @@ -117,10 +161,10 @@ public Uni enrichWithID(NamespaceEntity namespace, DebitNot String documentType; String idPrefix; if (debitNote.getSerieNumeroComprobanteAfectado().toUpperCase().startsWith("F")) { - documentType = "DebitNote_Factura"; + documentType = DEBIT_NOTE_FOR_FACTURA_TYPE; idPrefix = "FD"; } else { - documentType = "DebitNote_Boleta"; + documentType = DEBIT_NOTE_FOR_BOLETA_TYPE; idPrefix = "BD"; } @@ -136,11 +180,20 @@ public Uni enrichWithID(NamespaceEntity namespace, DebitNot @Override public Uni enrichWithID(NamespaceEntity namespace, VoidedDocumentInputModel voidedDocument, Map config) { - return Uni.createFrom().item(voidedDocument); + return generateNextIDVoidedAndSummaryDocument(namespace, voidedDocument.getProveedor().getRuc(), VOIDED_DOCUMENT_TYPE, config) + .map(generatedIDEntity -> { + voidedDocument.setNumero(generatedIDEntity.numero); + return voidedDocument; + }); } @Override public Uni enrichWithID(NamespaceEntity namespace, SummaryDocumentInputModel summaryDocument, Map config) { - return Uni.createFrom().item(summaryDocument); + return generateNextIDVoidedAndSummaryDocument(namespace, summaryDocument.getProveedor().getRuc(), SUMMARY_DOCUMENT_TYPE, config) + .map(generatedIDEntity -> { + summaryDocument.setNumero(generatedIDEntity.numero); + return summaryDocument; + }); } + } diff --git a/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java b/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java index f26e40c2..575989fd 100644 --- a/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java +++ b/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java @@ -34,7 +34,6 @@ import io.quarkus.test.junit.TestProfile; import io.restassured.http.ContentType; import io.vertx.core.json.JsonObject; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.keycloak.crypto.Algorithm; @@ -550,8 +549,8 @@ public void createXMLWithAutoIDGeneratorConfig() throws URISyntaxException { .withIdGenerator(IDGeneratorRepresentation.Builder.anIDGeneratorRepresentation() .withName(IDGeneratorType.generated) .withConfig(new HashMap() {{ - put(GeneratedIDGenerator.SERIE, "2"); - put(GeneratedIDGenerator.NUMERO, "33"); + put(GeneratedIDGenerator.SERIE_PROPERTY, "2"); + put(GeneratedIDGenerator.NUMERO_PROPERTY, "33"); }}) .build() ) From 577376d3c4872d7909ac66c2994354d48b0e6e5a Mon Sep 17 00:00:00 2001 From: Carlos Esteban Feria Vila <2582866+carlosthe19916@users.noreply.github.com> Date: Wed, 13 Oct 2021 22:00:11 +0200 Subject: [PATCH 7/8] Add credit and debit note test --- .../generators/GeneratedIDGenerator.java | 228 +++++++++++++----- .../generators/NoneIDGenerator.java | 63 ++++- .../xsender/resources/DocumentResource.java | 22 +- .../resources/DocumentResourceTest.java | 192 ++++++++++++++- 4 files changed, 421 insertions(+), 84 deletions(-) diff --git a/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/GeneratedIDGenerator.java b/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/GeneratedIDGenerator.java index bbc794fc..50b8e0f4 100644 --- a/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/GeneratedIDGenerator.java +++ b/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/GeneratedIDGenerator.java @@ -33,13 +33,13 @@ import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; +import java.util.*; @ApplicationScoped @IDGeneratorProvider(IDGeneratorType.generated) @@ -48,20 +48,41 @@ public class GeneratedIDGenerator implements IDGenerator { public static final String SERIE_PROPERTY = "serie"; public static final String NUMERO_PROPERTY = "numero"; - private static final String INVOICE_TYPE = "Invoice"; - private static final String CREDIT_NOTE_FOR_FACTURA_TYPE = "CreditNote_Factura"; - private static final String CREDIT_NOTE_FOR_BOLETA_TYPE = "CreditNote_Boleta"; - private static final String DEBIT_NOTE_FOR_FACTURA_TYPE = "DebitNote_Factura"; - private static final String DEBIT_NOTE_FOR_BOLETA_TYPE = "DebitNote_Boleta"; - private static final String VOIDED_DOCUMENT_TYPE = "VoidedDocument"; - private static final String SUMMARY_DOCUMENT_TYPE = "SummaryDocument"; - @ConfigProperty(name = "openubl.xbuilder.timezone") String timezone; + @Inject + Validator validator; + @Inject GeneratedIDRepository generatedIDRepository; + enum DocumentType { + INVOICE_TYPE("Invoice", "F"), + CREDIT_NOTE_FOR_FACTURA_TYPE("CreditNote_Factura", "FC"), + CREDIT_NOTE_FOR_BOLETA_TYPE("CreditNote_Boleta", "BC"), + DEBIT_NOTE_FOR_FACTURA_TYPE("DebitNote_Factura", "FD"), + DEBIT_NOTE_FOR_BOLETA_TYPE("DebitNote_Boleta", "BD"), + VOIDED_DOCUMENT_TYPE("VoidedDocument", ""), + SUMMARY_DOCUMENT_TYPE("SummaryDocument", ""); + + private final String name; + private final String prefix; + + DocumentType(String name, String prefix) { + this.name = name; + this.prefix = prefix; + } + + public String getName() { + return name; + } + + public String getPrefix() { + return prefix; + } + } + private Uni generateNextID(NamespaceEntity namespace, String ruc, String documentType, Map config) { Map generatorConfig = Objects.requireNonNullElseGet(config, HashMap::new); @@ -126,74 +147,157 @@ private Uni generateNextIDVoidedAndSummaryDocument(NamespaceE @Override public Uni enrichWithID(NamespaceEntity namespace, InvoiceInputModel invoice, Map config) { - return generateNextID(namespace, invoice.getProveedor().getRuc(), INVOICE_TYPE, config) - .map(generatedIDEntity -> { - invoice.setSerie("F" + StringUtils.leftPad(String.valueOf(generatedIDEntity.serie), 3, "0")); - invoice.setNumero(generatedIDEntity.numero); - - return invoice; - }); + return Uni.createFrom().item(invoice) + .map(input -> { + input.setSerie("F001"); + input.setNumero(1); + return input; + }) + .chain(input -> Uni.createFrom().emitter(uniEmitter -> { + Set> violations = validator.validate(input); + if (violations.isEmpty()) { + uniEmitter.complete(input); + } else { + uniEmitter.fail(new ConstraintViolationException(violations)); + } + })) + .chain(input -> generateNextID(namespace, input.getProveedor().getRuc(), DocumentType.INVOICE_TYPE.name, config) + .map(generatedIDEntity -> { + input.setSerie(DocumentType.INVOICE_TYPE.prefix + StringUtils.leftPad(String.valueOf(generatedIDEntity.serie), 3, "0")); + input.setNumero(generatedIDEntity.numero); + return input; + }) + ); } @Override public Uni enrichWithID(NamespaceEntity namespace, CreditNoteInputModel creditNote, Map config) { - String documentType; - String idPrefix; - if (creditNote.getSerieNumeroComprobanteAfectado().toUpperCase().startsWith("F")) { - documentType = CREDIT_NOTE_FOR_FACTURA_TYPE; - idPrefix = "FC"; - } else { - documentType = CREDIT_NOTE_FOR_BOLETA_TYPE; - idPrefix = "BC"; - } - - return generateNextID(namespace, creditNote.getProveedor().getRuc(), documentType, config) - .map(generatedIDEntity -> { - creditNote.setSerie(idPrefix + StringUtils.leftPad(String.valueOf(generatedIDEntity.serie), 2)); - creditNote.setNumero(generatedIDEntity.numero); + return Uni.createFrom().emitter(uniEmitter -> { + if (creditNote.getSerieNumeroComprobanteAfectado() != null && + creditNote.getProveedor() != null && + creditNote.getProveedor().getRuc() != null + ) { + uniEmitter.complete(null); + } else { + uniEmitter.fail(new ConstraintViolationException("ProveedorRUC or SerieNumeroComprobanteAfectado invalid", new HashSet<>())); + } + }) + .map(unused -> { + if (creditNote.getSerieNumeroComprobanteAfectado().toUpperCase().startsWith("F")) { + return DocumentType.CREDIT_NOTE_FOR_FACTURA_TYPE; + } else { + return DocumentType.CREDIT_NOTE_FOR_BOLETA_TYPE; + } + }) + .chain(documentType -> Uni + .createFrom().emitter(uniEmitter -> { + // Set fake serie-numero for verifying hibernate-validator + creditNote.setSerie(documentType.prefix + "01"); + creditNote.setNumero(1); - return creditNote; - }); + // Validate input + Set> violations = validator.validate(creditNote); + if (violations.isEmpty()) { + uniEmitter.complete(null); + } else { + uniEmitter.fail(new ConstraintViolationException(violations)); + } + }) + .chain(unused -> generateNextID(namespace, creditNote.getProveedor().getRuc(), documentType.name, config)) + .map(generatedIDEntity -> { + creditNote.setSerie(documentType.prefix + StringUtils.leftPad(String.valueOf(generatedIDEntity.serie), 2, "0")); + creditNote.setNumero(generatedIDEntity.numero); + return creditNote; + }) + ); } @Override public Uni enrichWithID(NamespaceEntity namespace, DebitNoteInputModel debitNote, Map config) { - String documentType; - String idPrefix; - if (debitNote.getSerieNumeroComprobanteAfectado().toUpperCase().startsWith("F")) { - documentType = DEBIT_NOTE_FOR_FACTURA_TYPE; - idPrefix = "FD"; - } else { - documentType = DEBIT_NOTE_FOR_BOLETA_TYPE; - idPrefix = "BD"; - } - - return generateNextID(namespace, debitNote.getProveedor().getRuc(), documentType, config) - .map(generatedIDEntity -> { - debitNote.setSerie(idPrefix + StringUtils.leftPad(String.valueOf(generatedIDEntity.serie), 2)); - debitNote.setNumero(generatedIDEntity.numero); + return Uni.createFrom().emitter(uniEmitter -> { + if (debitNote.getSerieNumeroComprobanteAfectado() != null && + debitNote.getProveedor() != null && + debitNote.getProveedor().getRuc() != null + ) { + uniEmitter.complete(null); + } else { + uniEmitter.fail(new ConstraintViolationException("ProveedorRUC or SerieNumeroComprobanteAfectado invalid", new HashSet<>())); + } + }) + .map(unused -> { + if (debitNote.getSerieNumeroComprobanteAfectado().toUpperCase().startsWith("F")) { + return DocumentType.DEBIT_NOTE_FOR_FACTURA_TYPE; + } else { + return DocumentType.DEBIT_NOTE_FOR_BOLETA_TYPE; + } + }) + .chain(documentType -> Uni + .createFrom().emitter(uniEmitter -> { + // Set fake serie-numero for verifying hibernate-validator + debitNote.setSerie(documentType.prefix + "01"); + debitNote.setNumero(1); - return debitNote; - }); + // Validate input + Set> violations = validator.validate(debitNote); + if (violations.isEmpty()) { + uniEmitter.complete(null); + } else { + uniEmitter.fail(new ConstraintViolationException(violations)); + } + }) + .chain(unused -> generateNextID(namespace, debitNote.getProveedor().getRuc(), documentType.name, config)) + .map(generatedIDEntity -> { + debitNote.setSerie(documentType.prefix + StringUtils.leftPad(String.valueOf(generatedIDEntity.serie), 2, "0")); + debitNote.setNumero(generatedIDEntity.numero); + return debitNote; + }) + ); } - @Override public Uni enrichWithID(NamespaceEntity namespace, VoidedDocumentInputModel voidedDocument, Map config) { - return generateNextIDVoidedAndSummaryDocument(namespace, voidedDocument.getProveedor().getRuc(), VOIDED_DOCUMENT_TYPE, config) - .map(generatedIDEntity -> { - voidedDocument.setNumero(generatedIDEntity.numero); - return voidedDocument; - }); + return Uni.createFrom().item(voidedDocument) + .map(input -> { + input.setNumero(1); + return input; + }) + .chain(input -> Uni.createFrom().emitter(uniEmitter -> { + Set> violations = validator.validate(input); + if (violations.isEmpty()) { + uniEmitter.complete(input); + } else { + uniEmitter.fail(new ConstraintViolationException(violations)); + } + })) + .chain(input -> generateNextIDVoidedAndSummaryDocument(namespace, input.getProveedor().getRuc(), DocumentType.VOIDED_DOCUMENT_TYPE.name, config) + .map(generatedIDEntity -> { + input.setNumero(generatedIDEntity.numero); + return input; + }) + ); } @Override public Uni enrichWithID(NamespaceEntity namespace, SummaryDocumentInputModel summaryDocument, Map config) { - return generateNextIDVoidedAndSummaryDocument(namespace, summaryDocument.getProveedor().getRuc(), SUMMARY_DOCUMENT_TYPE, config) - .map(generatedIDEntity -> { - summaryDocument.setNumero(generatedIDEntity.numero); - return summaryDocument; - }); + return Uni.createFrom().item(summaryDocument) + .map(input -> { + input.setNumero(1); + return input; + }) + .chain(input -> Uni.createFrom().emitter(uniEmitter -> { + Set> violations = validator.validate(input); + if (violations.isEmpty()) { + uniEmitter.complete(input); + } else { + uniEmitter.fail(new ConstraintViolationException(violations)); + } + })) + .chain(input -> generateNextIDVoidedAndSummaryDocument(namespace, input.getProveedor().getRuc(), DocumentType.SUMMARY_DOCUMENT_TYPE.name, config) + .map(generatedIDEntity -> { + input.setNumero(generatedIDEntity.numero); + return input; + }) + ); } } diff --git a/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/NoneIDGenerator.java b/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/NoneIDGenerator.java index f956b26d..8a704b95 100644 --- a/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/NoneIDGenerator.java +++ b/src/main/java/io/github/project/openubl/xsender/idgenerator/generators/NoneIDGenerator.java @@ -28,39 +28,82 @@ import io.smallrye.mutiny.Uni; import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; import java.util.Map; +import java.util.Set; @ApplicationScoped @IDGeneratorProvider(IDGeneratorType.none) public class NoneIDGenerator implements IDGenerator { + @Inject + Validator validator; + @Override public Uni enrichWithID(NamespaceEntity namespace, InvoiceInputModel invoice, Map config) { - // Nothing to do - return Uni.createFrom().item(invoice); + return Uni.createFrom().item(invoice) + .map(input -> { + Set> violations = validator.validate(input); + if (violations.isEmpty()) { + return input; + } else { + throw new ConstraintViolationException(violations); + } + }); } @Override public Uni enrichWithID(NamespaceEntity namespace, CreditNoteInputModel creditNote, Map config) { - // Nothing to do - return Uni.createFrom().item(creditNote); + return Uni.createFrom().item(creditNote) + .map(input -> { + Set> violations = validator.validate(input); + if (violations.isEmpty()) { + return input; + } else { + throw new ConstraintViolationException(violations); + } + }); } @Override public Uni enrichWithID(NamespaceEntity namespace, DebitNoteInputModel debitNote, Map config) { - // Nothing to do - return Uni.createFrom().item(debitNote); + return Uni.createFrom().item(debitNote) + .map(input -> { + Set> violations = validator.validate(input); + if (violations.isEmpty()) { + return input; + } else { + throw new ConstraintViolationException(violations); + } + }); } @Override public Uni enrichWithID(NamespaceEntity namespace, VoidedDocumentInputModel voidedDocument, Map config) { - // Nothing to do - return Uni.createFrom().item(voidedDocument); + return Uni.createFrom().item(voidedDocument) + .map(input -> { + Set> violations = validator.validate(input); + if (violations.isEmpty()) { + return input; + } else { + throw new ConstraintViolationException(violations); + } + }); } @Override public Uni enrichWithID(NamespaceEntity namespace, SummaryDocumentInputModel summaryDocument, Map config) { - // Nothing to do - return Uni.createFrom().item(summaryDocument); + return Uni.createFrom().item(summaryDocument) + .map(input -> { + Set> violations = validator.validate(input); + if (violations.isEmpty()) { + return input; + } else { + throw new ConstraintViolationException(violations); + } + }); } } diff --git a/src/main/java/io/github/project/openubl/xsender/resources/DocumentResource.java b/src/main/java/io/github/project/openubl/xsender/resources/DocumentResource.java index a327a264..65c58254 100644 --- a/src/main/java/io/github/project/openubl/xsender/resources/DocumentResource.java +++ b/src/main/java/io/github/project/openubl/xsender/resources/DocumentResource.java @@ -57,7 +57,10 @@ import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; import javax.validation.Valid; +import javax.validation.Validator; import javax.validation.constraints.NotNull; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; @@ -66,6 +69,7 @@ import java.security.PublicKey; import java.util.Date; import java.util.List; +import java.util.Set; import java.util.UUID; @Path("/namespaces") @@ -116,6 +120,9 @@ public Uni createXMLString(NamespaceEntity namespace, InputTemplateRepre switch (kind) { case Invoice: InvoiceInputModel invoice = inputTemplate.getSpec().getDocument().mapTo(InvoiceInputModel.class); + invoice.setSerie("F001"); + invoice.setNumero(1); + return idGenerator .enrichWithID(namespace, invoice, inputTemplate.getSpec().getIdGenerator().getConfig()) .map(input -> DocumentManager.createXML(input, xBuilderConfig, xBuilderClock).getXml()); @@ -206,18 +213,21 @@ public Uni createDocument( .chain(fileSavedId -> createDocumentFromFileID(namespaceEntity, fileSavedId)) // Response - .map(documentEntity -> { - return Response - .status(Response.Status.CREATED) - .entity(EntityToRepresentation.toRepresentation(documentEntity)) - .build(); - } + .map(documentEntity -> Response + .status(Response.Status.CREATED) + .entity(EntityToRepresentation.toRepresentation(documentEntity)) + .build() ) ) .onItem().ifNull().continueWith(Response.ok() .status(Response.Status.NOT_FOUND)::build ) + + .onFailure(throwable -> throwable instanceof ConstraintViolationException).recoverWithItem(throwable -> Response + .status(Response.Status.BAD_REQUEST) + .entity(throwable.getMessage()).build() + ) ); } diff --git a/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java b/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java index 575989fd..e4796311 100644 --- a/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java +++ b/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java @@ -21,6 +21,8 @@ import io.github.project.openubl.xmlbuilderlib.models.input.common.ProveedorInputModel; import io.github.project.openubl.xmlbuilderlib.models.input.standard.DocumentLineInputModel; import io.github.project.openubl.xmlbuilderlib.models.input.standard.invoice.InvoiceInputModel; +import io.github.project.openubl.xmlbuilderlib.models.input.standard.note.creditNote.CreditNoteInputModel; +import io.github.project.openubl.xmlbuilderlib.models.input.standard.note.debitNote.DebitNoteInputModel; import io.github.project.openubl.xsender.BaseAuthTest; import io.github.project.openubl.xsender.ProfileManager; import io.github.project.openubl.xsender.idgenerator.IDGeneratorType; @@ -180,7 +182,7 @@ public void searchDocuments_filterTextByName() { } @Test - public void createXML_byNotNsOwnerShouldNotBeAllowed() throws URISyntaxException { + public void createInvoice_byNotNsOwnerShouldNotBeAllowed() { // Given String nsId = "3"; @@ -238,7 +240,7 @@ public void createXML_byNotNsOwnerShouldNotBeAllowed() throws URISyntaxException } @Test - public void createXMLWithDefaultSignAlgorithm() throws URISyntaxException { + public void createInvoiceWithDefaultSignAlgorithm() { // Given String nsId = "1"; @@ -327,7 +329,7 @@ public void createXMLWithDefaultSignAlgorithm() throws URISyntaxException { } @Test - public void createXMLWithCustomSignAlgorithm() throws URISyntaxException { + public void createInvoiceWithCustomSignAlgorithm() { // Given String nsId = "1"; @@ -420,7 +422,7 @@ public void createXMLWithCustomSignAlgorithm() throws URISyntaxException { } @Test - public void createXMLWithAutoIDGenerator() throws URISyntaxException { + public void createInvoiceWithAutoIDGenerator() { // Given String nsId = "1"; @@ -509,7 +511,7 @@ public void createXMLWithAutoIDGenerator() throws URISyntaxException { } @Test - public void createXMLWithAutoIDGeneratorConfig() throws URISyntaxException { + public void createInvoiceWithAutoIDGeneratorConfig() { // Given String nsId = "1"; @@ -548,7 +550,7 @@ public void createXMLWithAutoIDGeneratorConfig() throws URISyntaxException { .withSpec(SpecRepresentation.Builder.aSpecRepresentation() .withIdGenerator(IDGeneratorRepresentation.Builder.anIDGeneratorRepresentation() .withName(IDGeneratorType.generated) - .withConfig(new HashMap() {{ + .withConfig(new HashMap<>() {{ put(GeneratedIDGenerator.SERIE_PROPERTY, "2"); put(GeneratedIDGenerator.NUMERO_PROPERTY, "33"); }}) @@ -601,6 +603,184 @@ public void createXMLWithAutoIDGeneratorConfig() throws URISyntaxException { ); } + @Test + public void createCreditNoteWithAutoIDGenerator() { + // Given + String nsId = "1"; + + CreditNoteInputModel input = CreditNoteInputModel.Builder.aCreditNoteInputModel() + .withSerieNumeroComprobanteAfectado("F001-1") + .withDescripcionSustento("DescripciĆ³n") + .withProveedor(ProveedorInputModel.Builder.aProveedorInputModel() + .withRuc("12345678912") + .withRazonSocial("Softgreen S.A.C.") + .build() + ) + .withCliente(ClienteInputModel.Builder.aClienteInputModel() + .withNombre("Carlos Feria") + .withNumeroDocumentoIdentidad("12121212121") + .withTipoDocumentoIdentidad(Catalog6.RUC.toString()) + .build() + ) + .withDetalle(Arrays.asList( + DocumentLineInputModel.Builder.aDocumentLineInputModel() + .withDescripcion("Item1") + .withCantidad(new BigDecimal(10)) + .withPrecioUnitario(new BigDecimal(100)) + .withUnidadMedida("KGM") + .build(), + DocumentLineInputModel.Builder.aDocumentLineInputModel() + .withDescripcion("Item2") + .withCantidad(new BigDecimal(10)) + .withPrecioUnitario(new BigDecimal(100)) + .withUnidadMedida("KGM") + .build()) + ) + .build(); + + InputTemplateRepresentation template = InputTemplateRepresentation.Builder.anInputTemplateRepresentation() + .withKind(KindRepresentation.CreditNote) + .withSpec(SpecRepresentation.Builder.aSpecRepresentation() + .withIdGenerator(IDGeneratorRepresentation.Builder.anIDGeneratorRepresentation() + .withName(IDGeneratorType.generated) + .build() + ) + .withDocument(JsonObject.mapFrom(input)) + .build() + ) + .build(); + + // When + DocumentRepresentation response = givenAuth("alice") + .contentType(ContentType.JSON) + .body(template) + .when() + .post("/" + nsId + "/documents") + .then() + .statusCode(201) + .body("id", is(notNullValue()), + "namespaceId", is("1"), + "inProgress", is(true) + ) + .extract().body().as(DocumentRepresentation.class); + + // Then + await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> { + DocumentRepresentation watchResponse = givenAuth("alice") + .contentType(ContentType.JSON) + .when() + + .get("/" + nsId + "/documents/" + response.getId()) + .then() + .statusCode(200) + .extract().body().as(DocumentRepresentation.class); + return !watchResponse.isInProgress(); + }); + + givenAuth("alice") + .contentType(ContentType.JSON) + .when() + .get("/" + nsId + "/documents/" + response.getId()) + .then() + .statusCode(200) + .body("inProgress", is(false), + "error", is(nullValue()), + "fileContentValid", is(true), + "fileContent.ruc", is("12345678912"), + "fileContent.documentID", is("FC01-1"), + "fileContent.documentType", is("CreditNote") + ); + } + + @Test + public void createDebitNoteWithAutoIDGenerator() { + // Given + String nsId = "1"; + + DebitNoteInputModel input = DebitNoteInputModel.Builder.aDebitNoteInputModel() + .withSerieNumeroComprobanteAfectado("F001-1") + .withDescripcionSustento("DescripciĆ³n") + .withProveedor(ProveedorInputModel.Builder.aProveedorInputModel() + .withRuc("12345678912") + .withRazonSocial("Softgreen S.A.C.") + .build() + ) + .withCliente(ClienteInputModel.Builder.aClienteInputModel() + .withNombre("Carlos Feria") + .withNumeroDocumentoIdentidad("12121212121") + .withTipoDocumentoIdentidad(Catalog6.RUC.toString()) + .build() + ) + .withDetalle(Arrays.asList( + DocumentLineInputModel.Builder.aDocumentLineInputModel() + .withDescripcion("Item1") + .withCantidad(new BigDecimal(10)) + .withPrecioUnitario(new BigDecimal(100)) + .withUnidadMedida("KGM") + .build(), + DocumentLineInputModel.Builder.aDocumentLineInputModel() + .withDescripcion("Item2") + .withCantidad(new BigDecimal(10)) + .withPrecioUnitario(new BigDecimal(100)) + .withUnidadMedida("KGM") + .build()) + ) + .build(); + + InputTemplateRepresentation template = InputTemplateRepresentation.Builder.anInputTemplateRepresentation() + .withKind(KindRepresentation.DebitNote) + .withSpec(SpecRepresentation.Builder.aSpecRepresentation() + .withIdGenerator(IDGeneratorRepresentation.Builder.anIDGeneratorRepresentation() + .withName(IDGeneratorType.generated) + .build() + ) + .withDocument(JsonObject.mapFrom(input)) + .build() + ) + .build(); + + // When + DocumentRepresentation response = givenAuth("alice") + .contentType(ContentType.JSON) + .body(template) + .when() + .post("/" + nsId + "/documents") + .then() + .statusCode(201) + .body("id", is(notNullValue()), + "namespaceId", is("1"), + "inProgress", is(true) + ) + .extract().body().as(DocumentRepresentation.class); + + // Then + await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> { + DocumentRepresentation watchResponse = givenAuth("alice") + .contentType(ContentType.JSON) + .when() + + .get("/" + nsId + "/documents/" + response.getId()) + .then() + .statusCode(200) + .extract().body().as(DocumentRepresentation.class); + return !watchResponse.isInProgress(); + }); + + givenAuth("alice") + .contentType(ContentType.JSON) + .when() + .get("/" + nsId + "/documents/" + response.getId()) + .then() + .statusCode(200) + .body("inProgress", is(false), + "error", is(nullValue()), + "fileContentValid", is(true), + "fileContent.ruc", is("12345678912"), + "fileContent.documentID", is("FD01-1"), + "fileContent.documentType", is("DebitNote") + ); + } + @Test public void uploadXML_byNotNsOwnerShouldNotBeAllowed() throws URISyntaxException { // Given From d4b8c93dfd319bd83cb16cbbebabb3d543d651cd Mon Sep 17 00:00:00 2001 From: Carlos Esteban Feria Vila <2582866+carlosthe19916@users.noreply.github.com> Date: Sun, 17 Oct 2021 09:20:29 +0200 Subject: [PATCH 8/8] Add tests --- .../builder/UBLHubXBuilderConfigProducer.java | 91 --------- .../xsender/builder/UblHubXBuilderClock.java | 14 +- .../xsender/builder/UblHubXBuilderConfig.java | 78 ++++---- .../builder/UblHubXBuilderClockMock.java | 54 ++++++ .../resources/DocumentResourceTest.java | 181 +++++++++++++++++- 5 files changed, 278 insertions(+), 140 deletions(-) delete mode 100644 src/main/java/io/github/project/openubl/xsender/builder/UBLHubXBuilderConfigProducer.java create mode 100644 src/test/java/io/github/project/openubl/xsender/builder/UblHubXBuilderClockMock.java diff --git a/src/main/java/io/github/project/openubl/xsender/builder/UBLHubXBuilderConfigProducer.java b/src/main/java/io/github/project/openubl/xsender/builder/UBLHubXBuilderConfigProducer.java deleted file mode 100644 index 42706c42..00000000 --- a/src/main/java/io/github/project/openubl/xsender/builder/UBLHubXBuilderConfigProducer.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2019 Project OpenUBL, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Eclipse Public License - v 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.eclipse.org/legal/epl-2.0/ - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.github.project.openubl.xsender.builder; - -import io.github.project.openubl.xmlbuilderlib.models.catalogs.Catalog; -import io.github.project.openubl.xmlbuilderlib.models.catalogs.Catalog10; -import io.github.project.openubl.xmlbuilderlib.models.catalogs.Catalog7; -import io.github.project.openubl.xmlbuilderlib.models.catalogs.Catalog9; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.inject.Produces; -import java.math.BigDecimal; -import java.util.TimeZone; - -@ApplicationScoped -public class UBLHubXBuilderConfigProducer { - - @ConfigProperty(name = "openubl.xbuilder.igv") - BigDecimal igv; - - @ConfigProperty(name = "openubl.xbuilder.ivap") - BigDecimal ivap; - - @ConfigProperty(name = "openubl.xbuilder.defaultMoneda") - String defaultMoneda; - - @ConfigProperty(name = "openubl.xbuilder.defaultUnidadMedida") - String defaultUnidadMedida; - - @ConfigProperty(name = "openubl.xbuilder.defaultTipoNotaCredito") - String defaultTipoNotaCredito; - - @ConfigProperty(name = "openubl.xbuilder.defaultTipoNotaDebito") - String defaultTipoNotaDebito; - - @ConfigProperty(name = "openubl.xbuilder.defaultIcb") - BigDecimal defaultIcb; - - @ConfigProperty(name = "openubl.xbuilder.defaultTipoIgv") - String defaultTipoIgv; - - // - - @ConfigProperty(name = "openubl.xbuilder.timezone") - String timezone; - - @Produces - public UblHubXBuilderConfig produceConfig() { - UblHubXBuilderConfig config = new UblHubXBuilderConfig(); - - config.setIgv(igv); - config.setIvap(ivap); - config.setDefaultMoneda(defaultMoneda); - config.setDefaultUnidadMedida(defaultUnidadMedida); - config.setDefaultTipoNotaCredito(Catalog.valueOfCode(Catalog9.class, defaultTipoNotaCredito) - .orElseThrow(() -> new IllegalStateException("Invalid defaultTipoNotaCredito in config")) - ); - config.setDefaultTipoNotaDebito(Catalog.valueOfCode(Catalog10.class, defaultTipoNotaDebito) - .orElseThrow(() -> new IllegalStateException("Invalid defaultTipoNotaDebito in config")) - ); - config.setDefaultIcb(defaultIcb); - config.setDefaultTipoIgv(Catalog.valueOfCode(Catalog7.class, defaultTipoIgv) - .orElseThrow(() -> new IllegalStateException("Invalid defaultTipoIgv in config")) - ); - - return config; - } - - @Produces - public UblHubXBuilderClock produceClock() { - UblHubXBuilderClock clock = new UblHubXBuilderClock(); - clock.setTimeZone(TimeZone.getTimeZone(timezone)); - - return clock; - } -} diff --git a/src/main/java/io/github/project/openubl/xsender/builder/UblHubXBuilderClock.java b/src/main/java/io/github/project/openubl/xsender/builder/UblHubXBuilderClock.java index 02cd6e88..f38eda45 100644 --- a/src/main/java/io/github/project/openubl/xsender/builder/UblHubXBuilderClock.java +++ b/src/main/java/io/github/project/openubl/xsender/builder/UblHubXBuilderClock.java @@ -17,17 +17,23 @@ package io.github.project.openubl.xsender.builder; import io.github.project.openubl.xmlbuilderlib.clock.SystemClock; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Default; import java.util.Calendar; import java.util.TimeZone; +@Default +@ApplicationScoped public class UblHubXBuilderClock implements SystemClock { - private TimeZone timeZone; + @ConfigProperty(name = "openubl.xbuilder.timezone") + String timezone; @Override public TimeZone getTimeZone() { - return timeZone; + return TimeZone.getTimeZone(timezone); } @Override @@ -35,8 +41,4 @@ public Calendar getCalendarInstance() { return Calendar.getInstance(); } - public void setTimeZone(TimeZone timeZone) { - this.timeZone = timeZone; - } - } diff --git a/src/main/java/io/github/project/openubl/xsender/builder/UblHubXBuilderConfig.java b/src/main/java/io/github/project/openubl/xsender/builder/UblHubXBuilderConfig.java index 0b33508e..53572eb8 100644 --- a/src/main/java/io/github/project/openubl/xsender/builder/UblHubXBuilderConfig.java +++ b/src/main/java/io/github/project/openubl/xsender/builder/UblHubXBuilderConfig.java @@ -17,22 +17,43 @@ package io.github.project.openubl.xsender.builder; import io.github.project.openubl.xmlbuilderlib.config.Config; +import io.github.project.openubl.xmlbuilderlib.models.catalogs.Catalog; import io.github.project.openubl.xmlbuilderlib.models.catalogs.Catalog10; import io.github.project.openubl.xmlbuilderlib.models.catalogs.Catalog7; import io.github.project.openubl.xmlbuilderlib.models.catalogs.Catalog9; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Default; import java.math.BigDecimal; +@Default +@ApplicationScoped public class UblHubXBuilderConfig implements Config { - private BigDecimal igv; - private BigDecimal ivap; - private String defaultMoneda; - private String defaultUnidadMedida; - private Catalog9 defaultTipoNotaCredito; - private Catalog10 defaultTipoNotaDebito; - private BigDecimal defaultIcb; - private Catalog7 defaultTipoIgv; + @ConfigProperty(name = "openubl.xbuilder.igv") + BigDecimal igv; + + @ConfigProperty(name = "openubl.xbuilder.ivap") + BigDecimal ivap; + + @ConfigProperty(name = "openubl.xbuilder.defaultMoneda") + String defaultMoneda; + + @ConfigProperty(name = "openubl.xbuilder.defaultUnidadMedida") + String defaultUnidadMedida; + + @ConfigProperty(name = "openubl.xbuilder.defaultTipoNotaCredito") + String defaultTipoNotaCredito; + + @ConfigProperty(name = "openubl.xbuilder.defaultTipoNotaDebito") + String defaultTipoNotaDebito; + + @ConfigProperty(name = "openubl.xbuilder.defaultIcb") + BigDecimal defaultIcb; + + @ConfigProperty(name = "openubl.xbuilder.defaultTipoIgv") + String defaultTipoIgv; @Override public BigDecimal getIgv() { @@ -56,12 +77,15 @@ public String getDefaultUnidadMedida() { @Override public Catalog9 getDefaultTipoNotaCredito() { - return defaultTipoNotaCredito; + return Catalog.valueOfCode(Catalog9.class, defaultTipoNotaCredito) + .orElseThrow(() -> new IllegalStateException("Invalid defaultTipoNotaCredito in config") + ); } @Override public Catalog10 getDefaultTipoNotaDebito() { - return defaultTipoNotaDebito; + return Catalog.valueOfCode(Catalog10.class, defaultTipoNotaDebito) + .orElseThrow(() -> new IllegalStateException("Invalid defaultTipoNotaDebito in config")); } @Override @@ -71,38 +95,8 @@ public BigDecimal getDefaultIcb() { @Override public Catalog7 getDefaultTipoIgv() { - return defaultTipoIgv; - } - - public void setIgv(BigDecimal igv) { - this.igv = igv; - } - - public void setIvap(BigDecimal ivap) { - this.ivap = ivap; + return Catalog.valueOfCode(Catalog7.class, defaultTipoIgv) + .orElseThrow(() -> new IllegalStateException("Invalid defaultTipoIgv in config")); } - public void setDefaultMoneda(String defaultMoneda) { - this.defaultMoneda = defaultMoneda; - } - - public void setDefaultUnidadMedida(String defaultUnidadMedida) { - this.defaultUnidadMedida = defaultUnidadMedida; - } - - public void setDefaultTipoNotaCredito(Catalog9 defaultTipoNotaCredito) { - this.defaultTipoNotaCredito = defaultTipoNotaCredito; - } - - public void setDefaultTipoNotaDebito(Catalog10 defaultTipoNotaDebito) { - this.defaultTipoNotaDebito = defaultTipoNotaDebito; - } - - public void setDefaultIcb(BigDecimal defaultIcb) { - this.defaultIcb = defaultIcb; - } - - public void setDefaultTipoIgv(Catalog7 defaultTipoIgv) { - this.defaultTipoIgv = defaultTipoIgv; - } } diff --git a/src/test/java/io/github/project/openubl/xsender/builder/UblHubXBuilderClockMock.java b/src/test/java/io/github/project/openubl/xsender/builder/UblHubXBuilderClockMock.java new file mode 100644 index 00000000..c47e2398 --- /dev/null +++ b/src/test/java/io/github/project/openubl/xsender/builder/UblHubXBuilderClockMock.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Project OpenUBL, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Eclipse Public License - v 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.github.project.openubl.xsender.builder; + +import io.github.project.openubl.xmlbuilderlib.clock.SystemClock; + +import javax.annotation.Priority; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Alternative; +import java.util.Calendar; +import java.util.TimeZone; + +@Alternative +@Priority(1) +@ApplicationScoped +public class UblHubXBuilderClockMock implements SystemClock { + + private final TimeZone timeZone; + private final Calendar calendar; + + public UblHubXBuilderClockMock() { + this.timeZone = TimeZone.getTimeZone("America/Lima"); + + Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(timeZone); + calendar.set(2019, Calendar.DECEMBER, 24, 20, 30, 59); + this.calendar = calendar; + } + + @Override + public TimeZone getTimeZone() { + return timeZone; + } + + @Override + public Calendar getCalendarInstance() { + return calendar; + } + +} diff --git a/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java b/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java index e4796311..42a25b31 100644 --- a/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java +++ b/src/test/java/io/github/project/openubl/xsender/resources/DocumentResourceTest.java @@ -16,6 +16,8 @@ */ package io.github.project.openubl.xsender.resources; +import io.github.project.openubl.xmlbuilderlib.models.catalogs.Catalog1; +import io.github.project.openubl.xmlbuilderlib.models.catalogs.Catalog19; import io.github.project.openubl.xmlbuilderlib.models.catalogs.Catalog6; import io.github.project.openubl.xmlbuilderlib.models.input.common.ClienteInputModel; import io.github.project.openubl.xmlbuilderlib.models.input.common.ProveedorInputModel; @@ -23,6 +25,7 @@ import io.github.project.openubl.xmlbuilderlib.models.input.standard.invoice.InvoiceInputModel; import io.github.project.openubl.xmlbuilderlib.models.input.standard.note.creditNote.CreditNoteInputModel; import io.github.project.openubl.xmlbuilderlib.models.input.standard.note.debitNote.DebitNoteInputModel; +import io.github.project.openubl.xmlbuilderlib.models.input.sunat.*; import io.github.project.openubl.xsender.BaseAuthTest; import io.github.project.openubl.xsender.ProfileManager; import io.github.project.openubl.xsender.idgenerator.IDGeneratorType; @@ -30,7 +33,6 @@ import io.github.project.openubl.xsender.idm.DocumentRepresentation; import io.github.project.openubl.xsender.idm.input.*; import io.github.project.openubl.xsender.models.ErrorType; -import io.github.project.openubl.xsender.idm.input.KindRepresentation; import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; @@ -44,6 +46,8 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; import java.util.HashMap; import java.util.concurrent.TimeUnit; @@ -781,6 +785,181 @@ public void createDebitNoteWithAutoIDGenerator() { ); } + @Test + public void createVoidedDocumentWithAutoIDGenerator() { + // Given + String nsId = "1"; + + Calendar calendar = Calendar.getInstance(); + calendar.set(2019, Calendar.DECEMBER, 1, 20, 30, 59); + + VoidedDocumentInputModel input = VoidedDocumentInputModel.Builder.aVoidedDocumentInputModel() + .withNumero(1) + .withProveedor(ProveedorInputModel.Builder.aProveedorInputModel() + .withRuc("12345678912") + .withRazonSocial("Softgreen S.A.C.") + .build() + ) + .withDescripcionSustento("mi razon de baja") + .withComprobante(VoidedDocumentLineInputModel.Builder.aVoidedDocumentLineInputModel() + .withSerieNumero("F001-1") + .withTipoComprobante(Catalog1.FACTURA.toString()) + .withFechaEmision(calendar.getTimeInMillis()) + .build() + ) + .build(); + + InputTemplateRepresentation template = InputTemplateRepresentation.Builder.anInputTemplateRepresentation() + .withKind(KindRepresentation.VoidedDocument) + .withSpec(SpecRepresentation.Builder.aSpecRepresentation() + .withIdGenerator(IDGeneratorRepresentation.Builder.anIDGeneratorRepresentation() + .withName(IDGeneratorType.generated) + .build() + ) + .withDocument(JsonObject.mapFrom(input)) + .build() + ) + .build(); + + // When + DocumentRepresentation response = givenAuth("alice") + .contentType(ContentType.JSON) + .body(template) + .when() + .post("/" + nsId + "/documents") + .then() + .statusCode(201) + .body("id", is(notNullValue()), + "namespaceId", is("1"), + "inProgress", is(true) + ) + .extract().body().as(DocumentRepresentation.class); + + // Then + await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> { + DocumentRepresentation watchResponse = givenAuth("alice") + .contentType(ContentType.JSON) + .when() + + .get("/" + nsId + "/documents/" + response.getId()) + .then() + .statusCode(200) + .extract().body().as(DocumentRepresentation.class); + return !watchResponse.isInProgress(); + }); + + givenAuth("alice") + .contentType(ContentType.JSON) + .when() + .get("/" + nsId + "/documents/" + response.getId()) + .then() + .statusCode(200) + .body("inProgress", is(false), + "error", is(nullValue()), + "fileContentValid", is(true), + "fileContent.ruc", is("12345678912"), + "fileContent.documentID", is("RA-20191224-1"), + "fileContent.documentType", is("VoidedDocuments") + ); + } + + @Test + public void createSummaryDocumentWithAutoIDGenerator() { + // Given + String nsId = "1"; + + Calendar calendar = Calendar.getInstance(); + calendar.set(2019, Calendar.DECEMBER, 1, 20, 30, 59); + + SummaryDocumentInputModel input = SummaryDocumentInputModel.Builder.aSummaryDocumentInputModel() + .withNumero(1) + .withFechaEmisionDeComprobantesAsociados(calendar.getTimeInMillis()) + .withProveedor(ProveedorInputModel.Builder.aProveedorInputModel() + .withRuc("12345678912") + .withRazonSocial("Softgreen S.A.C.") + .build() + ) + .withDetalle(Collections.singletonList( + SummaryDocumentLineInputModel.Builder.aSummaryDocumentLineInputModel() + .withTipoOperacion(Catalog19.ADICIONAR.toString()) + .withComprobante(SummaryDocumentComprobanteInputModel.Builder.aSummaryDocumentComprobanteInputModel() + .withTipo(Catalog1.BOLETA.toString()) + .withSerieNumero("B001-1") + .withCliente(ClienteInputModel.Builder.aClienteInputModel() + .withNombre("Carlos Feria") + .withNumeroDocumentoIdentidad("12345678") + .withTipoDocumentoIdentidad(Catalog6.DNI.toString()) + .build() + ) + .withImpuestos(SummaryDocumentImpuestosInputModel.Builder.aSummaryDocumentImpuestosInputModel() + .withIgv(new BigDecimal("100")) + .build() + ) + .withValorVenta(SummaryDocumentComprobanteValorVentaInputModel.Builder.aSummaryDocumentComprobanteValorVentaInputModel() + .withImporteTotal(new BigDecimal("118")) + .withGravado(new BigDecimal("100")) + .build() + ) + .build() + ) + .build() + )) + .build(); + + InputTemplateRepresentation template = InputTemplateRepresentation.Builder.anInputTemplateRepresentation() + .withKind(KindRepresentation.SummaryDocument) + .withSpec(SpecRepresentation.Builder.aSpecRepresentation() + .withIdGenerator(IDGeneratorRepresentation.Builder.anIDGeneratorRepresentation() + .withName(IDGeneratorType.generated) + .build() + ) + .withDocument(JsonObject.mapFrom(input)) + .build() + ) + .build(); + + // When + DocumentRepresentation response = givenAuth("alice") + .contentType(ContentType.JSON) + .body(template) + .when() + .post("/" + nsId + "/documents") + .then() + .statusCode(201) + .body("id", is(notNullValue()), + "namespaceId", is("1"), + "inProgress", is(true) + ) + .extract().body().as(DocumentRepresentation.class); + + // Then + await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> { + DocumentRepresentation watchResponse = givenAuth("alice") + .contentType(ContentType.JSON) + .when() + + .get("/" + nsId + "/documents/" + response.getId()) + .then() + .statusCode(200) + .extract().body().as(DocumentRepresentation.class); + return !watchResponse.isInProgress(); + }); + + givenAuth("alice") + .contentType(ContentType.JSON) + .when() + .get("/" + nsId + "/documents/" + response.getId()) + .then() + .statusCode(200) + .body("inProgress", is(false), + "error", is(nullValue()), + "fileContentValid", is(true), + "fileContent.ruc", is("12345678912"), + "fileContent.documentID", is("RC-20191224-1"), + "fileContent.documentType", is("SummaryDocuments") + ); + } + @Test public void uploadXML_byNotNsOwnerShouldNotBeAllowed() throws URISyntaxException { // Given