diff --git a/.gitignore b/.gitignore index 8204312a..10d3305c 100644 --- a/.gitignore +++ b/.gitignore @@ -54,4 +54,5 @@ target *dependency-reduced-pom.xml /lsp/ -bin/ \ No newline at end of file +bin/ +.vscode/* \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index c5f3f6b9..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "java.configuration.updateBuildConfiguration": "interactive" -} \ No newline at end of file diff --git a/README.md b/README.md index 56252dbd..f22134d1 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,96 @@ +# XHandler Java + [![License](https://img.shields.io/github/license/project-openubl/xhandler?logo=apache)](https://www.apache.org/licenses/LICENSE-2.0) [![CI](https://github.com/project-openubl/xhandler/actions/workflows/ci.yml/badge.svg)](https://github.com/project-openubl/xhandler/actions/workflows/ci.yml) [![Project Chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?style=for-the-badge&logo=zulip)](https://projectopenubl.zulipchat.com/) [![Supported JVM Versions](https://img.shields.io/badge/JVM--17-brightgreen.svg?style=for-the-badge&logo=Java)](https://github.com/project-openubl/xhandler/actions/) -| Artifact | Version | -|-------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| XBuilder | [![Maven Central](https://img.shields.io/maven-central/v/io.github.project-openubl/xbuilder)](https://search.maven.org/artifact/io.github.project-openubl/xbuilder/) | -| XBuilder Quarkus extension | [![Maven Central](https://img.shields.io/maven-central/v/io.github.project-openubl/quarkus-xbuilder)](https://search.maven.org/artifact/io.github.project-openubl/quarkus-xbuilder/) | -| XSender | [![Maven Central](https://img.shields.io/maven-central/v/io.github.project-openubl/xsender)](https://search.maven.org/artifact/io.github.project-openubl/xsender/) | -| XSender Quarkus extension | [![Maven Central](https://img.shields.io/maven-central/v/io.github.project-openubl/quarkus-xsender)](https://search.maven.org/artifact/io.github.project-openubl/quarkus-xsender/) | -| XSender Spring Boot extension | [![Maven Central](https://img.shields.io/maven-central/v/io.github.project-openubl/spring-boot-xsender)](https://search.maven.org/artifact/io.github.project-openubl/spring-boot-xsender/) | +**XHandler Java** es una suite de herramientas diseñada para facilitar la integración de **Facturación Electrónica en Perú (SUNAT)** en aplicaciones Java. Este repositorio es un "monorepo" que alberga las librerías `XBuilder` y `XSender`, proporcionando una solución integral para crear, firmar y enviar comprobantes de pago electrónicos. + +> [!TIP] +> Si buscas integrar facturación electrónica de manera rápida y estándar, estás en el lugar correcto. + +--- + +## 📦 Ecosistema -# XBuilder +El proyecto se divide en módulos principales y extensiones para frameworks populares: -Librería Java para crear XMLs basados en UBL y los estándares de la SUNAT respecto a la facturación electrónica. +| Componente | Descripción | Maven Central | +|------------|-------------|---------------| +| **XBuilder** | Creación y firma de XMLs (UBL 2.1) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.project-openubl/xbuilder)](https://search.maven.org/artifact/io.github.project-openubl/xbuilder/) | +| **XSender** | Envío de comprobantes a SUNAT/OSE | [![Maven Central](https://img.shields.io/maven-central/v/io.github.project-openubl/xsender)](https://search.maven.org/artifact/io.github.project-openubl/xsender/) | +| **Quarkus XBuilder** | Extensión XBuilder para Quarkus | [![Maven Central](https://img.shields.io/maven-central/v/io.github.project-openubl/quarkus-xbuilder)](https://search.maven.org/artifact/io.github.project-openubl/quarkus-xbuilder/) | +| **Quarkus XSender** | Extensión XSender para Quarkus | [![Maven Central](https://img.shields.io/maven-central/v/io.github.project-openubl/quarkus-xsender)](https://search.maven.org/artifact/io.github.project-openubl/quarkus-xsender/) | +| **Spring Boot XSender** | Starter XSender para Spring Boot | [![Maven Central](https://img.shields.io/maven-central/v/io.github.project-openubl/spring-boot-xsender)](https://search.maven.org/artifact/io.github.project-openubl/spring-boot-xsender/) | -XBuilder esta diseñado para que puedas crear XMLs fácilmente. +--- -- Crea XMLs sin que necesites conocer nada sobre manejo de archivos XMLs. -- Hace cálculos internos por ti. -- Requiere solamente datos mínimos. +## 🛠️ XBuilder -## ¿Qué puedes hacer con XBuilder? +XBuilder abstrae la complejidad de los estándares UBL y XML, permitiéndote construir documentos tributarios válidos escribiendo código Java simple. -- Crear XMLs -- Firmar XMLs +### Características +- **Simple**: No necesitas manipular XML directamente ni conciliar namespaces complejos. +- **Completo**: Soporte para Facturas, Boletas, Notas de Crédito/Débito, Guías de Remisión y Percepciones/Retenciones. +- **Validado**: Realiza cálculos automáticos y validaciones básicas según normativa SUNAT. -### Update snapshots +### Ejemplo de Uso -```shell -mvn clean test -Dxbuilder.snapshot.update +```java +// Ejemplo simplificado de creación de factura +Invoice invoice = Invoice.builder() + .serie("F001") + .numero(1) + .proveedor(proveedor) + .cliente(cliente) + .detalle(detalle) + .build(); + +XMLInvoice xml = new InvoiceXMLBuilder().build(invoice); ``` -# XSender +> [!NOTE] +> Para actualizar los snapshots de prueba en desarrollo local, ejecuta: +> `mvn clean test -Dxbuilder.snapshot.update` + +--- + +## 🚀 XSender + +XSender se encarga de la comunicación con los servicios SOAP de la SUNAT o de los Operadores de Servicios Electrónicos (OSE). + +### Características +- **Compatible**: Soporta los diversos endpoints de SUNAT (Beta/Producción) y OSEs. +- **Resiliente**: Gestiona el envío de archivos ZIP y el procesamiento de respuestas (CDR, Tickets). +- **Flexible**: Fácil integración con frameworks modernos como Quarkus y Spring Boot. + +--- + +## 💻 Ejemplos + +Explora la carpeta `examples/` para ver implementaciones de referencia: + +- [**Spring Boot**](./examples/springbot): Ejemplo de integración completa usando Spring Boot. +- [**Wildfly**](./examples/wildfly): Ejemplo para servidores de aplicaciones Jakarta EE. +- [**Tomcat**](./examples/tomcat): Ejemplo ligero desplegable en Tomcat. +- [**XBuilder/XSender**](./examples): Ejemplos "standalone" de uso de las librerías. + +--- + +## 📚 Documentación + +Para guías detalladas, referencia de API y tutoriales, consulta nuestra documentación oficial. -Libreria para realizar envíos de comprobantes electrónicos a los servicios web de la SUNAT y/o OSCE de acuerdo a lo -especificado por la SUNAT. +- 📖 **Sitio Web**: [project-openubl.github.io](https://project-openubl.github.io) +- 💬 **Comunidad**: [Únete al chat en Zulip](https://projectopenubl.zulipchat.com/) +- 🐛 **Soporte**: [Reportar un problema o discutir mejoras](https://github.com/project-openubl/xsender/discussions) -# Getting started +--- -- [Documentación](https://project-openubl.github.io) -- [Discusiones](https://github.com/project-openubl/xsender/discussions) +## 📄 Licencia -## License +Este proyecto se distribuye bajo la licencia **Apache 2.0**. Consulta el archivo [LICENSE](LICENSE) para más detalles. -- [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) +Copyright © Project OpenUBL. diff --git a/examples/springbot/src/main/java/io/github/project/openubl/quickstart/xbuilder/springboot/XBuilderController.java b/examples/springbot/src/main/java/io/github/project/openubl/quickstart/xbuilder/springboot/XBuilderController.java index 626fdae0..0c2fca82 100644 --- a/examples/springbot/src/main/java/io/github/project/openubl/quickstart/xbuilder/springboot/XBuilderController.java +++ b/examples/springbot/src/main/java/io/github/project/openubl/quickstart/xbuilder/springboot/XBuilderController.java @@ -1,10 +1,13 @@ package io.github.project.openubl.quickstart.xbuilder.springboot; -import io.github.project.openubl.xbuilder.content.catalogs.Catalog6; +import io.github.project.openubl.xbuilder.content.catalogs.*; import io.github.project.openubl.xbuilder.content.models.common.Cliente; import io.github.project.openubl.xbuilder.content.models.common.Proveedor; -import io.github.project.openubl.xbuilder.content.models.standard.general.DocumentoVentaDetalle; -import io.github.project.openubl.xbuilder.content.models.standard.general.Invoice; +import io.github.project.openubl.xbuilder.content.models.standard.general.*; +import io.github.project.openubl.xbuilder.content.models.standard.guia.*; +import io.github.project.openubl.xbuilder.content.models.sunat.baja.*; +import io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.*; +import io.github.project.openubl.xbuilder.content.models.sunat.resumen.*; import io.github.project.openubl.xbuilder.enricher.ContentEnricher; import io.github.project.openubl.xbuilder.enricher.config.DateProvider; import io.github.project.openubl.xbuilder.enricher.config.Defaults; @@ -13,7 +16,6 @@ import io.github.project.openubl.xbuilder.signature.CertificateDetailsFactory; import io.github.project.openubl.xbuilder.signature.XMLSigner; import io.github.project.openubl.xbuilder.signature.XmlSignatureHelper; -import io.quarkus.qute.Template; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -39,18 +41,90 @@ public class XBuilderController { @RequestMapping( method = RequestMethod.POST, - value = "/api/create-xml", - produces = "text/plain" + value = "/api/create-xml/invoice", produces = "text/plain" ) - public String createXML(@RequestBody String clientName) throws Exception { + public String createInvoiceXML(@RequestBody String clientName) throws Exception { Invoice invoice = createInvoice(clientName); ContentEnricher enricher = new ContentEnricher(defaults, dateProvider); enricher.enrich(invoice); + String xml = TemplateProducer.getInstance().getInvoice().data(invoice).render(); + return signAndRender(xml); + } + + @RequestMapping(method = RequestMethod.POST, value = "/api/create-xml/credit-note", produces = "text/plain") + public String createCreditNoteXML(@RequestBody String clientName) throws Exception { + CreditNote input = createCreditNote(clientName); + ContentEnricher enricher = new ContentEnricher(defaults, dateProvider); + enricher.enrich(input); + String xml = TemplateProducer.getInstance().getCreditNote().data(input).render(); + return signAndRender(xml); + } + + @RequestMapping(method = RequestMethod.POST, value = "/api/create-xml/debit-note", produces = "text/plain") + public String createDebitNoteXML(@RequestBody String clientName) throws Exception { + DebitNote input = createDebitNote(clientName); + ContentEnricher enricher = new ContentEnricher(defaults, dateProvider); + enricher.enrich(input); + String xml = TemplateProducer.getInstance().getDebitNote().data(input).render(); + return signAndRender(xml); + } - Template template = TemplateProducer.getInstance().getInvoice(); - String xml = template.data(invoice).render(); + @RequestMapping(method = RequestMethod.POST, value = "/api/create-xml/voided-documents", produces = "text/plain") + public String createVoidedDocumentsXML(@RequestBody String clientName) throws Exception { + VoidedDocuments input = createVoidedDocuments(clientName); + ContentEnricher enricher = new ContentEnricher(defaults, dateProvider); + enricher.enrich(input); + String xml = TemplateProducer.getInstance().getVoidedDocument().data(input).render(); + return signAndRender(xml); + } + @RequestMapping(method = RequestMethod.POST, value = "/api/create-xml/summary-documents", produces = "text/plain") + public String createSummaryDocumentsXML(@RequestBody String clientName) throws Exception { + SummaryDocuments input = createSummaryDocuments(clientName); + ContentEnricher enricher = new ContentEnricher(defaults, dateProvider); + enricher.enrich(input); + String xml = TemplateProducer.getInstance().getSummaryDocuments().data(input).render(); + return signAndRender(xml); + } + + @RequestMapping(method = RequestMethod.POST, value = "/api/create-xml/perception", produces = "text/plain") + public String createPerceptionXML(@RequestBody String clientName) throws Exception { + Perception input = createPerception(clientName); + ContentEnricher enricher = new ContentEnricher(defaults, dateProvider); + enricher.enrich(input); + String xml = TemplateProducer.getInstance().getPerception().data(input).render(); + return signAndRender(xml); + } + + @RequestMapping(method = RequestMethod.POST, value = "/api/create-xml/retention", produces = "text/plain") + public String createRetentionXML(@RequestBody String clientName) throws Exception { + Retention input = createRetention(clientName); + ContentEnricher enricher = new ContentEnricher(defaults, dateProvider); + enricher.enrich(input); + String xml = TemplateProducer.getInstance().getRetention().data(input).render(); + return signAndRender(xml); + } + + @RequestMapping(method = RequestMethod.POST, value = "/api/create-xml/despatch-advice", produces = "text/plain") + public String createDespatchAdviceXML(@RequestBody String clientName) throws Exception { + DespatchAdvice input = createDespatchAdvice(clientName); + ContentEnricher enricher = new ContentEnricher(defaults, dateProvider); + enricher.enrich(input); + String xml = TemplateProducer.getInstance().getDespatchAdvice().data(input).render(); + return signAndRender(xml); + } + + @RequestMapping(method = RequestMethod.POST, value = "/api/create-xml/reversion", produces = "text/plain") + public String createReversionXML(@RequestBody String clientName) throws Exception { + Reversion input = createReversion(clientName); + ContentEnricher enricher = new ContentEnricher(defaults, dateProvider); + enricher.enrich(input); + String xml = TemplateProducer.getInstance().getReversion().data(input).render(); + return signAndRender(xml); + } + + private String signAndRender(String xml) throws Exception { // Sign XML InputStream ksInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("LLAMA-PE-CERTIFICADO-DEMO-12345678912.pfx"); CertificateDetails certificate = CertificateDetailsFactory.create(ksInputStream, "password"); @@ -96,4 +170,265 @@ private Invoice createInvoice(String clientName) { .build(); } + private CreditNote createCreditNote(String clientName) { + return CreditNote.builder() + .serie("FC01") + .numero(1) + .comprobanteAfectadoSerieNumero("F001-1") + .sustentoDescripcion("mi sustento") + .proveedor(Proveedor.builder() + .ruc("12345678912") + .razonSocial("Softgreen S.A.C.") + .build() + ) + .cliente(Cliente.builder() + .nombre(clientName) + .numeroDocumentoIdentidad("12121212121") + .tipoDocumentoIdentidad(Catalog6.RUC.toString()) + .build() + ) + .detalle(DocumentoVentaDetalle.builder() + .descripcion("Item1") + .cantidad(new BigDecimal("10")) + .precio(new BigDecimal("100")) + .build() + ) + .build(); + } + + private DebitNote createDebitNote(String clientName) { + return DebitNote.builder() + .serie("FD01") + .numero(1) + .comprobanteAfectadoSerieNumero("F001-1") + .sustentoDescripcion("mi sustento") + .proveedor(Proveedor.builder() + .ruc("12345678912") + .razonSocial("Softgreen S.A.C.") + .build() + ) + .cliente(Cliente.builder() + .nombre(clientName) + .numeroDocumentoIdentidad("12121212121") + .tipoDocumentoIdentidad(Catalog6.RUC.toString()) + .build() + ) + .detalle(DocumentoVentaDetalle.builder() + .descripcion("Item1") + .cantidad(new BigDecimal("10")) + .precio(new BigDecimal("100")) + .build() + ) + .build(); + } + + private VoidedDocuments createVoidedDocuments(String clientName) { + return VoidedDocuments.builder() + .numero(1) + .fechaEmision(LocalDate.of(2022, 01, 31)) + .fechaEmisionComprobantes(LocalDate.of(2022, 01, 29)) + .proveedor(Proveedor.builder() + .ruc("12345678912") + .razonSocial("Softgreen S.A.C.") + .build() + ) + .comprobante(VoidedDocumentsItem.builder() + .serie("F001") + .numero(1) + .tipoComprobante(Catalog1_Invoice.FACTURA.getCode()) + .descripcionSustento("Mi sustento1") + .build() + ) + .comprobante(VoidedDocumentsItem.builder() + .serie("F001") + .numero(2) + .tipoComprobante(Catalog1_Invoice.FACTURA.getCode()) + .descripcionSustento("Mi sustento2") + .build() + ) + .build(); + } + + private SummaryDocuments createSummaryDocuments(String clientName) { + return SummaryDocuments.builder() + .numero(1) + .fechaEmisionComprobantes(dateProvider.now().minusDays(2)) + .proveedor(Proveedor.builder() + .ruc("12345678912") + .razonSocial("Softgreen S.A.C.") + .build() + ) + .comprobante(SummaryDocumentsItem.builder() + .tipoOperacion(Catalog19.ADICIONAR.toString()) + .comprobante(Comprobante.builder() + .tipoComprobante(Catalog1_Invoice.BOLETA.getCode())// + .serieNumero("B001-1") + .cliente(Cliente.builder() + .nombre(clientName) + .numeroDocumentoIdentidad("12345678") + .tipoDocumentoIdentidad( + Catalog6.DNI.getCode()) + .build() + ) + .impuestos(ComprobanteImpuestos.builder() + .igv(new BigDecimal("18")) + .icb(new BigDecimal(2)) + .build() + ) + .valorVenta(ComprobanteValorVenta.builder() + .importeTotal(new BigDecimal("120")) + .gravado(new BigDecimal("120")) + .build() + ) + .build() + ) + .build() + ) + .build(); + } + + private Perception createPerception(String clientName) { + return Perception.builder() + .serie("P001") + .numero(1) + .fechaEmision(LocalDate.of(2022, 01, 31)) + .proveedor(Proveedor.builder() + .ruc("12345678912") + .razonSocial("Softgreen S.A.C.") + .build() + ) + .cliente(Cliente.builder() + .nombre(clientName) + .numeroDocumentoIdentidad("12121212121") + .tipoDocumentoIdentidad(Catalog6.RUC.getCode()) + .build() + ) + .importeTotalPercibido(new BigDecimal("10")) + .importeTotalCobrado(new BigDecimal("210")) + .tipoRegimen(Catalog22.VENTA_INTERNA.getCode()) + .tipoRegimenPorcentaje(Catalog22.VENTA_INTERNA.getPercent()) // + .operacion(PercepcionRetencionOperacion.builder() + .numeroOperacion(1) + .fechaOperacion(LocalDate.of(2022, 01, 31)) + .importeOperacion(new BigDecimal("100")) + .comprobante(io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.ComprobanteAfectado + .builder() + .tipoComprobante(Catalog1.FACTURA.getCode()) + .serieNumero("F001-1") + .fechaEmision(LocalDate.of(2022, 01, 31)) + .importeTotal(new BigDecimal("200")) + .moneda("PEN") + .build() + ) + .build() + ) + .build(); + } + + private Retention createRetention(String clientName) { + return Retention.builder() + .serie("R001") + .numero(1) + .fechaEmision(LocalDate.of(2022, 01, 31)) + .proveedor(Proveedor.builder() + .ruc("12345678912") + .razonSocial("Softgreen S.A.C.") + .build()) + .cliente(Cliente.builder() + .nombre(clientName) + .numeroDocumentoIdentidad("12121212121") + .tipoDocumentoIdentidad(Catalog6.RUC.getCode()) + .build() + ) + .importeTotalRetenido(new BigDecimal("10")) + .importeTotalPagado(new BigDecimal("200")) + .tipoRegimen(Catalog23.TASA_TRES.getCode()) + .tipoRegimenPorcentaje(Catalog23.TASA_TRES.getPercent()) // + .operacion(PercepcionRetencionOperacion.builder() + .numeroOperacion(1) + .fechaOperacion(LocalDate.of(2022, 01, 31)) + .importeOperacion(new BigDecimal("100")) + .comprobante(io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.ComprobanteAfectado + .builder() + .tipoComprobante(Catalog1.FACTURA.getCode()) + .serieNumero("F001-1") + .fechaEmision(LocalDate.of(2022, 01, 31)) + .importeTotal(new BigDecimal("210")) + .moneda("PEN") + .build() + ) + .build() + ) + .build(); + } + + private DespatchAdvice createDespatchAdvice(String clientName) { + return DespatchAdvice.builder() + .serie("T001") + .numero(1) + .tipoComprobante(Catalog1.GUIA_REMISION_REMITENTE.getCode()) + .remitente(Remitente.builder() + .ruc("12345678912") + .razonSocial("Softgreen S.A.C.") + .build() + ) + .destinatario(Destinatario.builder() + .tipoDocumentoIdentidad(Catalog6.DNI.getCode()) + .numeroDocumentoIdentidad("12345678") + .nombre(clientName) + .build() + ) + .envio(Envio.builder() + .tipoTraslado(Catalog20.TRASLADO_EMISOR_ITINERANTE_CP.getCode()) + .pesoTotal(BigDecimal.ONE) + .pesoTotalUnidadMedida("KG") + .tipoModalidadTraslado(Catalog18.TRANSPORTE_PRIVADO.getCode()) + .fechaTraslado(dateProvider.now()) + .partida(Partida.builder() + .direccion("DireccionOrigen") + .ubigeo("010101") + .build() + ) + .destino(Destino.builder() + .direccion("DireccionDestino") + .ubigeo("020202") + .build() + ) + .build()) + .detalle(DespatchAdviceItem.builder() + .cantidad(new BigDecimal("0.5")) + .unidadMedida("KG") + .codigo("123456") + .build() + ) + .build(); + } + + private Reversion createReversion(String clientName) { + return Reversion.builder() + .numero(1) + .fechaEmision(LocalDate.now()) + .fechaEmisionComprobantes(LocalDate.now().minusDays(1)) + .proveedor(Proveedor.builder() + .ruc("12345678912") + .razonSocial("Softgreen S.A.C.") + .build() + ) + .comprobante(VoidedDocumentsItem.builder() + .serie("P001") + .numero(1) + .tipoComprobante(Catalog1.PERCEPCION.getCode()) + .descripcionSustento("Anulacion de percepcion por error en emision") + .build() + ) + .comprobante(VoidedDocumentsItem.builder() + .serie("R001") + .numero(1) + .tipoComprobante(Catalog1.RETENCION.getCode()) + .descripcionSustento("Anulacion de retencion por duplicado") + .build() + ) + .build(); + } + } diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/jaxb/mappers/DespatchAdviceMapper.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/jaxb/mappers/DespatchAdviceMapper.java index 05f18fcf..22332ed4 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/jaxb/mappers/DespatchAdviceMapper.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/jaxb/mappers/DespatchAdviceMapper.java @@ -1,6 +1,7 @@ package io.github.project.openubl.xbuilder.content.jaxb.mappers; import io.github.project.openubl.xbuilder.content.jaxb.mappers.common.FirmanteMapper; +import java.util.List; import io.github.project.openubl.xbuilder.content.jaxb.mappers.common.Numero2Translator; import io.github.project.openubl.xbuilder.content.jaxb.mappers.common.SerieNumeroMapper; import io.github.project.openubl.xbuilder.content.jaxb.mappers.common.SerieNumeroTranslator; @@ -10,14 +11,21 @@ import io.github.project.openubl.xbuilder.content.models.common.Proveedor; import io.github.project.openubl.xbuilder.content.models.standard.guia.DespatchAdvice; import io.github.project.openubl.xbuilder.content.models.standard.guia.DespatchAdviceItem; +import io.github.project.openubl.xbuilder.content.models.standard.guia.Comprador; +import io.github.project.openubl.xbuilder.content.models.standard.guia.Tercero; import io.github.project.openubl.xbuilder.content.models.standard.guia.Destinatario; import io.github.project.openubl.xbuilder.content.models.standard.guia.Destino; import io.github.project.openubl.xbuilder.content.models.standard.guia.DocumentoBaja; import io.github.project.openubl.xbuilder.content.models.standard.guia.DocumentoRelacionado; +import io.github.project.openubl.xbuilder.content.models.standard.guia.DocumentoAdicional; import io.github.project.openubl.xbuilder.content.models.standard.guia.Envio; import io.github.project.openubl.xbuilder.content.models.standard.guia.Partida; import io.github.project.openubl.xbuilder.content.models.standard.guia.Remitente; import io.github.project.openubl.xbuilder.content.models.standard.guia.Transportista; +import io.github.project.openubl.xbuilder.content.models.standard.guia.Driver; +import io.github.project.openubl.xbuilder.content.models.standard.guia.GuiaItemAttribute; +import io.github.project.openubl.xbuilder.content.models.standard.guia.Puerto; +import io.github.project.openubl.xbuilder.content.models.standard.guia.Vehicle; import org.mapstruct.Condition; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -26,23 +34,29 @@ @Mapper(uses = { SerieNumeroMapper.class, FirmanteMapper.class -}) +}, nullValuePropertyMappingStrategy = org.mapstruct.NullValuePropertyMappingStrategy.SET_TO_DEFAULT) public interface DespatchAdviceMapper { - @Mapping(target = "serie", source = "documentId", qualifiedBy = {SerieNumeroTranslator.class, SerieTranslator.class}) - @Mapping(target = "numero", source = "documentId", qualifiedBy = {SerieNumeroTranslator.class, Numero2Translator.class}) + @Mapping(target = "serie", source = "documentId", qualifiedBy = { SerieNumeroTranslator.class, SerieTranslator.class }) + @Mapping(target = "numero", source = "documentId", qualifiedBy = { SerieNumeroTranslator.class, Numero2Translator.class }) + @Mapping(target = "version", source = "customizationId") @Mapping(target = "fechaEmision", source = "issueDate") @Mapping(target = "horaEmision", source = "issueTime") @Mapping(target = "tipoComprobante", source = "despatchAdviceTypeCode") @Mapping(target = "observaciones", source = "note") @Mapping(target = "documentoBaja", source = "orderReference") - @Mapping(target = "documentoRelacionado", source = "additionalDocumentReference") + @Mapping(target = "documentoRelacionado", ignore = true) @Mapping(target = "firmante", source = "signature") @Mapping(target = "remitente", source = "despatchSupplierParty") @Mapping(target = "destinatario", source = "deliveryCustomerParty") - @Mapping(target = "proveedor", source = "sellerSupplierParty") + @Mapping(target = "proveedor", source = "sellerSupplierParty", qualifiedByName = "mapDespatchAdviceProveedor") + @Mapping(target = "tercero", source = "sellerSupplierParty", qualifiedByName = "mapDespatchAdviceTercero") @Mapping(target = "envio", source = "shipment") @Mapping(target = "detalles", source = "lines") + @Mapping(target = "documentosAdicionales", source = "additionalDocumentReferences", qualifiedByName = "mapDocumentosAdicionales") + @Mapping(target = "comprador", source = "buyerCustomerParty") + @Mapping(target = "documentoAdicional", ignore = true) + @Mapping(target = "detalle", ignore = true) DespatchAdvice map(XMLDespatchAdvice xml); @Mapping(target = "tipoDocumento", source = "orderTypeCode") @@ -55,58 +69,274 @@ public interface DespatchAdviceMapper { @Mapping(target = "ruc", source = "party.partyIdentification.id.value") @Mapping(target = "razonSocial", source = "party.partyLegalEntity.registrationName") + @Mapping(target = "numeroRegistroMTC", source = "party.partyLegalEntity.companyID") Remitente mapRemitente(XMLDespatchAdvice.DespatchSupplierParty xml); + @Mapping(target = "tipo", source = "jobTitle") + @Mapping(target = "tipoDocumentoIdentidad", source = "id.schemeID") + @Mapping(target = "numeroDocumentoIdentidad", source = "id.value") + @Mapping(target = "nombres", source = "firstName") + @Mapping(target = "apellidos", source = "familyName") + @Mapping(target = "licencia", source = "identityDocumentReference.id") + Driver mapDriver(XMLDespatchAdvice.DriverPerson xml); + @Mapping(target = "numeroDocumentoIdentidad", source = "party.partyIdentification.id.value") @Mapping(target = "tipoDocumentoIdentidad", source = "party.partyIdentification.id.schemeID") @Mapping(target = "nombre", source = "party.partyLegalEntity.registrationName") Destinatario mapDestinatario(XMLDespatchAdvice.DeliveryCustomerParty xml); - @Mapping(target = "ruc", source = "customerAssignedAccountId") - @Mapping(target = "nombreComercial", source = "party.partyLegalEntity.registrationName") - Proveedor mapProveedor(XMLDespatchAdvice.SellerSupplierParty xml); + @Named("mapDespatchAdviceProveedor") + default Proveedor mapDespatchAdviceProveedor(XMLDespatchAdvice.SellerSupplierParty xml) { + if (xml == null || xml.getCustomerAssignedAccountId() == null) { + return null; + } + Proveedor.ProveedorBuilder builder = Proveedor.builder(); + builder.ruc(xml.getCustomerAssignedAccountId()); + if (xml.getParty() != null && xml.getParty().getPartyLegalEntity() != null) { + builder.razonSocial(xml.getParty().getPartyLegalEntity().getRegistrationName()); + builder.nombreComercial(xml.getParty().getPartyLegalEntity().getRegistrationName()); + } + return builder.build(); + } + + @Named("mapDespatchAdviceTercero") + default Tercero mapDespatchAdviceTercero(XMLDespatchAdvice.SellerSupplierParty xml) { + if (xml == null || xml.getParty() == null || xml.getParty().getPartyIdentification() == null) { + return null; + } + Tercero.TerceroBuilder builder = Tercero.builder(); + if (xml.getParty().getPartyIdentification().getId() != null) { + builder.numeroDocumentoIdentidad(xml.getParty().getPartyIdentification().getId().getValue()); + builder.tipoDocumentoIdentidad(xml.getParty().getPartyIdentification().getId().getSchemeID()); + } + if (xml.getParty().getPartyLegalEntity() != null) { + builder.nombre(xml.getParty().getPartyLegalEntity().getRegistrationName()); + } + return builder.build(); + } + + @Mapping(target = "tipoDocumentoIdentidad", source = "party.partyIdentification.id.schemeID") + @Mapping(target = "numeroDocumentoIdentidad", source = "party.partyIdentification.id.value") + @Mapping(target = "nombre", source = "party.partyLegalEntity.registrationName") + Comprador mapComprador(XMLDespatchAdvice.BuyerCustomerParty xml); @Mapping(target = "tipoTraslado", source = "handlingCode") - @Mapping(target = "motivoTraslado", source = "information") + @Mapping(target = "motivoTraslado", source = "handlingInstructions") @Mapping(target = "pesoTotal", source = "grossWeightMeasure.value") @Mapping(target = "pesoTotalUnidadMedida", source = "grossWeightMeasure.unitCode") @Mapping(target = "numeroDeBultos", source = "totalTransportHandlingUnitQuantity") - @Mapping(target = "transbordoProgramado", source = "splitConsignmentIndicator") @Mapping(target = "tipoModalidadTraslado", source = "shipmentStage.transportModeCode") @Mapping(target = "fechaTraslado", source = "shipmentStage.transitPeriod.startDate") - @Mapping(target = "numeroDeContenedor", source = "transportHandlingUnit.transportEquipment.id") - @Mapping(target = "codigoDePuerto", source = "firstArrivalPortLocation.id") @Mapping(target = "transportista", source = "shipmentStage", conditionQualifiedByName = "transportistaRequirements") @Mapping(target = "destino", source = "delivery") - @Mapping(target = "partida", source = "originAddress") + @Mapping(target = "partida", source = ".", qualifiedByName = "mapPartidaFromShipment") + @Mapping(target = "choferes", source = "shipmentStage.driverPersons", qualifiedByName = "mapChoferes") + @Mapping(target = "indicadores", source = "specialInstructions", qualifiedByName = "mapIndicadores") + @Mapping(target = "contenedores", source = "transportHandlingUnit", qualifiedByName = "mapContenedores") + @Mapping(target = "pesoItems", source = "netWeightMeasure.value") + @Mapping(target = "sustentoPeso", source = "information") + @Mapping(target = "puerto", source = "firstArrivalPortLocation", qualifiedByName = "mapPuerto") + @Mapping(target = "aeropuerto", source = "firstArrivalPortLocation", qualifiedByName = "mapAeropuerto") + @Mapping(target = "vehiculo", source = "transportHandlingUnit", qualifiedByName = "mapVehiculo") + @Mapping(target = "chofer", ignore = true) + @Mapping(target = "indicador", ignore = true) + @Mapping(target = "contenedor", ignore = true) Envio mapEnvio(XMLDespatchAdvice.Shipment xml); @Condition @Named("transportistaRequirements") default boolean conditionTransportista(XMLDespatchAdvice.ShipmentStage xml) { - return xml.getCarrierParty() != null && xml.getTransportMeans() != null && xml.getDriverPerson() != null; + return xml.getCarrierParty() != null && xml.getTransportMeans() != null && xml.getDriverPersons() != null && !xml.getDriverPersons().isEmpty(); } - @Mapping(target = "tipoDocumentoIdentidad", source = "carrierParty.partyIdentification.id.value") - @Mapping(target = "numeroDocumentoIdentidad", source = "carrierParty.partyIdentification.id.schemeID") - @Mapping(target = "nombre", source = "carrierParty.partyName.name") - @Mapping(target = "placaDelVehiculo", source = "transportMeans.roadTransport.licensePlateID") - @Mapping(target = "choferTipoDocumentoIdentidad", source = "driverPerson.id.schemeID") - @Mapping(target = "choferNumeroDocumentoIdentidad", source = "driverPerson.id.value") + @Mapping(target = "tipoDocumentoIdentidad", source = "carrierParty.partyIdentification.id.schemeID") + @Mapping(target = "numeroDocumentoIdentidad", source = "carrierParty.partyIdentification.id.value") + @Mapping(target = "nombre", source = "carrierParty.partyLegalEntity.registrationName") + @Mapping(target = "numeroRegistroMTC", source = "carrierParty.partyLegalEntity.companyID") Transportista mapTransportista(XMLDespatchAdvice.ShipmentStage xml); @Mapping(target = "ubigeo", source = "deliveryAddress.id") @Mapping(target = "direccion", source = "deliveryAddress.addressLine.line") + @Mapping(target = "codigoLocal", source = "deliveryAddress.addressTypeCode.value") + @Mapping(target = "ruc", source = "deliveryAddress.addressTypeCode.listID") Destino mapDelivery(XMLDespatchAdvice.Delivery xml); @Mapping(target = "ubigeo", source = "id") @Mapping(target = "direccion", source = "streetName") + @Mapping(target = "codigoLocal", ignore = true) + @Mapping(target = "ruc", ignore = true) Partida mapPartida(XMLDespatchAdvice.OriginAddress xml); @Mapping(target = "unidadMedida", source = "deliveredQuantity.unitCode") @Mapping(target = "cantidad", source = "deliveredQuantity.value") - @Mapping(target = "descripcion", source = "item.name") + @Mapping(target = "descripcion", source = "item", qualifiedByName = "mapItemDescription") @Mapping(target = "codigo", source = "item.sellersItemIdentification.id") @Mapping(target = "codigoSunat", source = "item.commodityClassification.itemClassificationCode") + @Mapping(target = "atributos", source = "item.additionalItemProperties", qualifiedByName = "mapAtributos") + @Mapping(target = "atributo", ignore = true) DespatchAdviceItem mapLine(XMLDespatchAdviceLine xml); + + @Mapping(target = "code", source = "nameCode") + GuiaItemAttribute mapAttribute(XMLDespatchAdviceLine.AdditionalItemProperty xml); + + @Named("mapItemDescription") + default String mapItemDescription(XMLDespatchAdviceLine.Item item) { + if (item == null) { + return null; + } + // Prefer description (new format) over name (old format) + return item.getDescription() != null ? item.getDescription() : item.getName(); + } + + @Named("mapPartidaFromShipment") + default Partida mapPartidaFromShipment(XMLDespatchAdvice.Shipment shipment) { + if (shipment == null) { + return null; + } + + // Try new format first: delivery.despatch.despatchAddress + if (shipment.getDelivery() != null && + shipment.getDelivery().getDespatch() != null && + shipment.getDelivery().getDespatch().getDespatchAddress() != null) { + XMLDespatchAdvice.DespatchAddress addr = shipment.getDelivery().getDespatch().getDespatchAddress(); + return Partida.builder() + .ubigeo(addr.getId()) + .direccion(addr.getAddressLine() != null ? addr.getAddressLine().getLine() : null) + .codigoLocal(addr.getAddressTypeCode() != null ? addr.getAddressTypeCode().getValue() : null) + .ruc(addr.getAddressTypeCode() != null ? addr.getAddressTypeCode().getListID() : null) + .build(); + } + + return null; + } + + @Named("mapPrimaryDriverTipo") + default String mapPrimaryDriverTipo(List drivers) { + if (drivers == null || drivers.isEmpty()) + return null; + return drivers.get(0).getId() != null ? drivers.get(0).getId().getSchemeID() : null; + } + + @Named("mapPrimaryDriverNum") + default String mapPrimaryDriverNum(List drivers) { + if (drivers == null || drivers.isEmpty()) + return null; + return drivers.get(0).getId() != null ? drivers.get(0).getId().getValue() : null; + } + + @Named("mapContenedores") + default List mapContenedores(List units) { + if (units == null) + return java.util.Collections.emptyList(); + List result = new java.util.ArrayList<>(); + for (XMLDespatchAdvice.TransportHandlingUnit unit : units) { + if (unit.getPackages() != null) { + unit.getPackages().stream().map(XMLDespatchAdvice.Package::getTraceID) + .filter(java.util.Objects::nonNull) + .forEach(result::add); + } + if (unit.getTransportEquipments() != null) { + // Only add as container if it's NOT a vehicle (simple ID, no transport means) + unit.getTransportEquipments().stream() + .filter(e -> e.getApplicableTransportMeans() == null) + .map(XMLDespatchAdvice.TransportEquipment::getId) + .filter(java.util.Objects::nonNull) + .forEach(result::add); + } + } + return result; + } + + @Named("mapChoferes") + default List mapChoferes(List drivers) { + if (drivers == null) + return java.util.Collections.emptyList(); + return drivers.stream().map(this::mapDriver).collect(java.util.stream.Collectors.toList()); + } + + @Named("mapIndicadores") + default List mapIndicadores(List indicators) { + if (indicators == null) + return java.util.Collections.emptyList(); + return indicators; + } + + @Named("mapAtributos") + default List mapAtributos(List attrs) { + if (attrs == null) + return java.util.Collections.emptyList(); + return attrs.stream().map(this::mapAttribute).collect(java.util.stream.Collectors.toList()); + } + + @Named("mapDocumentosAdicionales") + default List mapDocumentosAdicionales( + List rels) { + if (rels == null) + return java.util.Collections.emptyList(); + return rels.stream().map(this::mapDocumentoAdicional).collect(java.util.stream.Collectors.toList()); + } + + @Named("mapPuerto") + default Puerto mapPuerto(XMLDespatchAdvice.FirstArrivalPortLocation xml) { + if (xml == null || !"1".equals(xml.getLocationTypeCode())) + return null; + return Puerto.builder().codigo(xml.getId()).nombre(xml.getName()).build(); + } + + @Named("mapAeropuerto") + default Puerto mapAeropuerto(XMLDespatchAdvice.FirstArrivalPortLocation xml) { + if (xml == null || !"2".equals(xml.getLocationTypeCode())) + return null; + return Puerto.builder().codigo(xml.getId()).nombre(xml.getName()).build(); + } + + @Named("mapVehiculo") + default Vehicle mapVehiculo(List units) { + if (units == null) + return null; + // The first equipment with transport means or multiple are usually vehicles + return units.stream() + .filter(u -> u.getTransportEquipments() != null) + .flatMap(u -> u.getTransportEquipments().stream()) + .filter(e -> e.getApplicableTransportMeans() != null || e.getAttachedTransportEquipments() != null) + .findFirst() + .map(this::mapDetailedVehicle) + .orElse(null); + } + + default Vehicle mapDetailedVehicle(XMLDespatchAdvice.TransportEquipment xml) { + if (xml == null) + return null; + Vehicle.VehicleBuilder builder = Vehicle.builder().placa(xml.getId()); + if (xml.getApplicableTransportMeans() != null) { + builder.numeroCirculacion(xml.getApplicableTransportMeans().getRegistrationNationalityID()); + } + if (xml.getShipmentDocumentReferences() != null && !xml.getShipmentDocumentReferences().isEmpty()) { + XMLDespatchAdvice.ShipmentDocumentReference doc = xml.getShipmentDocumentReferences().get(0); + if (doc.getId() != null) { + builder.numeroAutorizacion(doc.getId().getValue()); + builder.codigoEmisor(doc.getId().getSchemeID()); + } + } + if (xml.getAttachedTransportEquipments() != null) { + builder.secundarios(xml.getAttachedTransportEquipments().stream() + .map(this::mapDetailedVehicle) + .collect(java.util.stream.Collectors.toList())); + } + return builder.build(); + } + + @Mapping(target = "tipoDocumento", source = "documentTypeCode") + @Mapping(target = "tipoDocumentoDescripcion", source = "documentType") + @Mapping(target = "rucEmisor", source = "issuerParty.partyIdentification.id.value") + @Mapping(target = "numero", source = "id") + DocumentoAdicional mapDocumentoAdicional(XMLDespatchAdvice.AdditionalDocumentReference xml); + + @Named("mapPrimaryAdditionalDocument") + default DocumentoRelacionado mapPrimaryAdditionalDocument( + List rels) { + if (rels == null || rels.isEmpty()) + return null; + return mapDocumentoRelacionado(rels.get(0)); + } } diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/jaxb/models/XMLDespatchAdvice.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/jaxb/models/XMLDespatchAdvice.java index b2a71c16..d010512c 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/jaxb/models/XMLDespatchAdvice.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/jaxb/models/XMLDespatchAdvice.java @@ -23,6 +23,11 @@ @Data @NoArgsConstructor public class XMLDespatchAdvice { + @XmlElement(name = "UBLVersionID", namespace = XMLConstants.CBC) + private String ublVersionId; + + @XmlElement(name = "CustomizationID", namespace = XMLConstants.CBC) + private String customizationId; @XmlElement(name = "ID", namespace = XMLConstants.CBC) private String documentId; @@ -45,7 +50,7 @@ public class XMLDespatchAdvice { private OrderReference orderReference; @XmlElement(name = "AdditionalDocumentReference", namespace = XMLConstants.CAC) - private AdditionalDocumentReference additionalDocumentReference; + private List additionalDocumentReferences; @XmlElement(name = "Signature", namespace = XMLConstants.CAC) private XMLSignature signature; @@ -59,6 +64,9 @@ public class XMLDespatchAdvice { @XmlElement(name = "SellerSupplierParty", namespace = XMLConstants.CAC) private SellerSupplierParty sellerSupplierParty; + @XmlElement(name = "BuyerCustomerParty", namespace = XMLConstants.CAC) + private BuyerCustomerParty buyerCustomerParty; + @XmlElement(name = "Shipment", namespace = XMLConstants.CAC) private Shipment shipment; @@ -87,6 +95,21 @@ public static class AdditionalDocumentReference { @XmlElement(name = "DocumentTypeCode", namespace = XMLConstants.CBC) private String documentTypeCode; + + @XmlElement(name = "DocumentType", namespace = XMLConstants.CBC) + private String documentType; + + @XmlElement(name = "IssuerParty", namespace = XMLConstants.CAC) + private IssuerParty issuerParty; + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "DespatchAdvice.IssuerParty") + @Data + @NoArgsConstructor + public static class IssuerParty { + @XmlElement(name = "PartyIdentification", namespace = XMLConstants.CAC) + private PartyIdentification partyIdentification; } @XmlAccessorType(XmlAccessType.NONE) @@ -138,8 +161,22 @@ public static class ID { public static class PartyLegalEntity { @XmlElement(name = "RegistrationName", namespace = XMLConstants.CBC) private String registrationName; + + @XmlElement(name = "CompanyID", namespace = XMLConstants.CBC) + private String companyID; } + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "DespatchAdvice.BuyerCustomerParty") + @Data + @NoArgsConstructor + public static class BuyerCustomerParty { + @XmlElement(name = "CustomerAssignedAccountId", namespace = XMLConstants.CBC) + private String customerAssignedAccountId; + + @XmlElement(name = "Party", namespace = XMLConstants.CAC) + private Party party; + } @XmlAccessorType(XmlAccessType.NONE) @XmlType(name = "DespatchAdvice.DeliveryCustomerParty") @@ -179,8 +216,14 @@ public static class Shipment { @XmlElement(name = "TotalTransportHandlingUnitQuantity", namespace = XMLConstants.CBC) private Integer totalTransportHandlingUnitQuantity; - @XmlElement(name = "SplitConsignmentIndicator", namespace = XMLConstants.CBC) - private Boolean splitConsignmentIndicator; + @XmlElement(name = "HandlingInstructions", namespace = XMLConstants.CBC) + private String handlingInstructions; + + @XmlElement(name = "SpecialInstructions", namespace = XMLConstants.CBC) + private List specialInstructions; + + @XmlElement(name = "NetWeightMeasure", namespace = XMLConstants.CBC) + private GrossWeightMeasure netWeightMeasure; @XmlElement(name = "ShipmentStage", namespace = XMLConstants.CAC) private ShipmentStage shipmentStage; @@ -189,10 +232,7 @@ public static class Shipment { private Delivery delivery; @XmlElement(name = "TransportHandlingUnit", namespace = XMLConstants.CAC) - private TransportHandlingUnit transportHandlingUnit; - - @XmlElement(name = "OriginAddress", namespace = XMLConstants.CAC) - private OriginAddress originAddress; + private List transportHandlingUnit; @XmlElement(name = "FirstArrivalPortLocation", namespace = XMLConstants.CAC) private FirstArrivalPortLocation firstArrivalPortLocation; @@ -228,7 +268,7 @@ public static class ShipmentStage { private TransportMeans transportMeans; @XmlElement(name = "DriverPerson", namespace = XMLConstants.CAC) - private DriverPerson driverPerson; + private List driverPersons; } @XmlAccessorType(XmlAccessType.NONE) @@ -251,6 +291,9 @@ public static class CarrierParty { @XmlElement(name = "PartyName", namespace = XMLConstants.CAC) private PartyName partyName; + + @XmlElement(name = "PartyLegalEntity", namespace = XMLConstants.CAC) + private PartyLegalEntity partyLegalEntity; } @XmlAccessorType(XmlAccessType.NONE) @@ -287,6 +330,27 @@ public static class RoadTransport { public static class DriverPerson { @XmlElement(name = "ID", namespace = XMLConstants.CBC) private ID id; + + @XmlElement(name = "FirstName", namespace = XMLConstants.CBC) + private String firstName; + + @XmlElement(name = "FamilyName", namespace = XMLConstants.CBC) + private String familyName; + + @XmlElement(name = "JobTitle", namespace = XMLConstants.CBC) + private String jobTitle; + + @XmlElement(name = "IdentityDocumentReference", namespace = XMLConstants.CAC) + private IdentityDocumentReference identityDocumentReference; + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "DespatchAdvice.IdentityDocumentReference") + @Data + @NoArgsConstructor + public static class IdentityDocumentReference { + @XmlElement(name = "ID", namespace = XMLConstants.CBC) + private String id; } @XmlAccessorType(XmlAccessType.NONE) @@ -296,6 +360,33 @@ public static class DriverPerson { public static class Delivery { @XmlElement(name = "DeliveryAddress", namespace = XMLConstants.CAC) private DeliveryAddress deliveryAddress; + + @XmlElement(name = "Despatch", namespace = XMLConstants.CAC) + private Despatch despatch; + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "DespatchAdvice.Despatch") + @Data + @NoArgsConstructor + public static class Despatch { + @XmlElement(name = "DespatchAddress", namespace = XMLConstants.CAC) + private DespatchAddress despatchAddress; + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "DespatchAdvice.DespatchAddress") + @Data + @NoArgsConstructor + public static class DespatchAddress { + @XmlElement(name = "ID", namespace = XMLConstants.CBC) + private String id; + + @XmlElement(name = "AddressTypeCode", namespace = XMLConstants.CBC) + private AddressTypeCode addressTypeCode; + + @XmlElement(name = "AddressLine", namespace = XMLConstants.CAC) + private AddressLine addressLine; } @XmlAccessorType(XmlAccessType.NONE) @@ -306,10 +397,25 @@ public static class DeliveryAddress { @XmlElement(name = "ID", namespace = XMLConstants.CBC) private String id; + @XmlElement(name = "AddressTypeCode", namespace = XMLConstants.CBC) + private AddressTypeCode addressTypeCode; + @XmlElement(name = "AddressLine", namespace = XMLConstants.CAC) private AddressLine addressLine; } + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "DespatchAdvice.AddressTypeCode") + @Data + @NoArgsConstructor + public static class AddressTypeCode { + @XmlValue + private String value; + + @XmlAttribute(name = "listID") + private String listID; + } + @XmlAccessorType(XmlAccessType.NONE) @XmlType(name = "DespatchAdvice.AddressLine") @Data @@ -325,7 +431,22 @@ public static class AddressLine { @NoArgsConstructor public static class TransportHandlingUnit { @XmlElement(name = "TransportEquipment", namespace = XMLConstants.CAC) - private TransportEquipment transportEquipment; + private List transportEquipments; + + @XmlElement(name = "Package", namespace = XMLConstants.CAC) + private List packages; + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "DespatchAdvice.Package") + @Data + @NoArgsConstructor + public static class Package { + @XmlElement(name = "ID", namespace = XMLConstants.CBC) + private String id; + + @XmlElement(name = "TraceID", namespace = XMLConstants.CBC) + private String traceID; } @XmlAccessorType(XmlAccessType.NONE) @@ -335,6 +456,33 @@ public static class TransportHandlingUnit { public static class TransportEquipment { @XmlElement(name = "ID", namespace = XMLConstants.CBC) private String id; + + @XmlElement(name = "ApplicableTransportMeans", namespace = XMLConstants.CAC) + private ApplicableTransportMeans applicableTransportMeans; + + @XmlElement(name = "AttachedTransportEquipment", namespace = XMLConstants.CAC) + private List attachedTransportEquipments; + + @XmlElement(name = "ShipmentDocumentReference", namespace = XMLConstants.CAC) + private List shipmentDocumentReferences; + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "DespatchAdvice.ApplicableTransportMeans") + @Data + @NoArgsConstructor + public static class ApplicableTransportMeans { + @XmlElement(name = "RegistrationNationalityID", namespace = XMLConstants.CBC) + private String registrationNationalityID; + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "DespatchAdvice.ShipmentDocumentReference") + @Data + @NoArgsConstructor + public static class ShipmentDocumentReference { + @XmlElement(name = "ID", namespace = XMLConstants.CBC) + private ID id; } @XmlAccessorType(XmlAccessType.NONE) @@ -356,5 +504,11 @@ public static class OriginAddress { public static class FirstArrivalPortLocation { @XmlElement(name = "ID", namespace = XMLConstants.CBC) private String id; + + @XmlElement(name = "LocationTypeCode", namespace = XMLConstants.CBC) + private String locationTypeCode; + + @XmlElement(name = "Name", namespace = XMLConstants.CBC) + private String name; } } diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/jaxb/models/XMLDespatchAdviceLine.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/jaxb/models/XMLDespatchAdviceLine.java index 5a5505ba..791e3409 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/jaxb/models/XMLDespatchAdviceLine.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/jaxb/models/XMLDespatchAdviceLine.java @@ -3,6 +3,8 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlAttribute; @@ -42,11 +44,32 @@ public static class Item { @XmlElement(name = "Name", namespace = XMLConstants.CBC) private String name; + @XmlElement(name = "Description", namespace = XMLConstants.CBC) + private String description; + @XmlElement(name = "SellersItemIdentification", namespace = XMLConstants.CAC) private SellersItemIdentification sellersItemIdentification; @XmlElement(name = "CommodityClassification", namespace = XMLConstants.CAC) private CommodityClassification commodityClassification; + + @XmlElement(name = "AdditionalItemProperty", namespace = XMLConstants.CAC) + private List additionalItemProperties; + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "DespatchAdviceLine.AdditionalItemProperty") + @Data + @NoArgsConstructor + public static class AdditionalItemProperty { + @XmlElement(name = "Name", namespace = XMLConstants.CBC) + private String name; + + @XmlElement(name = "NameCode", namespace = XMLConstants.CBC) + private String nameCode; + + @XmlElement(name = "Value", namespace = XMLConstants.CBC) + private String value; } @XmlAccessorType(XmlAccessType.NONE) diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/general/EmbededDespatch.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/general/EmbededDespatch.java new file mode 100644 index 00000000..e1910c63 --- /dev/null +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/general/EmbededDespatch.java @@ -0,0 +1,49 @@ +package io.github.project.openubl.xbuilder.content.models.standard.general; + +import io.github.project.openubl.xbuilder.content.models.common.Cliente; +import io.github.project.openubl.xbuilder.content.models.common.Direccion; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EmbededDespatch { + + @Schema(description = "Punto de llegada") + private Direccion llegada; + + @Schema(description = "Punto de partida") + private Direccion partida; + + @Schema(description = "Datos del transportista") + private Cliente transportista; + + @Schema(description = "Numero de licencia de conducir") + private String nroLicencia; + + @Schema(description = "Placa del vehiculo") + private String transpPlaca; + + @Schema(description = "Codigo de autorizacion del transporte") + private String transpCodeAuth; + + @Schema(description = "Marca del vehiculo") + private String transpMarca; + + @Schema(description = "Modalidad de traslado. Catalog 18") + private String modTraslado; + + @Schema(description = "Peso bruto total") + private BigDecimal pesoBruto; + + @Schema(description = "Unidad de medida del peso bruto") + private String undPesoBruto; + +} diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/general/Invoice.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/general/Invoice.java index 248d59da..63257183 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/general/Invoice.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/general/Invoice.java @@ -45,6 +45,11 @@ public class Invoice extends SalesDocument { private Detraccion detraccion; private Percepcion percepcion; + /** + * Guia de remision embebida (Factura Guia) + */ + private EmbededDespatch guiaEmbebida; + /** * Anticipos asociados al comprobante */ diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/general/SalesDocument.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/general/SalesDocument.java index 6a45f35c..3d2d6581 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/general/SalesDocument.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/general/SalesDocument.java @@ -99,4 +99,10 @@ public abstract class SalesDocument extends Document { @Singular @ArraySchema private List documentosRelacionados; + + /** + * Cargos globales del documento + */ + @Singular + private List cargos; } diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Comprador.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Comprador.java new file mode 100644 index 00000000..47c0f2e7 --- /dev/null +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Comprador.java @@ -0,0 +1,36 @@ +package io.github.project.openubl.xbuilder.content.models.standard.guia; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Modelo para el comprador en la guía de remisión. + * Representa al adquiriente de los bienes cuando aplica. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Comprador { + + /** + * Tipo de documento de identidad (Catálogo 06) + */ + @Schema(description = "Catalogo 06", requiredMode = Schema.RequiredMode.REQUIRED) + private String tipoDocumentoIdentidad; + + /** + * Número de documento de identidad + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String numeroDocumentoIdentidad; + + /** + * Razón social o nombre + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String nombre; +} diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/DespatchAdvice.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/DespatchAdvice.java index 4b9e7be5..9337c08a 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/DespatchAdvice.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/DespatchAdvice.java @@ -21,6 +21,11 @@ @NoArgsConstructor @AllArgsConstructor public class DespatchAdvice { + /** + * Versión del formato de la guía de remisión (ejemplo: 2.0) + */ + private String version; + /** * Serie del comprobante */ @@ -56,6 +61,13 @@ public class DespatchAdvice { @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED) private DocumentoRelacionado documentoRelacionado; + /** + * Documentos adicionales relacionados al transporte (Catálogo 61) + */ + @Singular("documentoAdicional") + @Schema(description = "Documentos adicionales relacionados al transporte") + private List documentosAdicionales; + @Schema(description = "Persona que firma electrónicamente el comprobante. Si NULL los datos del proveedor son usados.") private Firmante firmante; @@ -68,6 +80,18 @@ public class DespatchAdvice { @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED) private Proveedor proveedor; + /** + * Datos del tercero (vendedor de los bienes cuando aplica) + */ + @Schema(description = "Tercero/Vendedor de los bienes") + private Tercero tercero; + + /** + * Datos del comprador (adquiriente de los bienes) + */ + @Schema(description = "Comprador/Adquiriente de los bienes") + private Comprador comprador; + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private Envio envio; diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/DespatchAdviceItem.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/DespatchAdviceItem.java index d8ca3ae5..6184d717 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/DespatchAdviceItem.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/DespatchAdviceItem.java @@ -4,9 +4,11 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.Singular; import lombok.extern.jackson.Jacksonized; import java.math.BigDecimal; +import java.util.List; @Jacksonized @Data @@ -20,4 +22,7 @@ public class DespatchAdviceItem { private String descripcion; private String codigo; private String codigoSunat; + + @Singular + private List atributos; } diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Destino.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Destino.java index 93d531de..1093c9d0 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Destino.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Destino.java @@ -1,5 +1,6 @@ package io.github.project.openubl.xbuilder.content.models.standard.guia; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -10,6 +11,22 @@ @NoArgsConstructor @AllArgsConstructor public class Destino { + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private String ubigeo; + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private String direccion; + + /** + * Código de establecimiento del punto de llegada + */ + @Schema(description = "Código de local anexo de llegada") + private String codigoLocal; + + /** + * RUC asociado al punto de llegada + */ + @Schema(description = "RUC asociado al punto de llegada") + private String ruc; } diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/DocumentoAdicional.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/DocumentoAdicional.java new file mode 100644 index 00000000..310cd139 --- /dev/null +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/DocumentoAdicional.java @@ -0,0 +1,43 @@ +package io.github.project.openubl.xbuilder.content.models.standard.guia; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Modelo para documentos adicionales relacionados al transporte. + * Catálogo 61 de SUNAT. + * Basado en el modelo AdditionalDoc de greenter. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DocumentoAdicional { + + /** + * Código del tipo de documento (Catálogo 61) + */ + @Schema(description = "Catalogo 61", requiredMode = Schema.RequiredMode.REQUIRED) + private String tipoDocumento; + + /** + * Descripción del tipo de documento + */ + @Schema(description = "Descripción del tipo de documento") + private String tipoDocumentoDescripcion; + + /** + * Número del documento + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String numero; + + /** + * RUC del emisor del documento + */ + @Schema(description = "RUC del emisor del documento") + private String rucEmisor; +} diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Driver.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Driver.java new file mode 100644 index 00000000..83e5902e --- /dev/null +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Driver.java @@ -0,0 +1,54 @@ +package io.github.project.openubl.xbuilder.content.models.standard.guia; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Modelo para conductor/chofer de la guía de remisión. + * Basado en el modelo Driver de greenter. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Driver { + + /** + * Tipo de conductor: "Principal" o "Secundario" + */ + @Schema(description = "Tipo de conductor: Principal o Secundario") + private String tipo; + + /** + * Tipo de documento de identidad del chofer (Catálogo 06) + */ + @Schema(description = "Catalogo 06", requiredMode = Schema.RequiredMode.REQUIRED) + private String tipoDocumentoIdentidad; + + /** + * Número de documento de identidad del chofer + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String numeroDocumentoIdentidad; + + /** + * Nombres del conductor + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String nombres; + + /** + * Apellidos del conductor + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String apellidos; + + /** + * Número de licencia de conducir + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String licencia; +} diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Envio.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Envio.java index 49c1a724..5dcf868b 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Envio.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Envio.java @@ -5,9 +5,11 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.Singular; import java.math.BigDecimal; import java.time.LocalDate; +import java.util.List; @Data @Builder @@ -17,6 +19,7 @@ public class Envio { @Schema(description = "Catalog 20", requiredMode = Schema.RequiredMode.REQUIRED) private String tipoTraslado; + private String motivoTraslado; @Schema(requiredMode = Schema.RequiredMode.REQUIRED) @@ -25,8 +28,19 @@ public class Envio { @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private String pesoTotalUnidadMedida; + /** + * Peso de los ítems seleccionados (en KGM) + */ + @Schema(description = "Peso bruto de los items seleccionados") + private BigDecimal pesoItems; + + /** + * Sustento de la diferencia del peso bruto total respecto al peso de los ítems + */ + @Schema(description = "Sustento de diferencia de peso") + private String sustentoPeso; + private Integer numeroDeBultos; - private boolean transbordoProgramado; @Schema(description = "Catalog 18", requiredMode = Schema.RequiredMode.REQUIRED) private String tipoModalidadTraslado; @@ -34,12 +48,48 @@ public class Envio { @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private LocalDate fechaTraslado; - private String numeroDeContenedor; - private String codigoDePuerto; + /** + * Lista de contenedores/precintos + */ + @Singular("contenedor") + @Schema(description = "Lista de contenedores o precintos") + private List contenedores; + + /** + * Puerto de embarque/desembarque + */ + @Schema(description = "Puerto de embarque/desembarque (Catalogo 63)") + private Puerto puerto; + + /** + * Aeropuerto de embarque/desembarque + */ + @Schema(description = "Aeropuerto de embarque/desembarque (Catalogo 64)") + private Puerto aeropuerto; @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED) private Transportista transportista; + /** + * Lista de conductores (principal y secundarios) + */ + @Singular("chofer") + @Schema(description = "Lista de conductores") + private List choferes; + + /** + * Vehículo principal con posibles vehículos secundarios + */ + @Schema(description = "Vehículo de transporte") + private Vehicle vehiculo; + + /** + * Indicadores especiales de transporte (SUNAT_Envio_*) + */ + @Singular("indicador") + @Schema(description = "Indicadores especiales de transporte") + private List indicadores; + @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED) private Partida partida; diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/GuiaItemAttribute.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/GuiaItemAttribute.java new file mode 100644 index 00000000..d107dbee --- /dev/null +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/GuiaItemAttribute.java @@ -0,0 +1,18 @@ +package io.github.project.openubl.xbuilder.content.models.standard.guia; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.jackson.Jacksonized; + +@Jacksonized +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class GuiaItemAttribute { + private String code; + private String name; + private String value; +} diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Partida.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Partida.java index 623ed9f1..fbff322b 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Partida.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Partida.java @@ -1,5 +1,6 @@ package io.github.project.openubl.xbuilder.content.models.standard.guia; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -10,6 +11,22 @@ @NoArgsConstructor @AllArgsConstructor public class Partida { + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private String ubigeo; + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private String direccion; + + /** + * Código de establecimiento del punto de partida + */ + @Schema(description = "Código de local anexo de partida") + private String codigoLocal; + + /** + * RUC asociado al punto de partida + */ + @Schema(description = "RUC asociado al punto de partida") + private String ruc; } diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Puerto.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Puerto.java new file mode 100644 index 00000000..cb4e26ca --- /dev/null +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Puerto.java @@ -0,0 +1,30 @@ +package io.github.project.openubl.xbuilder.content.models.standard.guia; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Modelo para puerto o aeropuerto de embarque/desembarque. + * Basado en el modelo Puerto de greenter. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Puerto { + + /** + * Código del puerto (Catálogo 63) o aeropuerto (Catálogo 64) + */ + @Schema(description = "Código del puerto (Cat. 63) o aeropuerto (Cat. 64)", requiredMode = Schema.RequiredMode.REQUIRED) + private String codigo; + + /** + * Nombre del puerto o aeropuerto + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String nombre; +} diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Remitente.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Remitente.java index a2705777..dd2fc4c5 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Remitente.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Remitente.java @@ -17,4 +17,7 @@ public class Remitente { @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private String razonSocial; + + @Schema(description = "Número de registro del Ministerio de Transportes y Comunicaciones") + private String numeroRegistroMTC; } diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Tercero.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Tercero.java new file mode 100644 index 00000000..8c75d6a6 --- /dev/null +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Tercero.java @@ -0,0 +1,36 @@ +package io.github.project.openubl.xbuilder.content.models.standard.guia; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Modelo para tercero/proveedor en la guía de remisión. + * Representa al vendedor de los bienes cuando aplica. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Tercero { + + /** + * Tipo de documento de identidad (Catálogo 06) + */ + @Schema(description = "Catalogo 06", requiredMode = Schema.RequiredMode.REQUIRED) + private String tipoDocumentoIdentidad; + + /** + * Número de documento de identidad + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String numeroDocumentoIdentidad; + + /** + * Razón social o nombre + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String nombre; +} diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Transportista.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Transportista.java index fe02e9c7..4413f90a 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Transportista.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Transportista.java @@ -21,13 +21,10 @@ public class Transportista { @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private String nombre; - @Schema(requiredMode = Schema.RequiredMode.REQUIRED) - private String placaDelVehiculo; - - @Schema(description = "Catalogo 06", requiredMode = Schema.RequiredMode.REQUIRED) - private String choferTipoDocumentoIdentidad; - - @Schema(requiredMode = Schema.RequiredMode.REQUIRED) - private String choferNumeroDocumentoIdentidad; + /** + * Número de registro del Ministerio de Transportes y Comunicaciones + */ + @Schema(description = "Número de registro MTC") + private String numeroRegistroMTC; } diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Vehicle.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Vehicle.java new file mode 100644 index 00000000..f1c381eb --- /dev/null +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/standard/guia/Vehicle.java @@ -0,0 +1,53 @@ +package io.github.project.openubl.xbuilder.content.models.standard.guia; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.Singular; + +import java.util.List; + +/** + * Modelo para vehículo de transporte de la guía de remisión. + * Soporta vehículo principal y vehículos secundarios (carreta, etc.). + * Basado en el modelo Vehicle de greenter. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Vehicle { + + /** + * Número de placa del vehículo + */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String placa; + + /** + * Número de tarjeta de circulación (TUC) + */ + @Schema(description = "Número de tarjeta única de circulación") + private String numeroCirculacion; + + /** + * Número de autorización o certificado de habilitación vehicular + */ + @Schema(description = "Número de autorización o certificado de habilitación") + private String numeroAutorizacion; + + /** + * Código de la entidad emisora de la autorización + */ + @Schema(description = "Código de entidad autorizadora") + private String codigoEmisor; + + /** + * Lista de vehículos secundarios (carretas, semirremolques, etc.) + */ + @Singular + @Schema(description = "Vehículos secundarios adjuntos") + private List secundarios; +} diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/baja/Reversion.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/baja/Reversion.java new file mode 100644 index 00000000..57fa796c --- /dev/null +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/baja/Reversion.java @@ -0,0 +1,17 @@ +package io.github.project.openubl.xbuilder.content.models.sunat.baja; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; + +@Jacksonized +@Data +@SuperBuilder +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class Reversion extends VoidedDocuments { +} diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/resumen/Comprobante.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/resumen/Comprobante.java index ab66d667..42f828a2 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/resumen/Comprobante.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/resumen/Comprobante.java @@ -15,7 +15,7 @@ public class Comprobante { @Schema(requiredMode = Schema.RequiredMode.AUTO, description = "Moneda del comprobante declarado") private String moneda; - + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Catalogo 01") private String tipoComprobante; @@ -31,5 +31,7 @@ public class Comprobante { @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private ComprobanteImpuestos impuestos; + private SummaryPerception percepcion; + private ComprobanteAfectado comprobanteAfectado; } diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/resumen/ComprobanteImpuestos.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/resumen/ComprobanteImpuestos.java index 80fe384e..49772424 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/resumen/ComprobanteImpuestos.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/resumen/ComprobanteImpuestos.java @@ -19,4 +19,13 @@ public class ComprobanteImpuestos { @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "ICB del comprobante") private BigDecimal icb; + + @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "ISC del comprobante") + private BigDecimal isc; + + @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "IVAP del comprobante") + private BigDecimal ivap; + + @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Otros tributos del comprobante") + private BigDecimal otros; } diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/resumen/SummaryPerception.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/resumen/SummaryPerception.java new file mode 100644 index 00000000..c8fafc33 --- /dev/null +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/content/models/sunat/resumen/SummaryPerception.java @@ -0,0 +1,31 @@ +package io.github.project.openubl.xbuilder.content.models.sunat.resumen; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SummaryPerception { + + @Schema(description = "Codigo de regimen de percepcion. Catalogo 22") + private String codReg; + + @Schema(description = "Tasa de percepcion") + private BigDecimal tasa; + + @Schema(description = "Monto base de percepcion") + private BigDecimal mtoBase; + + @Schema(description = "Monto de percepcion") + private BigDecimal mto; + + @Schema(description = "Monto total de percepcion") + private BigDecimal mtoTotal; +} diff --git a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/renderer/TemplateProducer.java b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/renderer/TemplateProducer.java index 05c38ff9..38ab3e91 100644 --- a/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/renderer/TemplateProducer.java +++ b/xbuilder/core/src/main/java/io/github/project/openubl/xbuilder/renderer/TemplateProducer.java @@ -36,6 +36,10 @@ public Template getDespatchAdvice() { return EngineProducer.getInstance().getEngine().getTemplate("Renderer/despatchAdvice.xml"); } + public Template getReversion() { + return EngineProducer.getInstance().getEngine().getTemplate("Renderer/reversion.xml"); + } + private static class TemplateProducerHolder { private static final TemplateProducer INSTANCE = new TemplateProducer(); diff --git a/xbuilder/core/src/main/resources/templates/Renderer/despatchAdvice.xml b/xbuilder/core/src/main/resources/templates/Renderer/despatchAdvice.xml index 476a8283..03ae1b94 100644 --- a/xbuilder/core/src/main/resources/templates/Renderer/despatchAdvice.xml +++ b/xbuilder/core/src/main/resources/templates/Renderer/despatchAdvice.xml @@ -7,7 +7,7 @@ > {#include ubl/standard/include/ubl-extensions.xml /} {#include ubl/standard/include/general-data.xml item=this /} - {tipoComprobante} + {tipoComprobante} {#if observaciones} {/if} @@ -23,6 +23,22 @@ {documentoRelacionado.tipoDocumento} {/if} + {#each documentosAdicionales.orEmpty} + + {it.numero} + {it.tipoDocumento} + {#if it.tipoDocumentoDescripcion} + {it.tipoDocumentoDescripcion} + {/if} + {#if it.rucEmisor} + + + {it.rucEmisor} + + + {/if} + + {/each} {#include ubl/common/signature.xml firmante=this.firmante /} {remitente.ruc} @@ -32,6 +48,9 @@ + {#if remitente.numeroRegistroMTC} + {remitente.numeroRegistroMTC} + {/if} @@ -45,7 +64,30 @@ - {#if proveedor} + {#if comprador} + + + + {comprador.numeroDocumentoIdentidad} + + + + + + + {/if} + {#if tercero} + + + + {tercero.numeroDocumentoIdentidad} + + + + + + + {#else if proveedor} {proveedor.ruc} @@ -56,16 +98,24 @@ {/if} - 1 + SUNAT_Envio {envio.tipoTraslado} {#if envio.motivoTraslado} - {envio.motivoTraslado} + {envio.motivoTraslado} + {/if} + {#if envio.sustentoPeso} + {envio.sustentoPeso} {/if} {envio.pesoTotal.scale(3)} + {#if envio.pesoItems} + {envio.pesoItems.scale(3)} + {/if} {#if envio.numeroDeBultos} {envio.numeroDeBultos} {/if} - {envio.transbordoProgramado} + {#each envio.indicadores.orEmpty} + {it} + {/each} {envio.tipoModalidadTraslado} @@ -76,55 +126,124 @@ {envio.transportista.numeroDocumentoIdentidad} - - - + + + {#if envio.transportista.numeroRegistroMTC} + {envio.transportista.numeroRegistroMTC} + {/if} + - - - {envio.transportista.placaDelVehiculo} - - + {/if} + {#each envio.choferes.orEmpty} - {envio.transportista.choferNumeroDocumentoIdentidad} + {it.numeroDocumentoIdentidad} + {#if it.nombres} + {it.nombres} + {/if} + {#if it.apellidos} + {it.apellidos} + {/if} + {#if it.tipo} + {it.tipo} + {/if} + {#if it.licencia} + + {it.licencia} + + {/if} - {/if} + {/each} + {#if envio.destino} {envio.destino.ubigeo} + {#if envio.destino.codigoLocal} + {envio.destino.codigoLocal} + {/if} {envio.destino.direccion} + {/if} + {#if envio.partida} + + + {envio.partida.ubigeo} + {#if envio.partida.codigoLocal} + {envio.partida.codigoLocal} + {/if} + + {envio.partida.direccion} + + + + {/if} - {#if envio.numeroDeContenedor} + {#each envio.contenedores.orEmpty} + + + {it_index.add(1)} + {it} + + + {/each} + {#if envio.vehiculo} - {envio.numeroDeContenedor} + {envio.vehiculo.placa} + {#if envio.vehiculo.numeroCirculacion} + + {envio.vehiculo.numeroCirculacion} + + {/if} + {#each envio.vehiculo.secundarios.orEmpty} + + {it.placa} + {#if it.numeroCirculacion} + + {it.numeroCirculacion} + + {/if} + {#if it.numeroAutorizacion} + + {it.numeroAutorizacion} + + {/if} + + {/each} + {#if envio.vehiculo.numeroAutorizacion} + + {envio.vehiculo.numeroAutorizacion} + + {/if} {/if} - - {envio.partida.ubigeo} - {envio.partida.direccion} - - {#if envio.codigoDePuerto} + {#if envio.puerto} + + {envio.puerto.codigo} + 1 + {envio.puerto.nombre} + + {#else if envio.aeropuerto} - {{ envio.codigoDePuerto }} + {envio.aeropuerto.codigo} + 2 + {envio.aeropuerto.nombre} {/if} {#each detalles.orEmpty} {it_index.add(1)} - {it.cantidad} + {it.cantidad.scale(2)} {it_index.add(1)} {#if it.descripcion} - + {/if} {it.codigo} @@ -134,6 +253,15 @@ {it.codigoSunat} {/if} + {#each it.atributos.orEmpty} + + {it.name} + {it.code} + {#if it.value} + {it.value} + {/if} + + {/each} {/each} diff --git a/xbuilder/core/src/main/resources/templates/Renderer/invoice.xml b/xbuilder/core/src/main/resources/templates/Renderer/invoice.xml index 7ad5e75e..5c8a16a6 100644 --- a/xbuilder/core/src/main/resources/templates/Renderer/invoice.xml +++ b/xbuilder/core/src/main/resources/templates/Renderer/invoice.xml @@ -48,6 +48,74 @@ {/if} + {#if guiaEmbebida} + + + + {guiaEmbebida.llegada.ubigueo} + {guiaEmbebida.llegada.direccion} + {#if guiaEmbebida.llegada.urbanizacion} + {guiaEmbebida.llegada.urbanizacion} + {/if} + {guiaEmbebida.llegada.provincia} + {guiaEmbebida.llegada.departamento} + {guiaEmbebida.llegada.distrito} + + {guiaEmbebida.llegada.codigoPais} + + + + + + {guiaEmbebida.partida.ubigueo} + {guiaEmbebida.partida.direccion} + {#if guiaEmbebida.partida.urbanizacion} + {guiaEmbebida.partida.urbanizacion} + {/if} + {guiaEmbebida.partida.provincia} + {guiaEmbebida.partida.departamento} + {guiaEmbebida.partida.distrito} + + {guiaEmbebida.partida.codigoPais} + + + + + {guiaEmbebida.pesoBruto} + + {guiaEmbebida.modTraslado} + + {fechaEmision} + + + + {guiaEmbebida.transportista.numeroDocumentoIdentidad} + + + + + + + {guiaEmbebida.nroLicencia} + + + + + {guiaEmbebida.llegada.ubigueo} + {guiaEmbebida.llegada.direccion} + + {guiaEmbebida.llegada.codigoPais} + + + + + + {guiaEmbebida.transpPlaca} + + + + + {/if} {#if detraccion} Detraccion @@ -85,6 +153,15 @@ {it.monto} {/each} + {#each cargos.orEmpty} + + true + {it.tipo} + {it.porcentaje.scale(2)} + {it.monto.scale(2)} + {it.monto.scale(2)} + + {/each} {#each descuentos.orEmpty} false diff --git a/xbuilder/core/src/main/resources/templates/Renderer/reversion.xml b/xbuilder/core/src/main/resources/templates/Renderer/reversion.xml new file mode 100644 index 00000000..a9c62caa --- /dev/null +++ b/xbuilder/core/src/main/resources/templates/Renderer/reversion.xml @@ -0,0 +1,26 @@ + + + {#include ubl/standard/include/ubl-extensions.xml /} + 2.0 + 1.0 + RR-{fechaEmision.format('yyyyMMdd')}-{numero} + {fechaEmisionComprobantes} + {fechaEmision} + {#include ubl/common/signature.xml firmante=this.firmante /} + {#include ubl/sunat/include/supplier.xml proveedor=this.proveedor /} + {#each comprobantes.orEmpty} + + {it_index.add(1)} + {it.tipoComprobante} + {it.serie} + {it.numero} + {it.descripcionSustento} + + {/each} + diff --git a/xbuilder/core/src/main/resources/templates/Renderer/summaryDocuments.xml b/xbuilder/core/src/main/resources/templates/Renderer/summaryDocuments.xml index 7f1b4a10..979271a2 100644 --- a/xbuilder/core/src/main/resources/templates/Renderer/summaryDocuments.xml +++ b/xbuilder/core/src/main/resources/templates/Renderer/summaryDocuments.xml @@ -38,6 +38,15 @@ {/if} + {#if it.comprobante.percepcion} + + {it.comprobante.percepcion.codReg} + {it.comprobante.percepcion.tasa.scale(2)} + {it.comprobante.percepcion.mto.scale(2)} + {it.comprobante.percepcion.mtoTotal.scale(2)} + {it.comprobante.percepcion.mtoBase.scale(2)} + + {/if} {it.tipoOperacion} @@ -100,6 +109,51 @@ {/if} + {#if it.comprobante.impuestos.isc} + + {it.comprobante.impuestos.isc} + + {it.comprobante.impuestos.isc} + + + 2000 + ISC + EXC + + + + + {/if} + {#if it.comprobante.impuestos.ivap} + + {it.comprobante.impuestos.ivap} + + {it.comprobante.impuestos.ivap} + + + 1016 + IVAP + VAT + + + + + {/if} + {#if it.comprobante.impuestos.otros} + + {it.comprobante.impuestos.otros} + + {it.comprobante.impuestos.otros} + + + 9999 + OTROS + OTH + + + + + {/if} {/each} diff --git a/xbuilder/core/src/main/resources/templates/ubl/standard/include/general-data.xml b/xbuilder/core/src/main/resources/templates/ubl/standard/include/general-data.xml index 3ab314c0..99ff4825 100644 --- a/xbuilder/core/src/main/resources/templates/ubl/standard/include/general-data.xml +++ b/xbuilder/core/src/main/resources/templates/ubl/standard/include/general-data.xml @@ -1,5 +1,5 @@ 2.1 - 2.0 + {item.version ?: '2.0'} {item.serie}-{item.numero} {item.fechaEmision} {#if item.horaEmision} diff --git a/xbuilder/core/src/test/java/e2e/AbstractTest.java b/xbuilder/core/src/test/java/e2e/AbstractTest.java index ec62ade5..495dbf6f 100644 --- a/xbuilder/core/src/test/java/e2e/AbstractTest.java +++ b/xbuilder/core/src/test/java/e2e/AbstractTest.java @@ -28,6 +28,7 @@ import io.github.project.openubl.xbuilder.content.models.standard.general.Invoice; import io.github.project.openubl.xbuilder.content.models.standard.guia.DespatchAdvice; import io.github.project.openubl.xbuilder.content.models.sunat.baja.VoidedDocuments; +import io.github.project.openubl.xbuilder.content.models.sunat.baja.Reversion; import io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.Perception; import io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.Retention; import io.github.project.openubl.xbuilder.content.models.sunat.resumen.SummaryDocuments; @@ -285,4 +286,28 @@ protected void assertInput(DespatchAdvice input, String snapshotFilename) throws writeYaml("DespatchAdvice", input, snapshotFilename); } + + protected void assertInputReversion(Reversion input, String snapshotFilename) throws Exception { + ContentEnricher enricher = new ContentEnricher(defaults, dateProvider); + enricher.enrich(input); + + // When + Template template = TemplateProducer.getInstance().getReversion(); + String xml = template.data(input).render(); + + String reconstructedXml; + try (StringReader reader = new StringReader(xml);) { + XMLVoidedDocuments xmlPojo = (XMLVoidedDocuments) JAXBContext.newInstance(XMLVoidedDocuments.class) + .createUnmarshaller() + .unmarshal(new InputSource(reader)); + VoidedDocuments inputFromXml = voidedDocumentsMapper.map(xmlPojo); + reconstructedXml = TemplateProducer.getInstance().getReversion().data(inputFromXml).render(); + } + + // Then + XMLAssertUtils.assertSnapshot(xml, reconstructedXml, getClass(), snapshotFilename); + XMLAssertUtils.assertSendSunat(xml, XMLAssertUtils.VOIDED_DOCUMENTS_XSD); + + writeYaml("Reversion", input, snapshotFilename); + } } diff --git a/xbuilder/core/src/test/java/e2e/renderer/despatchadvice/DespatchAdviceCarrierTest.java b/xbuilder/core/src/test/java/e2e/renderer/despatchadvice/DespatchAdviceCarrierTest.java new file mode 100644 index 00000000..4dfd049c --- /dev/null +++ b/xbuilder/core/src/test/java/e2e/renderer/despatchadvice/DespatchAdviceCarrierTest.java @@ -0,0 +1,81 @@ +package e2e.renderer.despatchadvice; + +import e2e.AbstractTest; +import io.github.project.openubl.xbuilder.content.catalogs.Catalog1; +import io.github.project.openubl.xbuilder.content.catalogs.Catalog6; +import io.github.project.openubl.xbuilder.content.catalogs.Catalog18; +import io.github.project.openubl.xbuilder.content.catalogs.Catalog20; +import io.github.project.openubl.xbuilder.content.models.standard.guia.*; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +public class DespatchAdviceCarrierTest extends AbstractTest { + + @Test + public void testCarrierData() throws Exception { + // Given + DespatchAdvice input = DespatchAdvice.builder() + .serie("V001") + .numero(1) + .version("2.1") + .tipoComprobante(Catalog1.GUIA_REMISION_TRANSPORTISTA.getCode()) // 31 + .remitente(Remitente.builder() // En una 31, el remitente es el Transportista emitente + .ruc("20123456789") + .razonSocial("Transportes Veloz S.A.C.") + .numeroRegistroMTC("MTC-654321") + .build() + ) + .destinatario(Destinatario.builder() + .tipoDocumentoIdentidad(Catalog6.RUC.getCode()) + .numeroDocumentoIdentidad("20876543210") + .nombre("Cliente Final S.A.") + .build() + ) + .tercero(Tercero.builder() // El Remitente original (el que solicita el transporte) + .tipoDocumentoIdentidad(Catalog6.RUC.getCode()) + .numeroDocumentoIdentidad("20555555555") + .nombre("Empresa Vendedora S.A.C.") + .build() + ) + .envio(Envio.builder() + .tipoTraslado(Catalog20.VENTA.getCode()) + .pesoTotal(new BigDecimal("500.00")) + .pesoTotalUnidadMedida("KGM") + .tipoModalidadTraslado(Catalog18.TRANSPORTE_PUBLICO.getCode()) + .fechaTraslado(dateProvider.now()) + .chofer(Driver.builder() + .tipoDocumentoIdentidad(Catalog6.DNI.getCode()) + .numeroDocumentoIdentidad("44444444") + .nombres("Carlos") + .apellidos("Guerrero") + .licencia("L5555555") + .build()) + .vehiculo(Vehicle.builder() + .placa("XYZ-789") + .numeroCirculacion("TUC-V001") + .build()) + .partida(Partida.builder() + .direccion("Almacen Principal") + .ubigeo("150101") + .build()) + .destino(Destino.builder() + .direccion("Tienda Centro") + .ubigeo("150102") + .build() + ) + .build() + ) + .detalle(DespatchAdviceItem.builder() + .cantidad(new BigDecimal("10.00")) + .unidadMedida("NIU") + .codigo("PROD-99") + .descripcion("Mercaderia variada") + .build() + ) + .build(); + + // When/Then + assertInput(input, "carrierData.xml"); + } +} diff --git a/xbuilder/core/src/test/java/e2e/renderer/despatchadvice/DespatchAdviceComplexTest.java b/xbuilder/core/src/test/java/e2e/renderer/despatchadvice/DespatchAdviceComplexTest.java new file mode 100644 index 00000000..d8bfb9b7 --- /dev/null +++ b/xbuilder/core/src/test/java/e2e/renderer/despatchadvice/DespatchAdviceComplexTest.java @@ -0,0 +1,130 @@ +package e2e.renderer.despatchadvice; + +import e2e.AbstractTest; +import io.github.project.openubl.xbuilder.content.catalogs.Catalog1; +import io.github.project.openubl.xbuilder.content.catalogs.Catalog18; +import io.github.project.openubl.xbuilder.content.catalogs.Catalog20; +import io.github.project.openubl.xbuilder.content.catalogs.Catalog6; +import io.github.project.openubl.xbuilder.content.models.standard.guia.*; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +public class DespatchAdviceComplexTest extends AbstractTest { + + @Test + public void testComplexData() throws Exception { + // Given + DespatchAdvice input = DespatchAdvice.builder() + .serie("T001") + .numero(100) + .version("2.1") + .tipoComprobante(Catalog1.GUIA_REMISION_REMITENTE.getCode()) + .remitente(Remitente.builder() + .ruc("12345678912") + .razonSocial("Softgreen S.A.C.") + .build() + ) + .destinatario(Destinatario.builder() + .tipoDocumentoIdentidad(Catalog6.DNI.getCode()) + .numeroDocumentoIdentidad("12345678") + .nombre("Mi Cliente S.A.C.") + .build() + ) + .comprador(Comprador.builder() + .tipoDocumentoIdentidad(Catalog6.DNI.getCode()) + .numeroDocumentoIdentidad("12345678") + .nombre("Mi Cliente S.A.C.") + .build() + ) + .documentoAdicional(DocumentoAdicional.builder() + .tipoDocumento("09") + .numero("T001-1") + .rucEmisor("20100010001") + .tipoDocumentoDescripcion("GUIA REMISION") + .build() + ) + .envio(Envio.builder() + .tipoTraslado(Catalog20.VENTA.getCode()) + .pesoTotal(new BigDecimal("100.50")) + .pesoTotalUnidadMedida("KGM") + .pesoItems(new BigDecimal("90.00")) + .sustentoPeso("Empaque madera") + .numeroDeBultos(10) + .tipoModalidadTraslado(Catalog18.TRANSPORTE_PUBLICO.getCode()) + .fechaTraslado(dateProvider.now()) + .chofer(Driver.builder() + .tipoDocumentoIdentidad(Catalog6.DNI.getCode()) + .numeroDocumentoIdentidad("11111111") + .nombres("Juan") + .apellidos("Perez") + .licencia("Q1234567") + .tipo("CONDUCTOR_PRINCIPAL") + .build()) + .chofer(Driver.builder() + .tipoDocumentoIdentidad(Catalog6.DNI.getCode()) + .numeroDocumentoIdentidad("22222222") + .nombres("Jose") + .apellidos("Gomez") + .tipo("COPILOTO") + .build()) + .contenedor("CONT-001") + .contenedor("CONT-002") + .vehiculo(Vehicle.builder() + .placa("ABC-123") + .numeroCirculacion("TUC-001") + .numeroAutorizacion("AUTH-001") + .codigoEmisor("MTC") + .secundario(Vehicle.builder() + .placa("CAR-456") + .numeroCirculacion("TUC-SEC") + .build() + ) + .build() + ) + .puerto(Puerto.builder() + .codigo("CALLAO") + .nombre("Puerto del Callao") + .build() + ) + .partida(Partida.builder() + .direccion("Av. Origen 123") + .ubigeo("010101") + .codigoLocal("0001") + .ruc("12345678912") + .build() + ) + .destino(Destino.builder() + .direccion("Av. Destino 456") + .ubigeo("020202") + .codigoLocal("0002") + .ruc("87654321098") + .build() + ) + .build() + ) + .detalle(DespatchAdviceItem.builder() + .cantidad(new BigDecimal("5.00")) + .unidadMedida("NIU") + .codigo("ITEM-01") + .descripcion("Caja de herramientas") + .atributo(GuiaItemAttribute.builder() + .name("Color") + .code("1001") + .value("Rojo") + .build() + ) + .atributo(GuiaItemAttribute.builder() + .name("Marca") + .code("1002") + .value("ToolMaster") + .build() + ) + .build() + ) + .build(); + + assertInput(input, "complexData.xml"); + } + +} diff --git a/xbuilder/core/src/test/java/e2e/renderer/despatchadvice/DespatchAdviceTest.java b/xbuilder/core/src/test/java/e2e/renderer/despatchadvice/DespatchAdviceTest.java index 2d019efd..413acbcf 100644 --- a/xbuilder/core/src/test/java/e2e/renderer/despatchadvice/DespatchAdviceTest.java +++ b/xbuilder/core/src/test/java/e2e/renderer/despatchadvice/DespatchAdviceTest.java @@ -40,7 +40,6 @@ public void testBasicMinData() throws Exception { .tipoTraslado(Catalog20.TRASLADO_EMISOR_ITINERANTE_CP.getCode()) .pesoTotal(BigDecimal.ONE) .pesoTotalUnidadMedida("KG") - .transbordoProgramado(false) .tipoModalidadTraslado(Catalog18.TRANSPORTE_PRIVADO.getCode()) .fechaTraslado(dateProvider.now()) .partida(Partida.builder() diff --git a/xbuilder/core/src/test/java/e2e/renderer/reversion/ReversionTest.java b/xbuilder/core/src/test/java/e2e/renderer/reversion/ReversionTest.java new file mode 100644 index 00000000..6625a0c6 --- /dev/null +++ b/xbuilder/core/src/test/java/e2e/renderer/reversion/ReversionTest.java @@ -0,0 +1,72 @@ +package e2e.renderer.reversion; + +import e2e.AbstractTest; +import io.github.project.openubl.xbuilder.content.catalogs.Catalog1; +import io.github.project.openubl.xbuilder.content.models.common.Proveedor; +import io.github.project.openubl.xbuilder.content.models.sunat.baja.Reversion; +import io.github.project.openubl.xbuilder.content.models.sunat.baja.VoidedDocumentsItem; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +/** + * Test for Reversion (Comunicacion de Baja de Retenciones/Percepciones). + * Reversion uses prefix "RR-" instead of "RA-" for the document ID. + */ +public class ReversionTest extends AbstractTest { + + @Test + public void testReversionWithPerceptions() throws Exception { + // Given - Reverting perceptions (type 40) + Reversion input = Reversion.builder() + .numero(1) + .fechaEmision(LocalDate.of(2022, 01, 31)) + .fechaEmisionComprobantes(LocalDate.of(2022, 01, 29)) + .proveedor(Proveedor.builder() + .ruc("12345678912") + .razonSocial("Softgreen S.A.C.") + .build() + ) + .comprobante(VoidedDocumentsItem.builder() + .serie("P001") + .numero(1) + .tipoComprobante(Catalog1.PERCEPCION.getCode()) + .descripcionSustento("Anulacion de percepcion por error en emision") + .build() + ) + .comprobante(VoidedDocumentsItem.builder() + .serie("P001") + .numero(2) + .tipoComprobante(Catalog1.PERCEPCION.getCode()) + .descripcionSustento("Anulacion de percepcion por duplicado") + .build() + ) + .build(); + + assertInputReversion(input, "reversion.xml"); + } + + @Test + public void testReversionWithRetentions() throws Exception { + // Given - Reverting retentions (type 20) + Reversion input = Reversion.builder() + .numero(2) + .fechaEmision(LocalDate.of(2022, 01, 31)) + .fechaEmisionComprobantes(LocalDate.of(2022, 01, 29)) + .proveedor(Proveedor.builder() + .ruc("12345678912") + .razonSocial("Softgreen S.A.C.") + .build() + ) + .comprobante(VoidedDocumentsItem.builder() + .serie("R001") + .numero(1) + .tipoComprobante(Catalog1.RETENCION.getCode()) + .descripcionSustento("Anulacion de retencion por error en calculo") + .build() + ) + .build(); + + assertInputReversion(input, "reversion_retention.xml"); + } +} diff --git a/xbuilder/core/src/test/resources/e2e/renderer/despatchadvice/DespatchAdviceCarrierTest/carrierData.xml b/xbuilder/core/src/test/resources/e2e/renderer/despatchadvice/DespatchAdviceCarrierTest/carrierData.xml new file mode 100644 index 00000000..5bdcab82 --- /dev/null +++ b/xbuilder/core/src/test/resources/e2e/renderer/despatchadvice/DespatchAdviceCarrierTest/carrierData.xml @@ -0,0 +1,122 @@ + + + + + + + + 2.1 + 2.1 + V001-1 + 2019-12-24 + 31 + + 20123456789 + + + 20123456789 + + + + + + + + #PROJECT-OPENUBL-SIGN + + + + + 20123456789 + + + 20123456789 + + + + MTC-654321 + + + + + + + 20876543210 + + + + + + + + + + 20555555555 + + + + + + + + SUNAT_Envio + 01 + 500.000 + + 01 + + 2019-12-24 + + + 44444444 + Carlos + Guerrero + + L5555555 + + + + + + 150102 + + Tienda Centro + + + + + 150101 + + Almacen Principal + + + + + + + XYZ-789 + + TUC-V001 + + + + + + 1 + 10.00 + + 1 + + + + + PROD-99 + + + + diff --git a/xbuilder/core/src/test/resources/e2e/renderer/despatchadvice/DespatchAdviceComplexTest/complexData.xml b/xbuilder/core/src/test/resources/e2e/renderer/despatchadvice/DespatchAdviceComplexTest/complexData.xml new file mode 100644 index 00000000..4bfd1add --- /dev/null +++ b/xbuilder/core/src/test/resources/e2e/renderer/despatchadvice/DespatchAdviceComplexTest/complexData.xml @@ -0,0 +1,179 @@ + + + + + + + + 2.1 + 2.1 + T001-100 + 2019-12-24 + 09 + + T001-1 + 09 + GUIA REMISION + + + 20100010001 + + + + + 12345678912 + + + 12345678912 + + + + + + + + #PROJECT-OPENUBL-SIGN + + + + + 12345678912 + + + 12345678912 + + + + + + + + + + 12345678 + + + + + + + + + + 12345678 + + + + + + + + SUNAT_Envio + 01 + Empaque madera + 100.500 + 90.000 + 10 + + 01 + + 2019-12-24 + + + 11111111 + Juan + Perez + CONDUCTOR_PRINCIPAL + + Q1234567 + + + + 22222222 + Jose + Gomez + COPILOTO + + + + + 020202 + 0002 + + Av. Destino 456 + + + + + 010101 + 0001 + + Av. Origen 123 + + + + + + + 1 + CONT-001 + + + + + 2 + CONT-002 + + + + + ABC-123 + + TUC-001 + + + CAR-456 + + TUC-SEC + + + + AUTH-001 + + + + + CALLAO + 1 + Puerto del Callao + + + + 1 + 5.00 + + 1 + + + + + ITEM-01 + + + Color + 1001 + Rojo + + + Marca + 1002 + ToolMaster + + + + diff --git a/xbuilder/core/src/test/resources/e2e/renderer/despatchadvice/DespatchAdviceTest/minData.xml b/xbuilder/core/src/test/resources/e2e/renderer/despatchadvice/DespatchAdviceTest/minData.xml index 30aab7f8..13d36baf 100644 --- a/xbuilder/core/src/test/resources/e2e/renderer/despatchadvice/DespatchAdviceTest/minData.xml +++ b/xbuilder/core/src/test/resources/e2e/renderer/despatchadvice/DespatchAdviceTest/minData.xml @@ -14,7 +14,7 @@ 2.0 T001-1 2019-12-24 - 09 + 09 12345678912 @@ -53,10 +53,9 @@ - 1 + SUNAT_Envio 18 1.000 - false 02 @@ -70,15 +69,19 @@ DireccionDestino + + + 010101 + + DireccionOrigen + + + - - 010101 - DireccionOrigen - 1 - 0.5 + 0.50 1 diff --git a/xbuilder/core/src/test/resources/e2e/renderer/reversion/ReversionTest/reversion.xml b/xbuilder/core/src/test/resources/e2e/renderer/reversion/ReversionTest/reversion.xml new file mode 100644 index 00000000..65ab4987 --- /dev/null +++ b/xbuilder/core/src/test/resources/e2e/renderer/reversion/ReversionTest/reversion.xml @@ -0,0 +1,58 @@ + + + + + + + + 2.0 + 1.0 + RR-20220131-1 + 2022-01-29 + 2022-01-31 + + 12345678912 + + + 12345678912 + + + + + + + + #PROJECT-OPENUBL-SIGN + + + + + 12345678912 + 6 + + + + + + + + 1 + 40 + P001 + 1 + Anulacion de percepcion por error en emision + + + 2 + 40 + P001 + 2 + Anulacion de percepcion por duplicado + + diff --git a/xbuilder/core/src/test/resources/e2e/renderer/reversion/ReversionTest/reversion_retention.xml b/xbuilder/core/src/test/resources/e2e/renderer/reversion/ReversionTest/reversion_retention.xml new file mode 100644 index 00000000..0464120a --- /dev/null +++ b/xbuilder/core/src/test/resources/e2e/renderer/reversion/ReversionTest/reversion_retention.xml @@ -0,0 +1,51 @@ + + + + + + + + 2.0 + 1.0 + RR-20220131-2 + 2022-01-29 + 2022-01-31 + + 12345678912 + + + 12345678912 + + + + + + + + #PROJECT-OPENUBL-SIGN + + + + + 12345678912 + 6 + + + + + + + + 1 + 20 + R001 + 1 + Anulacion de retencion por error en calculo + + diff --git a/xbuilder/quarkus-extension/deployment/src/main/java/io/github/project/openubl/quarkus/xbuilder/deployment/QuarkusXbuilderProcessor.java b/xbuilder/quarkus-extension/deployment/src/main/java/io/github/project/openubl/quarkus/xbuilder/deployment/QuarkusXbuilderProcessor.java index b8dab8fb..d542e5a8 100644 --- a/xbuilder/quarkus-extension/deployment/src/main/java/io/github/project/openubl/quarkus/xbuilder/deployment/QuarkusXbuilderProcessor.java +++ b/xbuilder/quarkus-extension/deployment/src/main/java/io/github/project/openubl/quarkus/xbuilder/deployment/QuarkusXbuilderProcessor.java @@ -33,6 +33,7 @@ FeatureBuildItem feature() { AdditionalBeanBuildItem additionalBeans() { return AdditionalBeanBuildItem.builder() .setUnremovable() + .setDefaultScope(io.quarkus.arc.processor.BuiltinScope.SINGLETON.getName()) .addBeanClasses(XBuilder.class, DefaultXBuilder.class, CustomTemplateLocator.class) .build(); } @@ -45,6 +46,7 @@ void registerTemplates(BuildProducer resource) thr "templates/Renderer/debitNote.xml", "templates/Renderer/invoice.xml", "templates/Renderer/voidedDocuments.xml", + "templates/Renderer/reversion.xml", "templates/Renderer/summaryDocuments.xml", "templates/Renderer/perception.xml", "templates/Renderer/retention.xml", @@ -76,7 +78,7 @@ void registerTemplates(BuildProducer resource) thr } @BuildStep - void registerServices(BuildProducer services) throws IOException { + void registerServices(BuildProducer services, BuildProducer reflectiveClass) throws IOException { String service = "META-INF/services/" + RuleFactory.class.getName(); // find out all the implementation classes listed in the service files @@ -90,6 +92,11 @@ void registerServices(BuildProducer services) throws I services.produce( new ServiceProviderBuildItem(RuleFactory.class.getName(), implementations.toArray(new String[0])) ); + + // register every listed implementation class for reflection so their annotations can be read + reflectiveClass.produce( + new ReflectiveClassBuildItem(true, false, implementations.toArray(new String[0])) + ); } @BuildStep @@ -101,9 +108,21 @@ ReflectiveClassBuildItem reflectionModelsLombok() { "io.github.project.openubl.xbuilder.content.models.sunat.baja.VoidedDocuments$VoidedDocumentsBuilderImpl", "io.github.project.openubl.xbuilder.content.models.sunat.resumen.SummaryDocuments$SummaryDocumentsBuilderImpl", + "io.github.project.openubl.xbuilder.content.models.sunat.baja.Reversion$ReversionBuilderImpl", + "io.github.project.openubl.xbuilder.content.models.common.Document$DocumentBuilderImpl", + "io.github.project.openubl.xbuilder.content.models.standard.general.SalesDocument$SalesDocumentBuilderImpl", + "io.github.project.openubl.xbuilder.content.models.sunat.SunatDocument$SunatDocumentBuilderImpl", + "io.github.project.openubl.xbuilder.content.models.standard.general.Note$NoteBuilderImpl", "io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.Perception$PerceptionBuilderImpl", - "io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.Retention$RetentionBuilderImpl" + "io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.Retention$RetentionBuilderImpl", + "io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.BasePercepcionRetencion$BasePercepcionRetencionBuilderImpl", + "io.github.project.openubl.xbuilder.content.models.standard.general.FormaDePago$FormaDePagoBuilderImpl", + "io.github.project.openubl.xbuilder.content.models.standard.general.TotalImporte$TotalImporteBuilderImpl", + + "io.github.project.openubl.xbuilder.enricher.config.Defaults$DefaultsBuilder", + "io.github.project.openubl.xbuilder.enricher.kie.ruleunits.BodyRuleContext$BodyRuleContextBuilder", + "io.github.project.openubl.xbuilder.enricher.kie.ruleunits.HeaderRuleContext$HeaderRuleContextBuilder" ); } @@ -146,11 +165,21 @@ ReflectiveClassBuildItem reflectionJaxbLombok() { "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdvice$TransportEquipment", "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdvice$TransportHandlingUnit", "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdvice$TransportMeans", + "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdvice$IdentityDocumentReference", + "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdvice$AddressTypeCode", + "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdvice$IssuerParty", + "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdvice$BuyerCustomerParty", + "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdvice$DespatchAddress", + "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdvice$ShipmentDocumentReference", + "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdvice$ApplicableTransportMeans", + "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdvice$Package", + "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdvice$Despatch", "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdviceLine$CommodityClassification", "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdviceLine$DeliveredQuantity", "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdviceLine$Item", "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdviceLine$SellersItemIdentification", + "io.github.project.openubl.xbuilder.content.jaxb.models.XMLDespatchAdviceLine$AdditionalItemProperty", "io.github.project.openubl.xbuilder.content.jaxb.models.XMLInvoiceLine$Quantity", @@ -222,6 +251,18 @@ ReflectiveClassBuildItem reflectionJaxbLombok() { @BuildStep ReflectiveClassBuildItem reflectionModels() { return new ReflectiveClassBuildItem(true, false, + io.github.project.openubl.quarkus.xbuilder.runtime.DefaultXBuilder.class, + io.github.project.openubl.quarkus.xbuilder.runtime.CustomTemplateLocator.class, + io.github.project.openubl.quarkus.xbuilder.runtime.XBuilderConfig.class, + io.github.project.openubl.xbuilder.enricher.config.Defaults.class, + io.github.project.openubl.xbuilder.enricher.kie.ruleunits.BodyRuleContext.class, + io.github.project.openubl.xbuilder.enricher.kie.ruleunits.HeaderRuleContext.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.GuiaItemAttribute.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.GuiaItemAttribute.GuiaItemAttributeBuilder.class, + io.github.project.openubl.xbuilder.content.models.standard.general.EmbededDespatch.class, + io.github.project.openubl.xbuilder.content.models.standard.general.EmbededDespatch.EmbededDespatchBuilder.class, + io.github.project.openubl.xbuilder.enricher.kie.RulePhase.class, + io.github.project.openubl.xbuilder.enricher.kie.RulePhase.PhaseType.class, io.github.project.openubl.xbuilder.content.models.common.Cliente.class, io.github.project.openubl.xbuilder.content.models.common.Cliente.ClienteBuilder.class, io.github.project.openubl.xbuilder.content.models.common.Contacto.class, @@ -300,6 +341,18 @@ ReflectiveClassBuildItem reflectionModels() { io.github.project.openubl.xbuilder.content.models.standard.guia.Remitente.RemitenteBuilder.class, io.github.project.openubl.xbuilder.content.models.standard.guia.Transportista.class, io.github.project.openubl.xbuilder.content.models.standard.guia.Transportista.TransportistaBuilder.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.Driver.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.Driver.DriverBuilder.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.Vehicle.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.Vehicle.VehicleBuilder.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.Puerto.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.Puerto.PuertoBuilder.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.DocumentoAdicional.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.DocumentoAdicional.DocumentoAdicionalBuilder.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.Tercero.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.Tercero.TerceroBuilder.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.Comprador.class, + io.github.project.openubl.xbuilder.content.models.standard.guia.Comprador.CompradorBuilder.class, io.github.project.openubl.xbuilder.content.catalogs.Catalog.class, io.github.project.openubl.xbuilder.content.catalogs.CatalogContadoCredito.class, @@ -341,6 +394,9 @@ ReflectiveClassBuildItem reflectionModels() { io.github.project.openubl.xbuilder.content.models.sunat.baja.VoidedDocumentsItem.class, io.github.project.openubl.xbuilder.content.models.sunat.baja.VoidedDocumentsItem.VoidedDocumentsItemBuilder.class, + io.github.project.openubl.xbuilder.content.models.sunat.baja.Reversion.class, + io.github.project.openubl.xbuilder.content.models.sunat.baja.Reversion.ReversionBuilder.class, + io.github.project.openubl.xbuilder.content.models.sunat.resumen.SummaryDocuments.class, io.github.project.openubl.xbuilder.content.models.sunat.resumen.SummaryDocuments.SummaryDocumentsBuilder.class, io.github.project.openubl.xbuilder.content.models.sunat.resumen.SummaryDocumentsItem.class, @@ -363,7 +419,8 @@ ReflectiveClassBuildItem reflectionModels() { io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.ComprobanteAfectado.class, io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.ComprobanteAfectado.ComprobanteAfectadoBuilder.class, io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.PercepcionRetencionOperacion.class, - io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.PercepcionRetencionOperacion.PercepcionRetencionOperacionBuilder.class + io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.PercepcionRetencionOperacion.PercepcionRetencionOperacionBuilder.class, + io.github.project.openubl.xbuilder.content.models.sunat.resumen.SummaryPerception.class ); } diff --git a/xbuilder/quarkus-extension/integration-tests/src/main/java/io/github/project/openubl/quarkus/xbuilder/it/QuarkusXbuilderResource.java b/xbuilder/quarkus-extension/integration-tests/src/main/java/io/github/project/openubl/quarkus/xbuilder/it/QuarkusXbuilderResource.java index a8a46046..9ab218ba 100644 --- a/xbuilder/quarkus-extension/integration-tests/src/main/java/io/github/project/openubl/quarkus/xbuilder/it/QuarkusXbuilderResource.java +++ b/xbuilder/quarkus-extension/integration-tests/src/main/java/io/github/project/openubl/quarkus/xbuilder/it/QuarkusXbuilderResource.java @@ -22,6 +22,7 @@ import io.github.project.openubl.xbuilder.content.models.standard.general.Invoice; import io.github.project.openubl.xbuilder.content.models.standard.guia.DespatchAdvice; import io.github.project.openubl.xbuilder.content.models.sunat.baja.VoidedDocuments; +import io.github.project.openubl.xbuilder.content.models.sunat.baja.Reversion; import io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.Perception; import io.github.project.openubl.xbuilder.content.models.sunat.percepcionretencion.Retention; import io.github.project.openubl.xbuilder.content.models.sunat.resumen.SummaryDocuments; @@ -40,10 +41,9 @@ import jakarta.ws.rs.core.MediaType; import jakarta.xml.bind.JAXBException; import jakarta.xml.bind.Unmarshaller; + import java.io.IOException; import java.io.StringReader; -import java.nio.file.Files; -import java.nio.file.Paths; import java.time.LocalDate; import static io.github.project.openubl.quarkus.xbuilder.XBuilder.Type.CREDIT_NOTE; @@ -54,6 +54,7 @@ import static io.github.project.openubl.quarkus.xbuilder.XBuilder.Type.RETENTION; import static io.github.project.openubl.quarkus.xbuilder.XBuilder.Type.SUMMARY_DOCUMENTS; import static io.github.project.openubl.quarkus.xbuilder.XBuilder.Type.VOIDED_DOCUMENTS; +import static io.github.project.openubl.quarkus.xbuilder.XBuilder.Type.REVERSION; @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.TEXT_PLAIN) @@ -291,4 +292,31 @@ public String createDespatchAdviceXml(String xml) { throw new RuntimeException(e); } } + + @POST + @Path("Reversion/from-json") + public String createReversion(JsonObject json) { + Reversion reversion = json.mapTo(Reversion.class); + + ContentEnricher enricher = new ContentEnricher(xBuilder.getDefaults(), () -> LocalDate.of(2022, 1, 25)); + enricher.enrich(reversion); + + Template template = xBuilder.getTemplate(REVERSION); + return template.data(reversion).render(); + } + + @POST + @Consumes(MediaType.TEXT_PLAIN) + @Path("Reversion/from-xml") + public String createReversionXml(String xml) { + Template template = xBuilder.getTemplate(REVERSION); + + try (StringReader reader = new StringReader(xml)) { + XMLVoidedDocuments xmlPojo = (XMLVoidedDocuments) unmarshaller.unmarshal(new InputSource(reader)); + VoidedDocuments inputFromXml = voidedDocumentsMapper.map(xmlPojo); + return template.data(inputFromXml).render(); + } catch (JAXBException e) { + throw new RuntimeException(e); + } + } } diff --git a/xbuilder/quarkus-extension/integration-tests/src/test/java/io/github/project/openubl/quarkus/xbuilder/it/QuarkusXbuilderResourceTest.java b/xbuilder/quarkus-extension/integration-tests/src/test/java/io/github/project/openubl/quarkus/xbuilder/it/QuarkusXbuilderResourceTest.java index 8f71fc6c..74018cf0 100644 --- a/xbuilder/quarkus-extension/integration-tests/src/test/java/io/github/project/openubl/quarkus/xbuilder/it/QuarkusXbuilderResourceTest.java +++ b/xbuilder/quarkus-extension/integration-tests/src/test/java/io/github/project/openubl/quarkus/xbuilder/it/QuarkusXbuilderResourceTest.java @@ -77,10 +77,10 @@ public void testAllYamlFilesFromSnapshot() throws URISyntaxException, IOExceptio .filter(p -> !p.toFile().isDirectory()) .forEach(p -> { try { - Map jsonObject = yamlMapper.readValue(p.toFile(), Map.class); + Map jsonObject = yamlMapper.readValue(p.toFile(), Map.class); String kind = (String) jsonObject.get("kind"); String snapshot = (String) jsonObject.get("snapshot"); - Map input = (Map) jsonObject.get("input"); + Map input = (Map) jsonObject.get("input"); given() .when() @@ -1150,7 +1150,6 @@ public void testDespatchAdvice() { .tipoTraslado(Catalog20.TRASLADO_EMISOR_ITINERANTE_CP.getCode()) .pesoTotal(BigDecimal.ONE) .pesoTotalUnidadMedida("KG") - .transbordoProgramado(false) .tipoModalidadTraslado(Catalog18.TRANSPORTE_PRIVADO.getCode()) .fechaTraslado(LocalDate.of(2022, 1, 25)) .partida(Partida.builder() @@ -1196,7 +1195,7 @@ public void testDespatchAdvice() { " 2.0\n" + " T001-1\n" + " 2022-01-25\n" + - " 09\n" + + " 09\n" + " \n" + " 12345678912\n" + " \n" + @@ -1235,10 +1234,9 @@ public void testDespatchAdvice() { " \n" + " \n" + " \n" + - " 1\n" + + " SUNAT_Envio\n" + " 18\n" + " 1.000\n" + - " false\n" + " \n" + " 02\n" + " \n" + @@ -1252,15 +1250,19 @@ public void testDespatchAdvice() { " DireccionDestino\n" + " \n" + " \n" + + " \n" + + " \n" + + " 010101\n" + + " \n" + + " DireccionOrigen\n" + + " \n" + + " \n" + + " \n" + " \n" + - " \n" + - " 010101\n" + - " DireccionOrigen\n" + - " \n" + " \n" + " \n" + " 1\n" + - " 0.5\n" + + " 0.50\n" + " \n" + " 1\n" + " \n" + @@ -1272,4 +1274,153 @@ public void testDespatchAdvice() { " \n" + "\n")); } + + @Test + public void testDespatchAdvice2() { + DespatchAdvice despatchAdvice = DespatchAdvice.builder() + .serie("T001") + .numero(1) + .tipoComprobante(Catalog1.GUIA_REMISION_REMITENTE.getCode()) + .remitente(Remitente.builder() + .ruc("12345678912") + .razonSocial("Softgreen S.A.C.") + .build() + ) + .destinatario(Destinatario.builder() + .tipoDocumentoIdentidad(Catalog6.DNI.getCode()) + .numeroDocumentoIdentidad("12345678") + .nombre("mi cliente") + .build() + ) + .envio(Envio.builder() + .tipoTraslado(Catalog20.TRASLADO_EMISOR_ITINERANTE_CP.getCode()) + .pesoTotal(BigDecimal.ONE) + .pesoTotalUnidadMedida("KG") + .tipoModalidadTraslado(Catalog18.TRANSPORTE_PRIVADO.getCode()) + .fechaTraslado(LocalDate.of(2022, 1, 25)) + .partida(Partida.builder() + .direccion("DireccionOrigen") + .ubigeo("010101") + .build() + ) + .destino(Destino.builder() + .direccion("DireccionDestino") + .ubigeo("020202") + .build() + ) + .build() + ) + .detalle(DespatchAdviceItem.builder() + .cantidad(new BigDecimal("0.5")) + .unidadMedida("KG") + .codigo("123456") + .build() + ) + .build(); + + given() + .when() + .contentType(ContentType.JSON) + .body(despatchAdvice) + .post("/quarkus-xbuilder/DespatchAdvice/from-json") + .then() + .statusCode(200) + .body(is( + """ + + + + + + + + 2.1 + 2.0 + T001-1 + 2022-01-25 + 09 + + 12345678912 + + + 12345678912 + + + + + + + + #PROJECT-OPENUBL-SIGN + + + + + 12345678912 + + + 12345678912 + + + + + + + + + + 12345678 + + + + + + + + SUNAT_Envio + 18 + 1.000 + + 02 + + 2022-01-25 + + + + + 020202 + + DireccionDestino + + + + + 010101 + + DireccionOrigen + + + + + + + 1 + 0.50 + + 1 + + + + 123456 + + + + + """)); + } } diff --git a/xbuilder/quarkus-extension/runtime/src/main/java/io/github/project/openubl/quarkus/xbuilder/XBuilder.java b/xbuilder/quarkus-extension/runtime/src/main/java/io/github/project/openubl/quarkus/xbuilder/XBuilder.java index 4db2edb6..e16ab53b 100644 --- a/xbuilder/quarkus-extension/runtime/src/main/java/io/github/project/openubl/quarkus/xbuilder/XBuilder.java +++ b/xbuilder/quarkus-extension/runtime/src/main/java/io/github/project/openubl/quarkus/xbuilder/XBuilder.java @@ -16,7 +16,8 @@ enum Type { SUMMARY_DOCUMENTS("summaryDocuments.xml"), PERCEPTION("perception.xml"), RETENTION("retention.xml"), - DESPATCH_ADVICE("despatchAdvice.xml"); + DESPATCH_ADVICE("despatchAdvice.xml"), + REVERSION("reversion.xml"); private final String templatePath;