init pfs-invoices

This commit is contained in:
Alex Prudencio 2024-04-06 11:21:22 -04:00
parent 07a97e1b2c
commit 344477881f
107 changed files with 21335 additions and 299 deletions

3
.gitignore vendored
View File

@ -19,3 +19,6 @@ drivers/
# Error screenshots generated by TestBench for failed integration tests
error-screenshots/
webpack.generated.js
utils/
src/main/bundles/

8
DOC.md Normal file
View File

@ -0,0 +1,8 @@
# QR
For example, can link to the URL:
https://siat.impuestos.gob.bo/consulta/QR?nit=289026029&cuf=13C6A589EDF26619EB3675A08B8CF1BDF154F3BB

View File

@ -1,24 +0,0 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>

View File

@ -1,4 +1,4 @@
# Flow CRM Tutorial
# PFS invoices
This project can be used as a starting point to create your own Vaadin application with Spring Boot.
It contains all the necessary configuration and some placeholder files to get you started.
@ -20,7 +20,7 @@ This will build a JAR file with all the dependencies and front-end resources,
ready to be deployed. The file can be found in the `target` folder after the build completes.
Once the JAR file is built, you can run it using
`java -jar target/flowcrmtutorial-1.0-SNAPSHOT.jar`
`java -jar target/pfs-invoices-1.0-SNAPSHOT.jar`
## Project structure
@ -51,11 +51,11 @@ To build the Dockerized version of the project, run
```
mvn clean package -Pproduction
docker build . -t flowcrmtutorial:latest
docker build . -t pfs-invoices:latest
```
Once the Docker image is correctly built, you can test it locally using
```
docker run -p 8080:8080 flowcrmtutorial:latest
docker run -p 8080:8080 pfs-invoices:latest
```

23
frontend/index.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<!--
This file is auto-generated by Vaadin.
-->
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body, #outlet {
height: 100vh;
width: 100%;
margin: 0;
}
</style>
<!-- index.ts is included here automatically (either by the dev server or during the build) -->
</head>
<body>
<!-- This outlet div is where the views are rendered -->
<div id="outlet"></div>
</body>
</html>

16070
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

257
package.json Normal file
View File

@ -0,0 +1,257 @@
{
"name": "no-name",
"license": "UNLICENSED",
"type": "module",
"dependencies": {
"@polymer/polymer": "3.5.1",
"@vaadin-component-factory/vcf-pdf-viewer": "2.0.1",
"@vaadin/a11y-base": "24.3.10",
"@vaadin/accordion": "24.3.10",
"@vaadin/app-layout": "24.3.10",
"@vaadin/avatar": "24.3.10",
"@vaadin/avatar-group": "24.3.10",
"@vaadin/bundles": "24.3.10",
"@vaadin/button": "24.3.10",
"@vaadin/checkbox": "24.3.10",
"@vaadin/checkbox-group": "24.3.10",
"@vaadin/combo-box": "24.3.10",
"@vaadin/common-frontend": "0.0.19",
"@vaadin/component-base": "24.3.10",
"@vaadin/confirm-dialog": "24.3.10",
"@vaadin/context-menu": "24.3.10",
"@vaadin/custom-field": "24.3.10",
"@vaadin/date-picker": "24.3.10",
"@vaadin/date-time-picker": "24.3.10",
"@vaadin/details": "24.3.10",
"@vaadin/dialog": "24.3.10",
"@vaadin/email-field": "24.3.10",
"@vaadin/field-base": "24.3.10",
"@vaadin/field-highlighter": "24.3.10",
"@vaadin/form-layout": "24.3.10",
"@vaadin/grid": "24.3.10",
"@vaadin/horizontal-layout": "24.3.10",
"@vaadin/icon": "24.3.10",
"@vaadin/icons": "24.3.10",
"@vaadin/input-container": "24.3.10",
"@vaadin/integer-field": "24.3.10",
"@vaadin/item": "24.3.10",
"@vaadin/list-box": "24.3.10",
"@vaadin/lit-renderer": "24.3.10",
"@vaadin/login": "24.3.10",
"@vaadin/menu-bar": "24.3.10",
"@vaadin/message-input": "24.3.10",
"@vaadin/message-list": "24.3.10",
"@vaadin/multi-select-combo-box": "24.3.10",
"@vaadin/notification": "24.3.10",
"@vaadin/number-field": "24.3.10",
"@vaadin/overlay": "24.3.10",
"@vaadin/password-field": "24.3.10",
"@vaadin/polymer-legacy-adapter": "24.3.10",
"@vaadin/progress-bar": "24.3.10",
"@vaadin/radio-group": "24.3.10",
"@vaadin/router": "1.7.5",
"@vaadin/scroller": "24.3.10",
"@vaadin/select": "24.3.10",
"@vaadin/side-nav": "24.3.10",
"@vaadin/split-layout": "24.3.10",
"@vaadin/tabs": "24.3.10",
"@vaadin/tabsheet": "24.3.10",
"@vaadin/text-area": "24.3.10",
"@vaadin/text-field": "24.3.10",
"@vaadin/time-picker": "24.3.10",
"@vaadin/tooltip": "24.3.10",
"@vaadin/upload": "24.3.10",
"@vaadin/vaadin-development-mode-detector": "2.0.6",
"@vaadin/vaadin-lumo-styles": "24.3.10",
"@vaadin/vaadin-material-styles": "24.3.10",
"@vaadin/vaadin-themable-mixin": "24.3.10",
"@vaadin/vaadin-usage-statistics": "2.1.2",
"@vaadin/vertical-layout": "24.3.10",
"@vaadin/virtual-list": "24.3.10",
"construct-style-sheets-polyfill": "3.1.0",
"date-fns": "2.29.3",
"lit": "3.1.2",
"print-js": "1.6.0"
},
"devDependencies": {
"@rollup/plugin-replace": "5.0.5",
"@rollup/pluginutils": "5.1.0",
"@vitejs/plugin-react": "4.2.1",
"@vitejs/plugin-react-swc": "3.5.0",
"async": "3.2.4",
"glob": "10.3.3",
"rollup-plugin-brotli": "3.1.0",
"rollup-plugin-visualizer": "5.12.0",
"strip-css-comments": "5.0.0",
"transform-ast": "2.4.4",
"typescript": "5.3.3",
"vite": "5.1.7",
"vite-plugin-checker": "0.6.4",
"workbox-build": "7.0.0",
"workbox-core": "7.0.0",
"workbox-precaching": "7.0.0"
},
"vaadin": {
"dependencies": {
"@polymer/polymer": "3.5.1",
"@vaadin-component-factory/vcf-pdf-viewer": "2.0.1",
"@vaadin/a11y-base": "24.3.10",
"@vaadin/accordion": "24.3.10",
"@vaadin/app-layout": "24.3.10",
"@vaadin/avatar": "24.3.10",
"@vaadin/avatar-group": "24.3.10",
"@vaadin/bundles": "24.3.10",
"@vaadin/button": "24.3.10",
"@vaadin/checkbox": "24.3.10",
"@vaadin/checkbox-group": "24.3.10",
"@vaadin/combo-box": "24.3.10",
"@vaadin/common-frontend": "0.0.19",
"@vaadin/component-base": "24.3.10",
"@vaadin/confirm-dialog": "24.3.10",
"@vaadin/context-menu": "24.3.10",
"@vaadin/custom-field": "24.3.10",
"@vaadin/date-picker": "24.3.10",
"@vaadin/date-time-picker": "24.3.10",
"@vaadin/details": "24.3.10",
"@vaadin/dialog": "24.3.10",
"@vaadin/email-field": "24.3.10",
"@vaadin/field-base": "24.3.10",
"@vaadin/field-highlighter": "24.3.10",
"@vaadin/form-layout": "24.3.10",
"@vaadin/grid": "24.3.10",
"@vaadin/horizontal-layout": "24.3.10",
"@vaadin/icon": "24.3.10",
"@vaadin/icons": "24.3.10",
"@vaadin/input-container": "24.3.10",
"@vaadin/integer-field": "24.3.10",
"@vaadin/item": "24.3.10",
"@vaadin/list-box": "24.3.10",
"@vaadin/lit-renderer": "24.3.10",
"@vaadin/login": "24.3.10",
"@vaadin/menu-bar": "24.3.10",
"@vaadin/message-input": "24.3.10",
"@vaadin/message-list": "24.3.10",
"@vaadin/multi-select-combo-box": "24.3.10",
"@vaadin/notification": "24.3.10",
"@vaadin/number-field": "24.3.10",
"@vaadin/overlay": "24.3.10",
"@vaadin/password-field": "24.3.10",
"@vaadin/polymer-legacy-adapter": "24.3.10",
"@vaadin/progress-bar": "24.3.10",
"@vaadin/radio-group": "24.3.10",
"@vaadin/router": "1.7.5",
"@vaadin/scroller": "24.3.10",
"@vaadin/select": "24.3.10",
"@vaadin/side-nav": "24.3.10",
"@vaadin/split-layout": "24.3.10",
"@vaadin/tabs": "24.3.10",
"@vaadin/tabsheet": "24.3.10",
"@vaadin/text-area": "24.3.10",
"@vaadin/text-field": "24.3.10",
"@vaadin/time-picker": "24.3.10",
"@vaadin/tooltip": "24.3.10",
"@vaadin/upload": "24.3.10",
"@vaadin/vaadin-development-mode-detector": "2.0.6",
"@vaadin/vaadin-lumo-styles": "24.3.10",
"@vaadin/vaadin-material-styles": "24.3.10",
"@vaadin/vaadin-themable-mixin": "24.3.10",
"@vaadin/vaadin-usage-statistics": "2.1.2",
"@vaadin/vertical-layout": "24.3.10",
"@vaadin/virtual-list": "24.3.10",
"construct-style-sheets-polyfill": "3.1.0",
"date-fns": "2.29.3",
"lit": "3.1.2",
"print-js": "1.6.0"
},
"devDependencies": {
"@rollup/plugin-replace": "5.0.5",
"@rollup/pluginutils": "5.1.0",
"@vitejs/plugin-react": "4.2.1",
"@vitejs/plugin-react-swc": "3.5.0",
"async": "3.2.4",
"glob": "10.3.3",
"rollup-plugin-brotli": "3.1.0",
"rollup-plugin-visualizer": "5.12.0",
"strip-css-comments": "5.0.0",
"transform-ast": "2.4.4",
"typescript": "5.3.3",
"vite": "5.1.7",
"vite-plugin-checker": "0.6.4",
"workbox-build": "7.0.0",
"workbox-core": "7.0.0",
"workbox-precaching": "7.0.0"
},
"hash": "22fa4a03f17449183ffb85c85d2c29db3d0fbd83e7221ee76d18b0a76cffa06f"
},
"overrides": {
"@vaadin/bundles": "$@vaadin/bundles",
"@vaadin/a11y-base": "$@vaadin/a11y-base",
"@vaadin/accordion": "$@vaadin/accordion",
"@vaadin/app-layout": "$@vaadin/app-layout",
"@vaadin/avatar": "$@vaadin/avatar",
"@vaadin/avatar-group": "$@vaadin/avatar-group",
"@vaadin/button": "$@vaadin/button",
"@vaadin/checkbox": "$@vaadin/checkbox",
"@vaadin/checkbox-group": "$@vaadin/checkbox-group",
"@vaadin/combo-box": "$@vaadin/combo-box",
"@vaadin/component-base": "$@vaadin/component-base",
"@vaadin/confirm-dialog": "$@vaadin/confirm-dialog",
"@vaadin/context-menu": "$@vaadin/context-menu",
"@vaadin/custom-field": "$@vaadin/custom-field",
"@vaadin/date-picker": "$@vaadin/date-picker",
"@vaadin/date-time-picker": "$@vaadin/date-time-picker",
"@vaadin/details": "$@vaadin/details",
"@vaadin/dialog": "$@vaadin/dialog",
"@vaadin/email-field": "$@vaadin/email-field",
"@vaadin/field-base": "$@vaadin/field-base",
"@vaadin/field-highlighter": "$@vaadin/field-highlighter",
"@vaadin/form-layout": "$@vaadin/form-layout",
"@vaadin/grid": "$@vaadin/grid",
"@vaadin/horizontal-layout": "$@vaadin/horizontal-layout",
"@vaadin/icon": "$@vaadin/icon",
"@vaadin/icons": "$@vaadin/icons",
"@vaadin/input-container": "$@vaadin/input-container",
"@vaadin/integer-field": "$@vaadin/integer-field",
"@vaadin/item": "$@vaadin/item",
"@vaadin/list-box": "$@vaadin/list-box",
"@vaadin/lit-renderer": "$@vaadin/lit-renderer",
"@vaadin/login": "$@vaadin/login",
"@vaadin/menu-bar": "$@vaadin/menu-bar",
"@vaadin/message-input": "$@vaadin/message-input",
"@vaadin/message-list": "$@vaadin/message-list",
"@vaadin/multi-select-combo-box": "$@vaadin/multi-select-combo-box",
"@vaadin/notification": "$@vaadin/notification",
"@vaadin/number-field": "$@vaadin/number-field",
"@vaadin/overlay": "$@vaadin/overlay",
"@vaadin/password-field": "$@vaadin/password-field",
"@vaadin/polymer-legacy-adapter": "$@vaadin/polymer-legacy-adapter",
"@vaadin/progress-bar": "$@vaadin/progress-bar",
"@vaadin/radio-group": "$@vaadin/radio-group",
"@vaadin/scroller": "$@vaadin/scroller",
"@vaadin/select": "$@vaadin/select",
"@vaadin/side-nav": "$@vaadin/side-nav",
"@vaadin/split-layout": "$@vaadin/split-layout",
"@vaadin/tabs": "$@vaadin/tabs",
"@vaadin/tabsheet": "$@vaadin/tabsheet",
"@vaadin/text-area": "$@vaadin/text-area",
"@vaadin/text-field": "$@vaadin/text-field",
"@vaadin/time-picker": "$@vaadin/time-picker",
"@vaadin/tooltip": "$@vaadin/tooltip",
"@vaadin/upload": "$@vaadin/upload",
"@vaadin/vaadin-development-mode-detector": "$@vaadin/vaadin-development-mode-detector",
"@vaadin/vaadin-lumo-styles": "$@vaadin/vaadin-lumo-styles",
"@vaadin/vaadin-material-styles": "$@vaadin/vaadin-material-styles",
"@vaadin/router": "$@vaadin/router",
"@vaadin/vaadin-usage-statistics": "$@vaadin/vaadin-usage-statistics",
"@vaadin/vertical-layout": "$@vaadin/vertical-layout",
"@vaadin/virtual-list": "$@vaadin/virtual-list",
"@vaadin/common-frontend": "$@vaadin/common-frontend",
"construct-style-sheets-polyfill": "$construct-style-sheets-polyfill",
"lit": "$lit",
"@polymer/polymer": "$@polymer/polymer",
"@vaadin/vaadin-themable-mixin": "$@vaadin/vaadin-themable-mixin",
"date-fns": "$date-fns",
"@vaadin-component-factory/vcf-pdf-viewer": "$@vaadin-component-factory/vcf-pdf-viewer",
"print-js": "$print-js"
}
}

256
pom.xml
View File

@ -2,10 +2,9 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Project from https://start.vaadin.com/project/746e647f-eaf8-4114-8c75-65de698e1e76 -->
<groupId>com.example.application</groupId>
<artifactId>flowcrmtutorial</artifactId>
<name>flowcrmtutorial</name>
<groupId>com.primefactorsolutions</groupId>
<artifactId>pfs-invoices</artifactId>
<name>pfs-invoices</name>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
@ -21,6 +20,15 @@
</parent>
<repositories>
<repository>
<id>central</id>
<name>Maven Central</name>
<layout>default</layout>
<url>https://repo1.maven.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>Vaadin Directory</id>
<url>https://maven.vaadin.com/vaadin-addons</url>
@ -45,8 +53,7 @@
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<!-- Replace artifactId with vaadin-core to use only free components -->
<artifactId>vaadin</artifactId>
<artifactId>vaadin-core</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
@ -68,11 +75,32 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
@ -83,11 +111,119 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-mapper-orm</artifactId>
<version>7.1.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-backend-lucene</artifactId>
<version>7.1.1.Final</version>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-testbench-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>in.virit</groupId>
<artifactId>viritin</artifactId>
<version>2.8.9</version>
</dependency>
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-63</artifactId>
<version>3.7.3</version>
</dependency>
<dependency>
<groupId>org.vaadin.addons.componentfactory</groupId>
<artifactId>vcf-pdf-viewer</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-pdfbox</artifactId>
<version>1.0.10</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>9.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>9.10.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.1.0-jre</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>org.jvnet.jaxb</groupId>
<artifactId>jaxb-maven-plugin</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.jvnet.jaxb</groupId>
<artifactId>jaxb-plugins</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fillumina</groupId>
<artifactId>krasa-jaxb-tools</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jakarta-xmlbind-annotations</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.2</version>
</dependency>
</dependencies>
<build>
@ -97,7 +233,109 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.final</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</path>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</dependency>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.jvnet.jaxb</groupId>
<artifactId>jaxb-maven-plugin</artifactId>
<version>4.0.3</version>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<args>
<arg>-XJsr303Annotations</arg>
<arg>-XJsr303Annotations:JSR_349=true</arg>
<arg>-XJsr303Annotations:verbose=false</arg>
<arg>-XJsr303Annotations:validationAnnotations=jakarta</arg>
<arg>-Xsetters</arg>
<arg>-Xsetters-mode=direct</arg>
</args>
<schemaDirectory>src/main/resources/schemas</schemaDirectory>
<bindingDirectory>src/main/resources/bindings</bindingDirectory>
<generatePackage>com.primefactorsolutions.invoices.xsd</generatePackage>
<plugins>
<dependency>
<groupId>org.jvnet.jaxb</groupId>
<artifactId>jaxb-plugins</artifactId>
<version>4.0.3</version>
</dependency>
<plugin>
<groupId>com.fillumina</groupId>
<artifactId>krasa-jaxb-tools</artifactId>
<version>2.2</version>
</plugin>
</plugins>
</configuration>
</plugin>
<plugin>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<goals>
<goal>wsimport</goal>
</goals>
</execution>
</executions>
<configuration>
<packageName>com.primefactorsolutions.invoices.wsdl</packageName>
<wsdlUrls>
<wsdlUrl>https://pilotosiatservicios.impuestos.gob.bo/v2/ServicioFacturacionComputarizada?wsdl</wsdlUrl>
</wsdlUrls>
<sourceDestDir>${sourcesDir}</sourceDestDir>
<destDir>${classesDir}</destDir>
<extension>true</extension>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>target/generated-sources/xjc</source>
<source>target/generated-sources/wsimport</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
@ -139,6 +377,7 @@
<executions>
<execution>
<goals>
<goal>prepare-frontend</goal>
<goal>build-frontend</goal>
</goals>
<phase>compile</phase>
@ -191,6 +430,7 @@
<enableAssertions>true</enableAssertions>
</configuration>
</plugin>
</plugins>
</build>
</profile>

View File

@ -0,0 +1,32 @@
This directory is automatically generated by Vaadin and contains the pre-compiled
frontend files/resources for your project (frontend development bundle).
It should be added to Version Control System and committed, so that other developers
do not have to compile it again.
Frontend development bundle is automatically updated when needed:
- an npm/pnpm package is added with @NpmPackage or directly into package.json
- CSS, JavaScript or TypeScript files are added with @CssImport, @JsModule or @JavaScript
- Vaadin add-on with front-end customizations is added
- Custom theme imports/assets added into 'theme.json' file
- Exported web component is added.
If your project development needs a hot deployment of the frontend changes,
you can switch Flow to use Vite development server (default in Vaadin 23.3 and earlier versions):
- set `vaadin.frontend.hotdeploy=true` in `application.properties`
- configure `vaadin-maven-plugin`:
```
<configuration>
<frontendHotdeploy>true</frontendHotdeploy>
</configuration>
```
- configure `jetty-maven-plugin`:
```
<configuration>
<systemProperties>
<vaadin.frontend.hotdeploy>true</vaadin.frontend.hotdeploy>
</systemProperties>
</configuration>
```
Read more [about Vaadin development mode](https://vaadin.com/docs/next/configuration/development-mode/#pre-compiled-front-end-bundle-for-faster-start-up).

BIN
src/main/bundles/dev.bundle Normal file

Binary file not shown.

View File

@ -1,34 +0,0 @@
package com.example.application.data;
import jakarta.annotation.Nullable;
import jakarta.persistence.Entity;
import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.NotBlank;
import java.util.LinkedList;
import java.util.List;
@Entity
public class Company extends AbstractEntity {
@NotBlank
private String name;
@OneToMany(mappedBy = "company")
@Nullable
private List<Contact> employees = new LinkedList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Contact> getEmployees() {
return employees;
}
public void setEmployees(List<Contact> employees) {
this.employees = employees;
}
}

View File

@ -1,78 +0,0 @@
package com.example.application.data;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
@Entity
public class Contact extends AbstractEntity {
@NotEmpty
private String firstName = "";
@NotEmpty
private String lastName = "";
@ManyToOne
@JoinColumn(name = "company_id")
@NotNull
@JsonIgnoreProperties({"employees"})
private Company company;
@NotNull
@ManyToOne
private Status status;
@Email
@NotEmpty
private String email = "";
@Override
public String toString() {
return firstName + " " + lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}

View File

@ -1,8 +0,0 @@
package com.example.application.data;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ContactRepository extends JpaRepository<Contact, Long> {
}

View File

@ -1,25 +0,0 @@
package com.example.application.data;
import jakarta.persistence.Entity;
@Entity
public class Status extends AbstractEntity {
private String name;
public Status() {
}
public Status(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -1,8 +0,0 @@
package com.example.application.data;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StatusRepository extends JpaRepository<Status, Long> {
}

View File

@ -1,33 +0,0 @@
package com.example.application.views.list;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.lumo.LumoUtility.Margin;
@PageTitle("list")
@Route(value = "")
public class ListView extends VerticalLayout {
public ListView() {
setSpacing(false);
Image img = new Image("images/empty-plant.png", "placeholder plant");
img.setWidth("200px");
add(img);
H2 header = new H2("This place intentionally left empty");
header.addClassNames(Margin.Top.XLARGE, Margin.Bottom.MEDIUM);
add(header);
add(new Paragraph("Its a place where you can grow your own UI 🤗"));
setSizeFull();
setJustifyContentMode(JustifyContentMode.CENTER);
setDefaultHorizontalComponentAlignment(Alignment.CENTER);
getStyle().set("text-align", "center");
}
}

View File

@ -1,4 +1,4 @@
package com.example.application;
package com.primefactorsolutions.invoices;
import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.theme.Theme;
@ -13,7 +13,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
*
*/
@SpringBootApplication
@Theme(value = "flowcrmtutorial")
@Theme(value = "pfs")
public class Application implements AppShellConfigurator {
public static void main(String[] args) {

View File

@ -0,0 +1,61 @@
package com.primefactorsolutions.invoices.beans;
import com.primefactorsolutions.invoices.xml.adapters.DateTimeXmlAdapter;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlSchemaType;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class CabeceraDTO {
@NotNull
@DecimalMin(value = "1", inclusive = true)
@DecimalMax(value = "9999999999999", inclusive = true)
protected long nitEmisor;
@XmlElement(required = true)
@NotNull
@Size(min = 1, max = 200)
protected String razonSocialEmisor;
@XmlElement(required = true)
@NotNull
@Size(min = 1, max = 25)
protected String municipio;
@XmlElement(required = true, nillable = true)
@Size(min = 1, max = 25)
protected String telefono;
@NotNull
@DecimalMin(value = "1", inclusive = true)
@DecimalMax(value = "9999999999", inclusive = true)
protected long numeroFactura;
@XmlElement(required = true)
@NotNull
@Size(min = 1, max = 100)
protected String cuf;
@XmlElement(required = true)
@NotNull
@Size(min = 1, max = 100)
protected String cufd;
@NotNull
@DecimalMin(value = "0", inclusive = true)
@DecimalMax(value = "9999", inclusive = true)
protected int codigoSucursal;
@XmlElement(required = true)
@NotNull
@Size(min = 1, max = 500)
protected String direccion;
@XmlElement(required = true, type = Integer.class, nillable = true)
@DecimalMin(value = "0", inclusive = true)
@DecimalMax(value = "9999", inclusive = true)
protected Integer codigoPuntoVenta;
@XmlElement(required = true, type = String.class)
@XmlJavaTypeAdapter(DateTimeXmlAdapter.class)
@XmlSchemaType(name = "dateTime")
@NotNull
protected LocalDateTime fechaEmision;
}

View File

@ -0,0 +1,50 @@
package com.primefactorsolutions.invoices.beans;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import jakarta.xml.bind.annotation.XmlElement;
import lombok.Data;
@Data
public class ClientDTO {
@XmlElement(required = true, nillable = true)
@Size(min = 1, max = 500)
protected String nombreRazonSocial;
@NotNull
@DecimalMin(value = "1", inclusive = true)
@DecimalMax(value = "5", inclusive = true)
protected int codigoTipoDocumentoIdentidad;
@XmlElement(required = true)
@NotNull
@Size(min = 1, max = 20)
protected String numeroDocumento;
@XmlElement(required = true, nillable = true)
@Size(max = 5)
protected String complemento;
@XmlElement(required = true)
@NotNull
@Size(min = 1, max = 500)
protected String direccionComprador;
@XmlElement(required = true)
@NotNull
@Size(min = 1, max = 100)
protected String codigoCliente;
@XmlElement(required = true)
@NotNull
@Size(min = 1, max = 500)
protected String lugarDestino;
@NotNull
@DecimalMin(value = "1", inclusive = true)
@DecimalMax(value = "212", inclusive = true)
protected int codigoPais;
@NotNull
@DecimalMin(value = "1", inclusive = true)
@DecimalMax(value = "308", inclusive = true)
protected int codigoMetodoPago;
@XmlElement(required = true, type = Long.class, nillable = true)
@DecimalMin(value = "0", inclusive = true)
@DecimalMax(value = "9999999999999999", inclusive = true)
protected Long numeroTarjeta;
}

View File

@ -0,0 +1,50 @@
package com.primefactorsolutions.invoices.beans;
import jakarta.validation.constraints.*;
import jakarta.xml.bind.annotation.XmlElement;
import lombok.Data;
import java.math.BigDecimal;
import java.math.BigInteger;
@Data
public class DetalleDTO {
@XmlElement(required = true)
@NotNull
@Size(min = 1, max = 10)
protected String actividadEconomica;
@NotNull
@DecimalMin(value = "1", inclusive = true)
@DecimalMax(value = "99999999", inclusive = true)
protected int codigoProductoSin;
@XmlElement(required = true)
@NotNull
@Size(min = 1, max = 50)
protected String codigoProducto;
@XmlElement(required = true)
@NotNull
@Size(min = 1, max = 500)
protected String descripcion;
@XmlElement(required = true)
@NotNull
protected BigInteger cantidad;
@NotNull
@DecimalMin(value = "1", inclusive = true)
@DecimalMax(value = "200", inclusive = true)
protected int unidadMedida;
@XmlElement(required = true)
@NotNull
@DecimalMin(value = "0", inclusive = false)
@Digits(integer = 20, fraction = 5)
protected BigDecimal precioUnitario;
@XmlElement(required = true, nillable = true)
@DecimalMin(value = "0", inclusive = true)
@Digits(integer = 20, fraction = 5)
protected BigDecimal montoDescuento;
@XmlElement(required = true)
@NotNull
@DecimalMin(value = "0", inclusive = false)
@Digits(integer = 20, fraction = 5)
protected BigDecimal subTotal;
}

View File

@ -0,0 +1,34 @@
package com.primefactorsolutions.invoices.beans;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import jakarta.xml.bind.annotation.XmlElement;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class TotalesDTO {
@XmlElement(required = true)
@NotNull
@DecimalMin(value = "0", inclusive = false)
@Digits(integer = 17, fraction = 2)
protected BigDecimal montoTotal;
@XmlElement(required = true)
@NotNull
protected BigDecimal montoTotalSujetoIva;
@XmlElement(required = true)
@NotNull
@DecimalMin(value = "0", inclusive = false)
@Digits(integer = 17, fraction = 2)
protected BigDecimal montoTotalMoneda;
@XmlElement(required = true, nillable = true)
@Size(min = 1, max = 10000)
protected String informacionAdicional;
@XmlElement(required = true, nillable = true)
@DecimalMin(value = "0", inclusive = true)
@Digits(integer = 17, fraction = 2)
protected BigDecimal descuentoAdicional;
}

View File

@ -0,0 +1,19 @@
package com.primefactorsolutions.invoices.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.Resources;
import com.primefactorsolutions.invoices.model.Help;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
@Configuration
public class ApplicationConfig {
@Bean
public Help getHelp() throws IOException {
var url = Resources.getResource("help/ComercialExportacionServicio.json");
var objectMapper = new ObjectMapper();
return objectMapper.readValue(url, Help.class);
}
}

View File

@ -0,0 +1,39 @@
package com.primefactorsolutions.invoices.config;
import com.primefactorsolutions.invoices.model.Client;
import com.primefactorsolutions.invoices.model.Product;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.massindexing.MassIndexer;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Data
@Log4j2
public class ApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {
private final EntityManager entityManager;
@Override
@Transactional
public void onApplicationEvent(ApplicationReadyEvent event) {
var indexed = new Class[]{Client.class, Product.class};
log.info("Indexing tables {}", Arrays.stream(indexed).toList());
SearchSession searchSession = Search.session(entityManager);
MassIndexer indexer = searchSession.massIndexer(indexed)
.threadsToLoadObjects(4);
try {
indexer.startAndWait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,37 @@
package com.primefactorsolutions.invoices.config;
import com.primefactorsolutions.invoices.views.LoginView;
import com.vaadin.flow.spring.security.VaadinWebSecurity;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@EnableWebSecurity
@Configuration
public class SecurityConfig extends VaadinWebSecurity {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth ->
auth.requestMatchers(
AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/images/*.png")).permitAll());
super.configure(http);
setLoginView(http, LoginView.class);
}
@Bean
public AuthenticationManager authenticationManager() {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource("ldap://ldap.primefactorsolutions.com:389/dc=primefactorsolutions,dc=com");
contextSource.setCacheEnvironmentProperties(false);
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=users");
return factory.createAuthenticationManager();
}
}

View File

@ -0,0 +1,12 @@
package com.primefactorsolutions.invoices.data;
import com.primefactorsolutions.invoices.model.Client;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.UUID;
public interface ClientRepository extends JpaRepository<Client, UUID> {
List<Client> findByCorreoElectronicoLike(String correoElectronico);
}

View File

@ -1,6 +1,7 @@
package com.example.application.data;
package com.primefactorsolutions.invoices.data;
import com.primefactorsolutions.invoices.model.Company;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CompanyRepository extends JpaRepository<Company, Long> {

View File

@ -0,0 +1,9 @@
package com.primefactorsolutions.invoices.data;
import com.primefactorsolutions.invoices.model.Invoice;
import org.springframework.data.jpa.repository.JpaRepository;
public interface InvoiceRepository extends JpaRepository<Invoice, Long> {
}

View File

@ -0,0 +1,12 @@
package com.primefactorsolutions.invoices.data;
import com.primefactorsolutions.invoices.model.Media;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface MediaRepository extends JpaRepository<Media, Long> {
List<Media> findByNameLike(String name);
}

View File

@ -0,0 +1,13 @@
package com.primefactorsolutions.invoices.data;
import com.primefactorsolutions.invoices.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.UUID;
public interface ProductRepository extends JpaRepository<Product, UUID> {
List<Product> findByDescripcionLike(String descripcion);
}

View File

@ -0,0 +1,9 @@
package com.primefactorsolutions.invoices.data;
import com.primefactorsolutions.invoices.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
public interface UserRepository extends JpaRepository<User, UUID> {
}

View File

@ -1,29 +1,28 @@
package com.example.application.data;
package com.primefactorsolutions.invoices.model;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Version;
import java.util.UUID;
@MappedSuperclass
public abstract class AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idgenerator")
// The initial value is to account for data.sql demo data ids
@SequenceGenerator(name = "idgenerator", initialValue = 1000)
private Long id;
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Version
private int version;
public Long getId() {
public UUID getId() {
return id;
}
public void setId(Long id) {
public void setId(UUID id) {
this.id = id;
}

View File

@ -0,0 +1,29 @@
package com.primefactorsolutions.invoices.model;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
@Entity
public class Branch extends AbstractEntity {
@ManyToOne
@JoinColumn(name = "company_id")
@NotNull
private Company company;
@NotNull
@DecimalMin(value = "0", inclusive = true)
@DecimalMax(value = "9999", inclusive = true)
protected int codigoSucursal;
protected String cuis;
protected String description;
}

View File

@ -0,0 +1,73 @@
package com.primefactorsolutions.invoices.model;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.*;
import jakarta.xml.bind.annotation.XmlElement;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
@Setter
@Getter
@Entity
@Indexed
public class Client extends AbstractEntity {
@ManyToOne
@JoinColumn(name = "company_id")
@NotNull
private Company company;
@NotNull
@FullTextField
private Status status;
@Email
@NotEmpty
@FullTextField
private String correoElectronico;
@DecimalMin(value = "0", inclusive = true)
@DecimalMax(value = "999999999999", inclusive = true)
private Long telefono;
@XmlElement(required = true, nillable = true)
@Size(min = 1, max = 500)
@FullTextField
protected String nombreRazonSocial;
@NotNull
@DecimalMin(value = "1", inclusive = true)
@DecimalMax(value = "5", inclusive = true)
protected int codigoTipoDocumentoIdentidad;
@NotNull
@Size(min = 1, max = 20)
protected String numeroDocumento;
@NotNull
@Size(min = 1, max = 500)
protected String direccionComprador;
@NotNull
@Size(min = 1, max = 100)
protected String codigoCliente;
@NotNull
@DecimalMin(value = "1", inclusive = true)
@DecimalMax(value = "212", inclusive = true)
protected int codigoPais;
@NotNull
@DecimalMin(value = "1", inclusive = true)
@DecimalMax(value = "308", inclusive = true)
protected int codigoMetodoPago;
@DecimalMin(value = "0", inclusive = true)
@DecimalMax(value = "9999999999999999", inclusive = true)
protected Long numeroTarjeta;
}

View File

@ -0,0 +1,40 @@
package com.primefactorsolutions.invoices.model;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.validation.constraints.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Type;
import java.util.Map;
@Setter
@Getter
@Entity
public class Company extends AbstractEntity {
@NotNull
@DecimalMin(value = "1", inclusive = true)
@DecimalMax(value = "9999999999999", inclusive = true)
protected long nitEmisor;
@NotNull
@Size(min = 1, max = 200)
protected String razonSocialEmisor;
@NotNull
@Size(min = 1, max = 25)
protected String municipio;
@Size(min = 1, max = 25)
protected String telefono;
@NotNull
@Size(min = 1, max = 500)
protected String direccion;
@Type(JsonType.class)
@Column(columnDefinition = "json")
private Map<String, Object> properties;
}

View File

@ -0,0 +1,25 @@
package com.primefactorsolutions.invoices.model;
import lombok.*;
import java.math.BigDecimal;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CurrencyPair {
private CurrencyType base;
private CurrencyType quote;
// the price that the forex broker will BUY the base currency from you
private BigDecimal bid;
// the price that the broker will SELL you the base currency in exchange for the quote or counter currency
private BigDecimal ask;
public enum CurrencyType {
BOB,
USD,
EUR;
}
}

View File

@ -0,0 +1,25 @@
package com.primefactorsolutions.invoices.model;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Type;
import java.time.Instant;
import java.util.Map;
import java.util.UUID;
@Entity
@Setter
@Getter
public class Event extends AbstractEntity{
private UUID userId;
private UUID entityId;
private Instant timestamp;
@Type(JsonType.class)
@Column(columnDefinition = "json")
private Map<String, Object> properties;
}

View File

@ -0,0 +1,11 @@
package com.primefactorsolutions.invoices.model;
import java.util.List;
public record Help(List<Section> sections) {
public record Section(String name, List<Entry> entries) {
}
public record Entry(String name, String text) {
}
}

View File

@ -0,0 +1,38 @@
package com.primefactorsolutions.invoices.model;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.*;
import jakarta.xml.bind.annotation.XmlElement;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Type;
import java.time.LocalDateTime;
@Setter
@Getter
@Entity
public class Invoice extends AbstractEntity {
@ManyToOne
@JoinColumn(name = "company_id")
@NotNull
private Company company;
@NotNull
private InvoiceStatus status;
@NotNull
protected LocalDateTime fechaEmision;
@Size(min = 1, max = 500)
protected String nombreRazonSocial;
@Type(JsonType.class)
@Column(columnDefinition = "json")
private Object payload;
}

View File

@ -0,0 +1,9 @@
package com.primefactorsolutions.invoices.model;
public enum InvoiceStatus {
DRAFT,
PENDING,
SUBMITTED,
FAILED
}

View File

@ -0,0 +1,70 @@
package com.primefactorsolutions.invoices.model;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.stream.Collectors;
public enum InvoiceType {
FACTURA_ELECTRONICA_COMPRA_VENTA_TASAS,
FACTURA_ELECTRONICA_COMERCIAL_EXPORTACION_MINERA,
FACTURA_ELECTRONICA_IMPORTACION_COMERCIALIZACION_LUBRICANTES_IEHD,
NOTA_ELECTRONICA_CREDITO_DEBITO_DESCUENTO,
FACTURA_ELECTRONICA_SERVICIO_TURISTICO_HOSPEDAJE,
FACTURA_ELECTRONICA_DUTTY_FREE,
FACTURA_ELECTRONICA_SECTOR_EDUCATIVO_Z_F,
FACTURA_ELECTRONICA_COMERCIALIZACION_GN_GLP,
FACTURA_ELECTRONICA_PREVALORADA_S_D,
FACTURA_ELECTRONICA_PREVALORADA,
FACTURA_ELECTRONICA_ALQUILER_BIEN_INMUEBLE,
FACTURA_ELECTRONICA_HIDROCARBURO_ALCANZADA_IEHD,
FACTURA_ELECTRONICA_ENTIDAD_FINANCIERA,
FACTURA_ELECTRONICA_COMERCIALIZACION_GNV,
FACTURA_ELECTRONICA_ZONA_FRANCA,
FACTURA_ELECTRONICA_SERVICIO_BASICO,
NOTA_COMPUTARIZADA_CREDITO_DEBITO,
FACTURA_ELECTRONICA_BOLETO_AEREO,
FACTURA_ELECTRONICA_VENTA_MINERAL,
NOTA_ELECTRONICA_CREDITO_DEBITO_ICE,
FACTURA_ELECTRONICA_HIDROCARBURO_NO_ALCANZADA_IEHD,
FACTURA_ELECTRONICA_IMPORTACION_COMERCIALIZACION_LUBRICANTES,
FACTURA_ELECTRONICA_SERVICIO_BASICO_ZF,
FACTURA_ELECTRONICA_COMPRA_VENTA,
NOTA_COMPUTARIZADA_CREDITO_DEBITO_ICE,
FACTURA_ELECTRONICA_SEGUROS,
FACTURA_ELECTRONICA_VENTA_MINERAL_B_C_B,
FACTURA_ELECTRONICA_COMERCIAL_EXPORTACION,
FACTURA_ELECTRONICA_HOSPITAL_CLINICA_Z_F,
FACTURA_ELECTRONICA_TELECOMUNICACION_Z_F,
FACTURA_ELECTRONICA_TELECOMUNICACION,
FACTURA_ELECTRONICA_HOSPITAL_CLINICA,
FACTURA_ELECTRONICA_SUMINISTRO_ENERGIA,
FACTURA_ELECTRONICA_HOTEL,
FACTURA_ELECTRONICA_ENGARRAFADORAS,
FACTURA_ELECTRONICA_SEGURIDAD_ALIMENTARIA,
NOTA_COMPUTARIZADA_CREDITO_DEBITO_DESCUENTO,
FACTURA_ELECTRONICA_JUEGO_AZAR,
FACTURA_ELECTRONICA_LIBRE_CONSIGNACION,
FACTURA_ELECTRONICA_COMERCIALIZACION_HIDROCARBURO,
FACTURA_ELECTRONICA_COMERCIAL_EXPORTACION_P_VENTA,
FACTURA_ELECTRONICA_COMERCIAL_EXPORTACION_SERVICIO,
FACTURA_ELECTRONICA_TASA_CERO,
NOTA_COMPUTARIZADA_CONCILIACION,
FACTURA_ELECTRONICA_MONEDA_EXTRANJERA,
FACTURA_ELECTRONICA_SECTOR_EDUCATIVO,
FACTURA_ELECTRONICA_COMERCIAL_EXPORTACION_HIDRO,
FACTURA_ELECTRONICA_ALQUILER_Z_F,
FACTURA_ELECTRONICA_ALCANZADA_ICE,
NOTA_ELECTRONICA_CREDITO_DEBITO,
FACTURA_ELECTRONICA_COMPRA_VENTA_BON,
NOTA_ELECTRONICA_CONCILIACION;
public String getDisplayName() {
return Arrays.stream(this.name().replace("FACTURA_ELECTRONICA", "")
.replace("_", " ")
.split(" "))
.map(String::toLowerCase)
.map(StringUtils::capitalize)
.collect(Collectors.joining(" "));
}
}

View File

@ -0,0 +1,33 @@
package com.primefactorsolutions.invoices.model;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Type;
import java.util.Map;
@Entity
@Setter
@Getter
public class Media extends AbstractEntity{
@NotBlank
private String name;
@Lob
private byte[] content;
@Type(JsonType.class)
@Column(columnDefinition = "json")
private Map<String, Object> properties;
@ManyToOne
@JoinColumn(name = "company_id")
@NotNull
private Company company;
}

View File

@ -0,0 +1,10 @@
package com.primefactorsolutions.invoices.model;
public enum POSType {
COMISIONISTA,
VENTANILLA_DE_COBRANZA,
MOVILES,
YPFB,
CAJEROS,
CONJUNTA;
}

View File

@ -0,0 +1,37 @@
package com.primefactorsolutions.invoices.model;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
@Entity
public class PointOfSale extends AbstractEntity {
@ManyToOne
@JoinColumn(name = "company_id")
@NotNull
private Company company;
@NotNull
@DecimalMin(value = "0", inclusive = true)
@DecimalMax(value = "9999", inclusive = true)
protected int codigoSucursal;
@DecimalMin(value = "0", inclusive = true)
@DecimalMax(value = "9999", inclusive = true)
protected Integer codigoPuntoVenta;
protected POSType codigoTipoPuntoVenta;
protected String cuis;
protected String description;
protected String nombrePuntoVenta;
}

View File

@ -0,0 +1,49 @@
package com.primefactorsolutions.invoices.model;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
import java.math.BigDecimal;
@Setter
@Getter
@Entity
@Indexed
public class Product extends AbstractEntity {
@NotNull
@DecimalMin(value = "1", inclusive = true)
@DecimalMax(value = "99999999", inclusive = true)
protected int codigoProductoSin;
@NotNull
@Size(min = 1, max = 50)
protected String codigoProducto;
@NotNull
@Size(min = 1, max = 500)
protected String descripcion;
@NotNull
@DecimalMin(value = "1", inclusive = true)
@DecimalMax(value = "200", inclusive = true)
protected int unidadMedida;
@NotNull
@DecimalMin(value = "0", inclusive = false)
@Digits(integer = 20, fraction = 5)
protected BigDecimal precioUnitario;
@ManyToOne
@JoinColumn(name = "company_id")
@NotNull
private Company company;
@NotNull
private Status status;
}

View File

@ -0,0 +1,6 @@
package com.primefactorsolutions.invoices.model;
public enum Role {
ADMIN,
REGULAR
}

View File

@ -0,0 +1,6 @@
package com.primefactorsolutions.invoices.model;
public enum Status {
ACTIVE,
DISABLED
}

View File

@ -0,0 +1,25 @@
package com.primefactorsolutions.invoices.model;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Type;
import java.math.BigInteger;
import java.util.Map;
@Entity
@Setter
@Getter
public class Task extends AbstractEntity{
@NotBlank
private BigInteger nitEmisor;
@Type(JsonType.class)
@Column(columnDefinition = "json")
private Map<String, Object> properties;
}

View File

@ -0,0 +1,31 @@
package com.primefactorsolutions.invoices.model;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "user_")
@Data
public class User extends AbstractEntity {
@NotBlank
private String email;
@NotBlank
private String fullName;
private Role role;
@NotNull
private Status status;
@ManyToOne
@JoinColumn(name = "company_id")
@NotNull
private Company company;
}

View File

@ -0,0 +1,20 @@
package com.primefactorsolutions.invoices.model.mappers;
import com.primefactorsolutions.invoices.beans.CabeceraDTO;
import com.primefactorsolutions.invoices.beans.ClientDTO;
import com.primefactorsolutions.invoices.beans.TotalesDTO;
import com.primefactorsolutions.invoices.model.Company;
import com.primefactorsolutions.invoices.xsd.FacturaComputarizadaComercialExportacionServicio;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface CabeceraMapper {
CabeceraMapper INSTANCE = Mappers.getMapper(CabeceraMapper.class);
FacturaComputarizadaComercialExportacionServicio.Cabecera cabeceraFromDtos(CabeceraDTO cabeceraDTO,
ClientDTO clientDTO,
TotalesDTO totalesDTO);
CabeceraDTO cabeceraDtoFromCompany(Company company);
}

View File

@ -0,0 +1,14 @@
package com.primefactorsolutions.invoices.model.mappers;
import com.primefactorsolutions.invoices.beans.ClientDTO;
import com.primefactorsolutions.invoices.model.Client;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface ClientMapper {
ClientMapper INSTANCE = Mappers.getMapper(ClientMapper.class);
ClientDTO clientDtoFromClient(Client client);
}

View File

@ -0,0 +1,14 @@
package com.primefactorsolutions.invoices.model.mappers;
import com.primefactorsolutions.invoices.beans.DetalleDTO;
import com.primefactorsolutions.invoices.xsd.FacturaComputarizadaComercialExportacionServicio;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface DetalleMapper {
DetalleMapper INSTANCE = Mappers.getMapper(DetalleMapper.class);
FacturaComputarizadaComercialExportacionServicio.Detalle detalleFromDto(DetalleDTO detalleDTO);
}

View File

@ -0,0 +1,14 @@
package com.primefactorsolutions.invoices.model.validators;
import com.google.common.collect.Lists;
import com.primefactorsolutions.invoices.xsd.FacturaComputarizadaComercialExportacionServicio;
import java.util.List;
public class FacturaComputarizadaComercialExportacionServicioValidator implements InvoiceValidator<FacturaComputarizadaComercialExportacionServicio> {
@Override
public List<ValidationError> validate(FacturaComputarizadaComercialExportacionServicio invoice) {
return Lists.newArrayList();
}
}

View File

@ -0,0 +1,7 @@
package com.primefactorsolutions.invoices.model.validators;
import java.util.List;
public interface InvoiceValidator<T> {
List<ValidationError> validate(T invoice);
}

View File

@ -0,0 +1,4 @@
package com.primefactorsolutions.invoices.model.validators;
public record ValidationError(String field, String message) {
}

View File

@ -0,0 +1,25 @@
package com.primefactorsolutions.invoices.security;
import com.vaadin.flow.spring.security.AuthenticationContext;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class SecurityService {
private final AuthenticationContext authenticationContext;
public SecurityService(AuthenticationContext authenticationContext) {
this.authenticationContext = authenticationContext;
}
public Optional<UserDetails> getAuthenticatedUser() {
return authenticationContext.getAuthenticatedUser(UserDetails.class);
}
public void logout() {
authenticationContext.logout();
}
}

View File

@ -0,0 +1,64 @@
package com.primefactorsolutions.invoices.services;
import com.primefactorsolutions.invoices.data.ClientRepository;
import com.primefactorsolutions.invoices.model.Client;
import com.primefactorsolutions.invoices.model.Status;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.apache.logging.log4j.util.Strings;
import org.hibernate.search.engine.search.query.SearchResult;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
@Service
@Data
@Log4j2
public class ClientService {
private final ClientRepository clientRepository;
private final EntityManager entityManager;
private final CompanyService companyService;
@Transactional
public List<Client> findAllClients(String code) {
return findAllClients(code, PageRequest.of(0, 25));
}
@Transactional
public List<Client> findAllClients(String code, PageRequest of) {
if (Strings.isBlank(code)) {
return clientRepository.findAll();
}
SearchSession searchSession = Search.session(entityManager);
SearchResult<Client> result = searchSession.search(Client.class)
.where(f -> f.match()
.fields("nombreRazonSocial", "correoElectronico")
.matching(code))
.fetch(20);
long totalHitCount = result.total().hitCount();
log.info("Found {} results", totalHitCount);
return result.hits();
}
public void saveOrUpdateClient(Client updatedClient) {
updatedClient.setCompany(companyService.getCompany());
updatedClient.setStatus(Status.ACTIVE);
clientRepository.save(updatedClient);
}
public Client getClient(UUID uuid) {
return clientRepository.findById(uuid)
.orElseThrow(() -> new IllegalArgumentException("Client not found"));
}
}

View File

@ -0,0 +1,20 @@
package com.primefactorsolutions.invoices.services;
import com.primefactorsolutions.invoices.data.CompanyRepository;
import com.primefactorsolutions.invoices.model.Company;
import lombok.Data;
import org.springframework.stereotype.Service;
@Service
@Data
public class CompanyService {
private final CompanyRepository companyRepository;
public Company getCompany() {
return companyRepository.findAll().stream().findFirst().get();
}
public void saveOrUpdateCompany(final Company company) {
companyRepository.save(company);
}
}

View File

@ -0,0 +1,125 @@
package com.primefactorsolutions.invoices.services;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.openhtmltopdf.pdfboxout.PdfBoxRenderer;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import com.primefactorsolutions.invoices.data.InvoiceRepository;
import com.primefactorsolutions.invoices.data.MediaRepository;
import com.primefactorsolutions.invoices.model.Help;
import com.primefactorsolutions.invoices.model.Invoice;
import com.primefactorsolutions.invoices.model.InvoiceStatus;
import com.primefactorsolutions.invoices.model.Media;
import com.primefactorsolutions.invoices.xsd.FacturaComputarizadaComercialExportacionServicio;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import jakarta.transaction.Transactional;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.TimeZone;
@Service
@Data
@Log4j2
public class InvoiceService {
private final Help help;
private final InvoiceRepository invoiceRepository;
private final MediaRepository mediaRepository;
private final CompanyService companyService;
private final ObjectMapper objectMapper;
public List<Invoice> findAllInvoices() {
return invoiceRepository.findAll();
}
@Transactional
public void sendInvoice(FacturaComputarizadaComercialExportacionServicio factura) {
try(var os = new ByteArrayOutputStream()) {
writeAsPdf(factura, os);
var media = new Media();
media.setName("factura.pdf");
media.setContent(os.toByteArray());
mediaRepository.save(media);
} catch (IOException e) {
// no-op
}
var invoice = new Invoice();
invoice.setStatus(InvoiceStatus.DRAFT);
invoice.setFechaEmision(LocalDateTime.ofInstant(Instant.now(), ZoneId.of("UTC")));
invoice.setCompany(companyService.getCompany());
invoice.setNombreRazonSocial(factura.getCabecera().getNombreRazonSocial());
try {
invoice.setPayload(objectMapper.writeValueAsString(factura));
} catch (JsonProcessingException e) {
log.info("Error writing invoice JSON");
}
invoiceRepository.save(invoice);
}
protected static InputStream getTemplate() {
return getDefaultTemplate();
}
public static InputStream getDefaultTemplate() {
return Invoice.class.getResourceAsStream("/pfs-invoice.html");
}
public void writeAsPdf(FacturaComputarizadaComercialExportacionServicio factura, OutputStream out) {
try {
var in = getTemplate();
Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
// cfg.setDirectoryForTemplateLoading(new File("/where/you/store/templates"));
// Recommended settings for new projects:
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setLogTemplateExceptions(false);
cfg.setWrapUncheckedExceptions(true);
cfg.setFallbackOnNullLoopVariable(false);
cfg.setSQLDateAndTimeTimeZone(TimeZone.getDefault());
Reader reader = new InputStreamReader(in);
Template temp = new Template("pfs-invoice", reader, cfg);
DefaultObjectWrapper wrapper = new DefaultObjectWrapper();
ByteArrayOutputStream oo = new ByteArrayOutputStream();
Writer outTemplate = new OutputStreamWriter(oo);
temp.process(wrapper.wrap(factura), outTemplate);
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.usePDDocument(new PDDocument(MemoryUsageSetting.setupMixed(1000000)));
builder.withHtmlContent(oo.toString(StandardCharsets.UTF_8), "/test");
builder.toStream(out);
try (PdfBoxRenderer pdfBoxRenderer = builder.buildPdfRenderer()) {
pdfBoxRenderer.layout();
pdfBoxRenderer.createPDF();
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public Help getHelp() {
return help;
}
}

View File

@ -0,0 +1,26 @@
package com.primefactorsolutions.invoices.services;
import com.primefactorsolutions.invoices.data.MediaRepository;
import com.primefactorsolutions.invoices.model.Media;
import lombok.Data;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@Data
public class MediaService {
private final MediaRepository mediaRepository;
public Media findMedia(String name) {
return mediaRepository.findAll().stream().findFirst().get();
}
public List<Media> findAllMedia(String name) {
return mediaRepository.findByNameLike(name);
}
public void saveMedia(Media media) {
mediaRepository.save(media);
}
}

View File

@ -0,0 +1,32 @@
package com.primefactorsolutions.invoices.services;
import com.primefactorsolutions.invoices.data.ProductRepository;
import com.primefactorsolutions.invoices.model.Product;
import com.primefactorsolutions.invoices.model.Status;
import lombok.Data;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
@Service
@Data
public class ProductService {
private final ProductRepository productRepository;
private final CompanyService companyService;
public List<Product> findAllProducts(String name) {
return productRepository.findAll();
}
public void saveOrUpdateProduct(Product product) {
var company = companyService.getCompany();
product.setCompany(company);
product.setStatus(Status.ACTIVE);
productRepository.save(product);
}
public Product getProduct(UUID uuid) {
return productRepository.findById(uuid).get();
}
}

View File

@ -0,0 +1,37 @@
package com.primefactorsolutions.invoices.services;
import com.primefactorsolutions.invoices.data.UserRepository;
import com.primefactorsolutions.invoices.model.Role;
import com.primefactorsolutions.invoices.model.Status;
import com.primefactorsolutions.invoices.model.User;
import lombok.Data;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
@Service
@Data
public class UserService {
private final UserRepository userRepository;
private final CompanyService companyService;
public List<User> findAllUsers(String code) {
return userRepository.findAll();
}
public User getUser(UUID id) {
return userRepository.findById(id).get();
}
public void saveOrUpdateUser(User updatedUser) {
if (updatedUser.getId() == null) {
var company = companyService.getCompany();
updatedUser.setCompany(company);
updatedUser.setRole(Role.REGULAR);
updatedUser.setStatus(Status.ACTIVE);
}
userRepository.save(updatedUser);
}
}

View File

@ -0,0 +1,29 @@
package com.primefactorsolutions.invoices.utils;
import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@UtilityClass
public class TextUtils {
private static final Pattern WORD_FINDER = Pattern.compile("(([A-Z]?[a-z]+)|([A-Z]))");
public static String makeLabel(final String text) {
return findWordsInMixedCase(text).stream()
.map(StringUtils::capitalize)
.collect(Collectors.joining(" "));
}
private static List<String> findWordsInMixedCase(final String text) {
var matcher = WORD_FINDER.matcher(text);
var words = new ArrayList<String>();
while (matcher.find()) {
words.add(matcher.group(0));
}
return words;
}
}

View File

@ -0,0 +1,12 @@
package com.primefactorsolutions.invoices.utils;
import com.vaadin.flow.component.Component;
import lombok.experimental.UtilityClass;
@UtilityClass
public class UiUtils {
public static void goTo(Component component, String location) {
component.getUI().ifPresent(ui -> ui.navigate(location));
}
}

View File

@ -0,0 +1,8 @@
package com.primefactorsolutions.invoices.views;
public interface Bindable<T> {
void setBean(Object bean);
T getBean();
}

View File

@ -0,0 +1,72 @@
package com.primefactorsolutions.invoices.views;
import com.primefactorsolutions.invoices.model.Client;
import com.primefactorsolutions.invoices.services.ClientService;
import com.primefactorsolutions.invoices.views.component.Breadcrumbs;
import com.primefactorsolutions.invoices.views.component.GenericForm;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.i18n.I18NProvider;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.context.annotation.Scope;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import static com.primefactorsolutions.invoices.utils.UiUtils.goTo;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = "clients", layout = MainLayout.class)
@PageTitle("Client | PFS Facturacion")
public class ClientEditView extends VerticalLayout implements HasUrlParameter<String> {
ClientService clientService;
I18NProvider i18NProvider;
GenericForm<Client> clientGenericForm;
public ClientEditView(ClientService clientService, I18NProvider i18NProvider) {
this.clientService = clientService;
this.i18NProvider = i18NProvider;
var client = new Client();
this.clientGenericForm = new GenericForm<>(Client.class);
this.clientGenericForm.setBean(client);
String text = this.i18NProvider.getTranslation("action.save", Locale.of("es"));
var saveButton = new Button(text);
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
saveButton.addClickListener(c -> {
var updatedClient = clientGenericForm.getBean();
clientService.saveOrUpdateClient(updatedClient);
goTo(this, "clients");
});
var cancelButton = new Button("Cancel");
cancelButton.addClickListener(c -> goTo(this, "clients"));
var breadcrumbs = new Breadcrumbs(List.of(Pair.of("Clientes", "clients"), Pair.of("Editar", null)));
var buttonLayout = new HorizontalLayout(saveButton, cancelButton);
add(breadcrumbs, clientGenericForm, buttonLayout);
}
@Override
public void setParameter(BeforeEvent beforeEvent, String s) {
if (StringUtils.isNotBlank(s) && !"new".equals(s)) {
var product = clientService.getClient(UUID.fromString(s));
clientGenericForm.setBean(product);
}
}
}

View File

@ -0,0 +1,101 @@
package com.primefactorsolutions.invoices.views;
import com.primefactorsolutions.invoices.model.Client;
import com.primefactorsolutions.invoices.model.InvoiceType;
import com.primefactorsolutions.invoices.model.Status;
import com.primefactorsolutions.invoices.services.ClientService;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.H4;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.PagingGrid;
import static com.primefactorsolutions.invoices.utils.UiUtils.goTo;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = "clients", layout = MainLayout.class)
@PageTitle("Facturas | PFS Facturacion")
public class ClientListView extends VerticalLayout {
PagingGrid<Client> grid = new PagingGrid<>(Client.class);
TextField filterText = new TextField();
ClientService clientService;
public ClientListView(ClientService clientService) {
this.clientService = clientService;
addClassName("pfs-list-view");
setSizeFull();
configureGrid();
add(getTitle(), getToolbar(), getContent());
updateList();
}
private Component getTitle() {
return new H4("Clientes");
}
private HorizontalLayout getContent() {
HorizontalLayout content = new HorizontalLayout(grid);
content.setFlexGrow(2, grid);
// content.setFlexGrow(1, form);
content.addClassNames("content");
content.setSizeFull();
return content;
}
private void configureGrid() {
grid.addClassNames("pfs-grid");
grid.setSizeFull();
grid.setColumns("correoElectronico", "telefono");
grid.addColumn(new ComponentRenderer<>(Anchor::new, (a, p) -> {
a.setHref("clients/" + p.getId());
a.setText(p.getNombreRazonSocial());
})).setHeader("nombreRazonSocial");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
grid.setPagingDataProvider((l, i) -> clientService.findAllClients(""));
var clients = clientService.findAllClients("");
grid.setItems(clients);
grid.setPageSize(25);
}
private Component getToolbar() {
filterText.setPlaceholder("Buscar ...");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList());
var comboBox = new ComboBox<Status>();
comboBox.setItems(Status.values());
comboBox.setItemLabelGenerator(Status::name);
comboBox.setValue(Status.ACTIVE);
add(comboBox);
var addClientButton = new Button("Nuevo Cliente");
addClientButton.addClickListener(click -> goTo(this, "clients/new"));
var addComponent = new HorizontalLayout(comboBox, addClientButton);
var toolbar = new HorizontalLayout(filterText, addComponent);
toolbar.addClassName("toolbar");
return toolbar;
}
private void updateList() {
grid.setItems(clientService.findAllClients(filterText.getValue()));
}
}

View File

@ -0,0 +1,86 @@
package com.primefactorsolutions.invoices.views;
import com.google.common.collect.Lists;
import com.primefactorsolutions.invoices.model.Company;
import com.primefactorsolutions.invoices.model.CurrencyPair;
import com.primefactorsolutions.invoices.services.CompanyService;
import com.primefactorsolutions.invoices.views.component.GenericForm;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.H4;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.BigDecimalField;
import com.vaadin.flow.i18n.I18NProvider;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.checkerframework.checker.units.qual.C;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.textfield.VBigDecimalField;
import org.vaadin.firitin.fields.ElementCollectionField;
import org.vaadin.firitin.fields.EnumSelect;
import java.math.BigDecimal;
import java.util.List;
import java.util.Locale;
import static com.primefactorsolutions.invoices.utils.UiUtils.goTo;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = "company-config", layout = MainLayout.class)
@PageTitle("Compania | PFS Facturacion")
public class CompanyEditView extends VerticalLayout {
CompanyService companyService;
public CompanyEditView(CompanyService companyService, I18NProvider i18NProvider) {
this.companyService = companyService;
var company = this.companyService.getCompany();
var companyGenericForm = new GenericForm<>(Company.class);
companyGenericForm.setBean(company);
String text = i18NProvider.getTranslation("action.save", Locale.of("es"));
var saveButton = new Button(text);
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
saveButton.addClickListener(c -> {
var updatedCompany = companyGenericForm.getBean();
companyService.saveOrUpdateCompany(updatedCompany);
});
var cancelButton = new Button("Cancel");
cancelButton.addClickListener(c -> goTo(this, ""));
var buttonLayout = new HorizontalLayout(saveButton, cancelButton);
var propForm = getCurrencyForm();
add(getTitle(), companyGenericForm, propForm, buttonLayout);
}
private Component getTitle() {
return new H4("Configuracion");
}
private Component getCurrencyForm() {
ElementCollectionField<CurrencyPair> field = new ElementCollectionField<>(CurrencyPair.class, CurrencyEditor.class)
.withEditorInstantiator(CurrencyEditor::new);
var cp = new CurrencyPair(CurrencyPair.CurrencyType.BOB, CurrencyPair.CurrencyType.USD, BigDecimal.valueOf(7.0), BigDecimal.valueOf(6.97));
field.setValue(Lists.newArrayList(cp));
return field;
}
public static class CurrencyEditor {
EnumSelect<CurrencyPair.CurrencyType> base = new EnumSelect<>(CurrencyPair.CurrencyType.class);
EnumSelect<CurrencyPair.CurrencyType> quote = new EnumSelect<>(CurrencyPair.CurrencyType.class);
BigDecimalField bid = new BigDecimalField();
BigDecimalField ask = new BigDecimalField();
}
}

View File

@ -0,0 +1,48 @@
package com.primefactorsolutions.invoices.views;
import com.vaadin.flow.component.ScrollOptions;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.server.ErrorEvent;
import com.vaadin.flow.server.ErrorHandler;
import lombok.extern.log4j.Log4j2;
import org.aspectj.weaver.ast.Not;
import org.springframework.stereotype.Component;
@Log4j2
public class CustomErrorHandler implements ErrorHandler {
@Override
public void error(ErrorEvent errorEvent) {
log.error("Something wrong happened", errorEvent.getThrowable());
if(UI.getCurrent() != null) {
UI.getCurrent().access(() -> {
var notification = new Notification();
notification.addThemeVariants(NotificationVariant.LUMO_ERROR);
notification.setPosition(Notification.Position.TOP_CENTER);
notification.setDuration(0);
var layout = new HorizontalLayout(new Text("An internal error has occurred." +
"Contact support for assistance."), new CloseButton());
layout.setAlignItems(FlexComponent.Alignment.CENTER);
notification.add(layout);
notification.open();
});
}
}
public static class CloseButton extends Button {
public CloseButton() {
super(new Icon("lumo", "cross"));
addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE);
setAriaLabel("Close");
addClickListener(e -> findAncestor(Notification.class).close());
}
}
}

View File

@ -0,0 +1,266 @@
package com.primefactorsolutions.invoices.views;
import com.primefactorsolutions.invoices.beans.CabeceraDTO;
import com.primefactorsolutions.invoices.beans.ClientDTO;
import com.primefactorsolutions.invoices.beans.DetalleDTO;
import com.primefactorsolutions.invoices.beans.TotalesDTO;
import com.primefactorsolutions.invoices.model.mappers.CabeceraMapper;
import com.primefactorsolutions.invoices.model.mappers.ClientMapper;
import com.primefactorsolutions.invoices.model.mappers.DetalleMapper;
import com.primefactorsolutions.invoices.services.ClientService;
import com.primefactorsolutions.invoices.services.CompanyService;
import com.primefactorsolutions.invoices.services.InvoiceService;
import com.primefactorsolutions.invoices.views.component.invoice.*;
import com.primefactorsolutions.invoices.xsd.FacturaComputarizadaComercialExportacionServicio;
import com.vaadin.flow.component.*;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.contextmenu.MenuItem;
import com.vaadin.flow.component.details.Details;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.html.*;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.menubar.MenuBar;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.Scope;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Stream;
import static com.primefactorsolutions.invoices.utils.TextUtils.makeLabel;
import static com.primefactorsolutions.invoices.utils.UiUtils.goTo;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = "edit-invoice", layout = MainLayout.class)
@PageTitle("Factura | Editar")
public class FacturaComputarizadaComercialExportacionesServicioEditView extends VerticalLayout {
private final CabeceraForm<CabeceraDTO> cabeceraForm;
private final ClientForm<ClientDTO> clientForm;
private final TotalesForm<TotalesDTO> totalesForm;
private final DetallesForm<DetalleDTO> detalleForm;
private final Button save = new Button("Crear");
private final Button close = new Button("Cancelar");
private final Dialog dialog = new Dialog();
private final InvoiceService invoiceService;
private final ClientService clientService;
private final CompanyService companyService;
public FacturaComputarizadaComercialExportacionesServicioEditView(
InvoiceService invoiceService,
ClientService clientService,
CompanyService companyService) {
this.invoiceService = invoiceService;
this.clientService = clientService;
this.companyService = companyService;
var company = companyService.getCompany();
var cabeceraDTO = CabeceraMapper.INSTANCE.cabeceraDtoFromCompany(company);
this.cabeceraForm = new CabeceraForm<>(CabeceraDTO.class);
this.cabeceraForm.binder.setBean(cabeceraDTO);
var clientDTO = new ClientDTO();
this.clientForm = new ClientForm<>(ClientDTO.class);
this.clientForm.binder.setBean(clientDTO);
this.detalleForm = new DetallesForm<>(DetalleDTO.class, DetalleDTO::new);
var totalesDTO = new TotalesDTO();
totalesDTO.setInformacionAdicional("Ninguno");
totalesDTO.setDescuentoAdicional(BigDecimal.valueOf(0));
this.totalesForm = new TotalesForm<>(TotalesDTO.class);
this.totalesForm.binder.setBean(totalesDTO);
var emisorDetails = getEmisorDetails(cabeceraDTO);
var clienteDetails = getClienteDetails();
var detalleDetails = getDetalleDetails();
add(createMenu(), emisorDetails, clienteDetails, detalleDetails, createButtonsLayout());
createHelp();
}
@NotNull
private Details getDetalleDetails() {
var detalleSummary = new HorizontalLayout();
detalleSummary.setSpacing(false);
detalleSummary.add(new Text("Detalle"));
var content = new VerticalLayout();
var detalleDetails = new Details(detalleSummary, content);
var addButton = new Button("Agregar Detalle");
addButton.addClickListener(e -> this.detalleForm.addDetalle());
content.add(this.detalleForm);
content.add(addButton);
content.add(new Text("Totales"));
content.add(this.totalesForm);
detalleDetails.setOpened(true);
this.detalleForm.addChangeListener(c -> {
var values = this.detalleForm.getValues();
var currentTotales = this.totalesForm.binder.getBean();
var total = values.stream()
.map(DetalleDTO::getSubTotal)
.map(b -> b == null ? new BigDecimal(0) : b)
.reduce(BigDecimal::add).orElse(new BigDecimal(0));
System.out.println(">>>>>>>>" + total);
currentTotales.setMontoTotalMoneda(total);
currentTotales.setMontoTotal((total.subtract(currentTotales.getDescuentoAdicional()))
.multiply(BigDecimal.valueOf(7.0)));
currentTotales.setMontoTotalSujetoIva(currentTotales.getMontoTotal().subtract(currentTotales.getDescuentoAdicional()));
this.totalesForm.binder.setBean(currentTotales);
});
return detalleDetails;
}
@NotNull
private Details getClienteDetails() {
ClientInputForm clientInputForm = new ClientInputForm(clientService);
clientInputForm.addListener(c -> {
var selectedClientDTO = ClientMapper.INSTANCE.clientDtoFromClient(c);
this.clientForm.binder.setBean(selectedClientDTO);
this.clientForm.setVisible(true);
});
var clienteText = new Text("Cliente");
var clienteSummary = new HorizontalLayout();
clienteSummary.setSpacing(false);
clienteSummary.add(clienteText);
var clientPanel = new VerticalLayout();
clientPanel.add(clientInputForm);
clientPanel.add(this.clientForm);
var clienteDetails = new Details(clienteSummary, clientPanel);
clienteDetails.setOpened(true);
clienteDetails.addOpenedChangeListener(e -> {
if (e.isOpened()) {
clienteText.setText("Cliente");
} else {
var clienteDTO = clientForm.binder.getBean();
clienteText.setText("Cliente (" + clienteDTO.getNombreRazonSocial() + ")");
}
});
var clienteButtons = new HorizontalLayout();
var clienteOkButton = new Button("OK");
clienteButtons.addClickListener(c -> clienteDetails.setOpened(false));
clienteButtons.add(clienteOkButton);
clientPanel.add(clienteButtons);
return clienteDetails;
}
@NotNull
private Details getEmisorDetails(CabeceraDTO cabeceraDTO) {
var emisorText = new Text("Emisor");
var emisorSummary = new HorizontalLayout();
emisorSummary.setSpacing(false);
emisorSummary.add(emisorText);
var content = new VerticalLayout();
var emisorDetails = new Details(emisorSummary, content);
emisorDetails.setOpened(true);
emisorDetails.addOpenedChangeListener(e -> {
if (e.isOpened()) {
emisorText.setText("Emisor");
} else {
emisorText.setText("Emisor (" + cabeceraDTO.getRazonSocialEmisor() + ")");
}
});
var emisorButtons = new HorizontalLayout();
var emisorOkButton = new Button("OK");
emisorButtons.addClickListener(c -> emisorDetails.setOpened(false));
emisorButtons.add(emisorOkButton);
content.add(this.cabeceraForm, emisorButtons);
return emisorDetails;
}
private Component createButtonsLayout() {
this.save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
this.close.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
this.save.addClickListener(e -> {
var factura = new FacturaComputarizadaComercialExportacionServicio();
var cabecera = CabeceraMapper.INSTANCE.cabeceraFromDtos(this.cabeceraForm.binder.getBean(),
this.clientForm.binder.getBean(),
this.totalesForm.binder.getBean());
var detalles = detalleForm.getValues().stream()
.map(DetalleMapper.INSTANCE::detalleFromDto)
.toList();
factura.setCabecera(cabecera);
factura.setDetalle(detalles);
this.invoiceService.sendInvoice(factura);
goTo(this, "invoice-pdf");
});
this.close.addClickShortcut(Key.ESCAPE);
this.close.addClickListener(click -> goTo(this, ""));
return new HorizontalLayout(this.save, this.close);
}
private Component createMenu() {
ComponentEventListener<ClickEvent<MenuItem>> listener = e -> {
if (e.getSource().getText().equals("Atras")) {
goTo(this, "");
} else if (e.getSource().getText().equals("Ayuda")) {
this.dialog.open();
}
};
var menuBar = new MenuBar();
menuBar.addItem("Atras", listener);
menuBar.addItem("Ayuda", listener);
return menuBar;
}
private void createHelp() {
this.dialog.setHeaderTitle("Ayuda");
var closeButton = new Button(new Icon("lumo", "cross"),
(e) -> this.dialog.close());
closeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
this.dialog.getHeader().add(closeButton);
var help = this.invoiceService.getHelp();
var ol = new Div();
help.sections().forEach(s -> {
var section = new Section();
var descriptionList = new DescriptionList();
s.entries().forEach(e -> {
descriptionList.add(new DescriptionList.Term(makeLabel(e.name())));
descriptionList.add(new DescriptionList.Description(e.text()));
});
section.add(new H4(makeLabel(s.name())));
section.add(descriptionList);
ol.add(section);
});
this.dialog.add(ol);
}
}

View File

@ -0,0 +1,25 @@
package com.primefactorsolutions.invoices.views;
import com.vaadin.flow.server.ServiceInitEvent;
import com.vaadin.flow.server.VaadinServiceInitListener;
import com.vaadin.flow.server.VaadinSession;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;
@Component
@Log4j2
public class InitListener implements VaadinServiceInitListener {
@Override
public void serviceInit(ServiceInitEvent event) {
event.getSource().addSessionInitListener(
initEvent -> {
log.info("A new Session has been initialized!");
VaadinSession.getCurrent().setErrorHandler(new CustomErrorHandler());
});
event.getSource().addUIInitListener(
initEvent -> log.info("A new UI has been initialized!"));
}
}

View File

@ -0,0 +1,111 @@
package com.primefactorsolutions.invoices.views;
import com.primefactorsolutions.invoices.model.Invoice;
import com.primefactorsolutions.invoices.model.InvoiceType;
import com.primefactorsolutions.invoices.services.InvoiceService;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.H4;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.PagingGrid;
import java.util.List;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = "", layout = MainLayout.class)
@PageTitle("Facturas | PFS Facturacion")
public class InvoiceListView extends VerticalLayout {
PagingGrid<Invoice> grid = new PagingGrid<>(Invoice.class);
TextField filterText = new TextField();
InvoiceService invoiceService;
public InvoiceListView(InvoiceService invoiceService) {
this.invoiceService = invoiceService;
addClassName("list-view");
setSizeFull();
configureGrid();
add(getTitle(), getToolbar(), getContent());
updateList();
}
private Component getTitle() {
return new H4("Facturas");
}
private HorizontalLayout getContent() {
HorizontalLayout content = new HorizontalLayout(grid);
content.setFlexGrow(2, grid);
// content.setFlexGrow(1, form);
content.addClassNames("content");
content.setSizeFull();
return content;
}
private void configureGrid() {
grid.addClassNames("invoice-grid");
grid.setSizeFull();
grid.setColumns("nombreRazonSocial", "fechaEmision");
grid.addColumn(
new ComponentRenderer<>(Button::new, (button, invoice) -> {
button.addThemeVariants(ButtonVariant.LUMO_ICON, ButtonVariant.LUMO_TERTIARY);
button.setIcon(new Icon(VaadinIcon.CHECK));
})).setHeader("status");
//grid.addColumn(contact -> contact.getStatus().getName()).setHeader("Status");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
grid.setPagingDataProvider((l, i) -> invoiceService.findAllInvoices());
List<Invoice> invoices = invoiceService.findAllInvoices();
grid.setItems(invoices);
grid.setPageSize(25);
}
private Component getToolbar() {
filterText.setPlaceholder("Buscar ...");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList());
var comboBox = new ComboBox<InvoiceType>();
comboBox.getStyle().set("--vaadin-combo-box-overlay-width", "350px");
comboBox.setItems(InvoiceType.values());
comboBox.setItemLabelGenerator(InvoiceType::getDisplayName);
add(comboBox);
var addInvoiceButton = new Button("Nueva Factura");
// addContactButton.addClickListener(click -> addContact());
addInvoiceButton.addClickListener(click -> {
this.getUI().ifPresent(ui -> ui.navigate("edit-invoice"));
});
var addComponent = new HorizontalLayout(comboBox, addInvoiceButton);
var toolbar = new HorizontalLayout(filterText, addComponent);
toolbar.addClassName("toolbar");
return toolbar;
}
private void updateList() {
grid.setItems(invoiceService.findAllInvoices());
}
}

View File

@ -0,0 +1,38 @@
package com.primefactorsolutions.invoices.views;
import com.primefactorsolutions.invoices.services.MediaService;
import com.vaadin.componentfactory.pdfviewer.PdfViewer;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamResource;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import java.io.ByteArrayInputStream;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = "invoice-pdf", layout = MainLayout.class)
@PageTitle("Vista | PFS Facturacion")
public class InvoicePdfView extends Div {
public InvoicePdfView(MediaService mediaService) {
var media = mediaService.findMedia("foo.pdf");
var content = media.getContent();
var pdfViewer = new PdfViewer();
pdfViewer.setSizeFull();
var resource = new StreamResource("factura.pdf",
() -> new ByteArrayInputStream(content));
pdfViewer.setSrc(resource);
getStyle().set("height", "100%");
getStyle().set("overflow", "hidden");
getStyle().set("display", "flex");
getStyle().set("flex-direction", "column");
add(pdfViewer);
}
}

View File

@ -0,0 +1,41 @@
package com.primefactorsolutions.invoices.views;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.login.LoginForm;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.auth.AnonymousAllowed;
@Route("login")
@PageTitle("Connexion | PFS Facturacion")
@AnonymousAllowed
public class LoginView extends VerticalLayout implements BeforeEnterObserver {
private final LoginForm login = new LoginForm();
public LoginView(){
addClassName("login-view");
setSizeFull();
setAlignItems(Alignment.CENTER);
setJustifyContentMode(JustifyContentMode.CENTER);
login.setAction("login");
add(new H1("PFS Facturacion"));
add(login);
}
@Override
public void beforeEnter(final BeforeEnterEvent beforeEnterEvent) {
// inform the user about an authentication error
if(beforeEnterEvent.getLocation()
.getQueryParameters()
.getParameters()
.containsKey("error")) {
login.setError(true);
}
}
}

View File

@ -0,0 +1,53 @@
package com.primefactorsolutions.invoices.views;
import com.primefactorsolutions.invoices.security.SecurityService;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.RouterLink;
import com.vaadin.flow.theme.lumo.LumoUtility;
import org.springframework.security.core.userdetails.UserDetails;
public class MainLayout extends AppLayout {
private final SecurityService securityService;
public MainLayout(SecurityService securityService) {
this.securityService = securityService;
createHeader();
createDrawer();
}
private void createHeader() {
var logo = new H1("PFS Facturacion");
logo.addClassNames(
LumoUtility.FontSize.LARGE,
LumoUtility.Margin.MEDIUM);
var u = securityService.getAuthenticatedUser().map(UserDetails::getUsername).orElse("");
var logout = new Button("Desconectar " + u, e -> securityService.logout());
var header = new HorizontalLayout(new DrawerToggle(), logo, logout);
header.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
header.expand(logo);
header.setWidthFull();
header.addClassNames(
LumoUtility.Padding.Vertical.NONE,
LumoUtility.Padding.Horizontal.MEDIUM);
addToNavbar(header);
}
private void createDrawer() {
addToDrawer(new VerticalLayout(
new RouterLink("Facturas", InvoiceListView.class),
new RouterLink("Clientes", ClientListView.class),
new RouterLink("Productos", ProductListView.class),
new RouterLink("Configuracion", CompanyEditView.class),
new RouterLink("Usuarios", UserListView.class)
));
}
}

View File

@ -0,0 +1,74 @@
package com.primefactorsolutions.invoices.views;
import com.primefactorsolutions.invoices.model.Product;
import com.primefactorsolutions.invoices.services.ProductService;
import com.primefactorsolutions.invoices.views.component.Breadcrumbs;
import com.primefactorsolutions.invoices.views.component.GenericForm;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.i18n.I18NProvider;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.context.annotation.Scope;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import static com.primefactorsolutions.invoices.utils.UiUtils.goTo;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = "product-edit", layout = MainLayout.class)
@PageTitle("Product | PFS Facturacion")
public class ProductEditView extends VerticalLayout implements HasUrlParameter<String> {
ProductService productService;
I18NProvider i18NProvider;
GenericForm<Product> productGenericForm;
public ProductEditView(ProductService productService, I18NProvider i18NProvider) {
this.productService = productService;
this.i18NProvider = i18NProvider;
var product = new Product();
this.productGenericForm = new GenericForm<>(Product.class);
this.productGenericForm.setBean(product);
String text = this.i18NProvider.getTranslation("action.save", Locale.of("es"));
var saveButton = new Button(text);
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
saveButton.addClickListener(c -> {
var updatedProduct = productGenericForm.getBean();
productService.saveOrUpdateProduct(updatedProduct);
goTo(this, "products");
});
var cancelButton = new Button("Cancel");
cancelButton.addClickListener(c -> {
goTo(this, "products");
});
var breadcrumbs = new Breadcrumbs(List.of(Pair.of("Products", "products"), Pair.of("Editar", null)));
var buttonLayout = new HorizontalLayout(saveButton, cancelButton);
add(breadcrumbs, productGenericForm, buttonLayout);
}
@Override
public void setParameter(BeforeEvent beforeEvent, String s) {
if (StringUtils.isNotBlank(s) && !"new".equals(s)) {
var product = productService.getProduct(UUID.fromString(s));
productGenericForm.setBean(product);
}
}
}

View File

@ -0,0 +1,102 @@
package com.primefactorsolutions.invoices.views;
import com.primefactorsolutions.invoices.model.InvoiceType;
import com.primefactorsolutions.invoices.model.Product;
import com.primefactorsolutions.invoices.model.Status;
import com.primefactorsolutions.invoices.services.ProductService;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.H4;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.PagingGrid;
import static com.primefactorsolutions.invoices.utils.UiUtils.goTo;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = "products", layout = MainLayout.class)
@PageTitle("Facturas | PFS Facturacion")
public class ProductListView extends VerticalLayout {
PagingGrid<Product> grid = new PagingGrid<>(Product.class);
TextField filterText = new TextField();
ProductService productService;
public ProductListView(ProductService productService) {
this.productService = productService;
addClassName("list-view");
setSizeFull();
configureGrid();
add(getTitle(), getToolbar(), getContent());
updateList();
}
private Component getTitle() {
return new H4("Productos");
}
private HorizontalLayout getContent() {
HorizontalLayout content = new HorizontalLayout(grid);
content.setFlexGrow(2, grid);
content.addClassNames("content");
content.setSizeFull();
return content;
}
private void configureGrid() {
grid.addClassNames("pfs-grid");
grid.setSizeFull();
grid.setColumns("descripcion", "precioUnitario");
grid.addColumn(new ComponentRenderer<>(Anchor::new, (a, p) -> {
a.setHref("product-edit/" + p.getId());
a.setText(p.getCodigoProducto());
})).setHeader("codigoProducto");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
grid.setPagingDataProvider((l, i) -> productService.findAllProducts(""));
var products = productService.findAllProducts("");
grid.setItems(products);
grid.setPageSize(25);
}
private Component getToolbar() {
filterText.setPlaceholder("input.search");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList());
var comboBox = new ComboBox<Status>();
comboBox.setItems(Status.values());
comboBox.setItemLabelGenerator(Status::name);
comboBox.setValue(Status.ACTIVE);
add(comboBox);
var addInvoiceButton = new Button("action.new");
addInvoiceButton.addClickListener(click -> goTo(this, "product-edit/new"));
var addComponent = new HorizontalLayout(comboBox, addInvoiceButton);
var toolbar = new HorizontalLayout(filterText, addComponent);
toolbar.addClassName("toolbar");
return toolbar;
}
private void updateList() {
grid.setItems(productService.findAllProducts(""));
}
}

View File

@ -0,0 +1,75 @@
package com.primefactorsolutions.invoices.views;
import com.primefactorsolutions.invoices.model.User;
import com.primefactorsolutions.invoices.services.UserService;
import com.primefactorsolutions.invoices.views.component.Breadcrumbs;
import com.primefactorsolutions.invoices.views.component.GenericForm;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.i18n.I18NProvider;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.context.annotation.Scope;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import static com.primefactorsolutions.invoices.utils.UiUtils.goTo;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = "users", layout = MainLayout.class)
@PageTitle("User | PFS Facturacion")
public class UserEditView extends VerticalLayout implements HasUrlParameter<String> {
UserService userService;
I18NProvider i18NProvider;
GenericForm<User> userGenericForm;
public UserEditView(UserService userService, I18NProvider i18NProvider) {
this.userService = userService;
this.i18NProvider = i18NProvider;
var user = new User();
this.userGenericForm = new GenericForm<>(User.class);
this.userGenericForm.setBean(user);
String text = this.i18NProvider.getTranslation("action.save", Locale.of("es"));
var saveButton = new Button(text);
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
saveButton.addClickListener(c -> {
var updatedUser = userGenericForm.getBean();
userService.saveOrUpdateUser(updatedUser);
goTo(this, "users");
});
var cancelButton = new Button("Cancel");
cancelButton.addClickListener(c -> {
goTo(this, "users");
});
var breadcrumbs = new Breadcrumbs(List.of(Pair.of("Usuarios", "users"), Pair.of("Editar", null)));
var buttonLayout = new HorizontalLayout(saveButton, cancelButton);
add(breadcrumbs, userGenericForm, buttonLayout);
}
@Override
public void setParameter(BeforeEvent beforeEvent, String s) {
if (StringUtils.isNotBlank(s) && !"new".equals(s)) {
var user = userService.getUser(UUID.fromString(s));
userGenericForm.setBean(user);
}
}
}

View File

@ -0,0 +1,103 @@
package com.primefactorsolutions.invoices.views;
import com.primefactorsolutions.invoices.model.Status;
import com.primefactorsolutions.invoices.model.User;
import com.primefactorsolutions.invoices.services.UserService;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.H4;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.PagingGrid;
import java.util.List;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = "users", layout = MainLayout.class)
@PageTitle("Facturas | PFS Facturacion")
public class UserListView extends VerticalLayout {
PagingGrid<User> grid = new PagingGrid<>(User.class);
TextField filterText = new TextField();
UserService userService;
public UserListView(UserService userService) {
this.userService = userService;
addClassName("list-view");
setSizeFull();
configureGrid();
add(getTitle(), getToolbar(), getContent());
updateList();
}
private Component getTitle() {
return new H4("Users");
}
private HorizontalLayout getContent() {
HorizontalLayout content = new HorizontalLayout(grid);
content.setFlexGrow(2, grid);
// content.setFlexGrow(1, form);
content.addClassNames("content");
content.setSizeFull();
return content;
}
private void configureGrid() {
grid.addClassNames("invoice-grid");
grid.setSizeFull();
grid.setColumns("fullName", "email", "role", "status");
grid.getColumnByKey("fullName").setRenderer(new ComponentRenderer<>(Anchor::new, (anchor, u) -> {
anchor.setHref("users/" + u.getId());
anchor.setText(u.getFullName());
}));
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
grid.setPagingDataProvider((l, i) -> userService.findAllUsers(""));
List<User> invoices = userService.findAllUsers("");
grid.setItems(invoices);
grid.setPageSize(25);
}
private Component getToolbar() {
filterText.setPlaceholder("Buscar ...");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList());
var comboBox = new ComboBox<Status>();
comboBox.setItems(Status.values());
comboBox.setItemLabelGenerator(Status::name);
comboBox.setValue(Status.ACTIVE);
add(comboBox);
var addInvoiceButton = new Button("Nuevo Usuario");
addInvoiceButton.addClickListener(click -> {
this.getUI().ifPresent(ui -> ui.navigate("users/new"));
});
var addComponent = new HorizontalLayout(comboBox, addInvoiceButton);
var toolbar = new HorizontalLayout(filterText, addComponent);
toolbar.addClassName("toolbar");
return toolbar;
}
private void updateList() {
grid.setItems(userService.findAllUsers(""));
}
}

View File

@ -0,0 +1,193 @@
package com.primefactorsolutions.invoices.views.component;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.shared.HasThemeVariant;
import com.vaadin.flow.component.textfield.AbstractNumberField;
import com.vaadin.flow.component.textfield.TextFieldVariant;
import com.vaadin.flow.function.SerializableFunction;
import java.math.BigInteger;
@Tag("vaadin-integer-field")
@NpmPackage(value = "@vaadin/polymer-legacy-adapter", version = "24.3.10")
@JsModule("@vaadin/polymer-legacy-adapter/style-modules.js")
@NpmPackage(value = "@vaadin/integer-field", version = "24.3.10")
@JsModule("@vaadin/integer-field/src/vaadin-integer-field.js")
public class BigIntegerField extends AbstractNumberField<BigIntegerField, BigInteger>
implements HasThemeVariant<TextFieldVariant> {
private static final SerializableFunction<String, BigInteger> PARSER = valueFormClient -> {
if (valueFormClient == null || valueFormClient.isEmpty()) {
return null;
}
try {
return new BigInteger(valueFormClient);
} catch (NumberFormatException e) {
return null;
}
};
private static final SerializableFunction<BigInteger, String> FORMATTER = valueFromModel -> valueFromModel == null
? ""
: valueFromModel.toString();
/**
* Constructs an empty {@code LongField}.
*/
public BigIntegerField() {
super(PARSER, FORMATTER, Double.MIN_VALUE, Double.MAX_VALUE);
}
/**
* Constructs an empty {@code LongField} with the given label.
*
* @param label
* the text to set as the label
*/
public BigIntegerField(String label) {
this();
setLabel(label);
}
/**
* Constructs an empty {@code LongField} with the given label and
* placeholder text.
*
* @param label
* the text to set as the label
* @param placeholder
* the placeholder text to set
*/
public BigIntegerField(String label, String placeholder) {
this(label);
setPlaceholder(placeholder);
}
/**
* Constructs an empty {@code LongField} with a value change listener.
*
* @param listener
* the value change listener
*
* @see #addValueChangeListener(ValueChangeListener)
*/
public BigIntegerField(
ValueChangeListener<? super ComponentValueChangeEvent<BigIntegerField, BigInteger>> listener) {
this();
addValueChangeListener(listener);
}
/**
* Constructs an empty {@code LongField} with a value change listener and
* a label.
*
* @param label
* the text to set as the label
* @param listener
* the value change listener
*
* @see #setLabel(String)
* @see #addValueChangeListener(ValueChangeListener)
*/
public BigIntegerField(String label,
ValueChangeListener<? super ComponentValueChangeEvent<BigIntegerField, BigInteger>> listener) {
this(label);
addValueChangeListener(listener);
}
/**
* Constructs a {@code LongField} with a value change listener, a label
* and an initial value.
*
* @param label
* the text to set as the label
* @param initialValue
* the initial value
* @param listener
* the value change listener
*
* @see #setLabel(String)
* @see #setValue(Object)
* @see #addValueChangeListener(ValueChangeListener)
*/
public BigIntegerField(String label, BigInteger initialValue,
ValueChangeListener<? super ComponentValueChangeEvent<BigIntegerField, BigInteger>> listener) {
this(label);
setValue(initialValue);
addValueChangeListener(listener);
}
/**
* Sets the minimum value of the field. Entering a value which is smaller
* than {@code min} invalidates the field.
*
* @param min
* the min value to set
*/
public void setMin(int min) {
super.setMin(min);
}
/**
* Gets the minimum allowed value of the field.
*
* @return the min property of the field
* @see #setMin(int)
*/
public int getMin() {
return (int) getMinDouble();
}
/**
* Sets the maximum value of the field. Entering a value which is greater
* than {@code max} invalidates the field.
*
* @param max
* the max value to set
*/
public void setMax(int max) {
super.setMax(max);
}
/**
* Gets the maximum allowed value of the field.
*
* @return the max property of the field
* @see #setMax(int)
*/
public int getMax() {
return (int) getMaxDouble();
}
/**
* Sets the allowed number intervals of the field. This specifies how much
* the value will be increased/decreased when clicking on the
* {@link #setStepButtonsVisible(boolean) step buttons}. It is also used to
* invalidate the field, if the value doesn't align with the specified step
* and {@link #setMin(int) min} (if specified by user).
*
* @param step
* the new step to set
* @throws IllegalArgumentException
* if the argument is less or equal to zero.
*/
public void setStep(int step) {
if (step <= 0) {
throw new IllegalArgumentException(
"The step cannot be less or equal to zero.");
}
super.setStep(step);
}
/**
* Gets the allowed number intervals of the field.
*
* @return the step property of the field
* @see #setStep(int)
*/
public int getStep() {
return (int) getStepDouble();
}
}

View File

@ -0,0 +1,27 @@
package com.primefactorsolutions.invoices.views.component;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import org.apache.commons.lang3.tuple.Pair;
import java.util.List;
import java.util.stream.Stream;
public class Breadcrumbs extends HorizontalLayout {
public Breadcrumbs(List<Pair<String, String>> parts) {
var components = parts.stream()
.flatMap(p -> {
if (p.getValue() == null) {
return Stream.of((Component)new Span(p.getKey()), (Component)new Span(">"));
} else {
return Stream.of((Component)new Anchor(p.getValue(), p.getKey()), (Component)new Span(">"));
}
}).toList();
if (components.size() > 1) {
this.add(components.subList(0, components.size() - 1));
}
}
}

View File

@ -0,0 +1,43 @@
package com.primefactorsolutions.invoices.views.component;
import com.primefactorsolutions.invoices.views.Bindable;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class CollectionContainer<T> extends VerticalLayout implements Bindable<List<T>> {
private List<Bindable<T>> elements = new ArrayList<>();
public CollectionContainer(Class<T> elementClazz) {
Button button = new Button("Add");
button.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
GenericForm<T> element = new GenericForm<>(elementClazz);
try {
element.setBean(elementClazz.getConstructor().newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
elements.add(element);
add(element);
});
add(button);
}
@Override
public void setBean(Object bean) {
for (int i = 0; i < elements.size(); i++) {
elements.get(i).setBean(((List<T>)bean).get(i));
}
}
@Override
public List<T> getBean() {
return elements.stream().map(Bindable::getBean).collect(Collectors.toList());
}
}

View File

@ -0,0 +1,190 @@
package com.primefactorsolutions.invoices.views.component;
import com.primefactorsolutions.invoices.views.Bindable;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Focusable;
import com.vaadin.flow.component.datetimepicker.DateTimePicker;
import com.vaadin.flow.component.details.Details;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.BigDecimalField;
import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.component.textfield.NumberField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import javax.xml.datatype.XMLGregorianCalendar;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
import static com.primefactorsolutions.invoices.utils.TextUtils.makeLabel;
public class GenericForm<T> extends VerticalLayout implements Bindable<T> {
Binder<T> binder;
List<Pair<String, GenericForm<?>>> inner2 = new ArrayList<>();
List<Pair<String, CollectionContainer<?>>> inner = new ArrayList<>();
@Getter(value = AccessLevel.PUBLIC)
Class<T> tClass;
boolean recursive;
public GenericForm(Class<T> tClass) {
this(tClass, false);
}
public GenericForm(Class<T> tClass, boolean withDetails) {
this.recursive = false;
this.tClass = tClass;
this.binder = new BeanValidationBinder<>(tClass);
List<Component> content = getComponents(tClass).toList();
FormLayout formLayout = new FormLayout();
formLayout.setResponsiveSteps(
new FormLayout.ResponsiveStep("0", 1),
new FormLayout.ResponsiveStep("500px", 1),
new FormLayout.ResponsiveStep("800px", 1));
if (withDetails) {
Details details = new Details(tClass.getName());
details.setOpened(true);
details.add(formLayout);
for (Component c : content) {
if (!(c instanceof CollectionContainer<?>)) {
formLayout.add(c);
} else {
details.add(c);
}
}
add(details);
} else {
add(formLayout);
for (Component c : content) {
if (!(c instanceof CollectionContainer<?>)) {
formLayout.add(c);
} else {
add(c);
}
}
}
}
public Stream<Component> getComponents(Class<?> clazz) {
return Arrays.stream(clazz.getDeclaredFields())
.sorted(Comparator.comparing(Field::getName))
.flatMap(field -> {
if (field.getType().getPackageName().startsWith("com.primefactorsolutions") && this.recursive) {
GenericForm<?> ff = new GenericForm<>(field.getType(), true);
inner2.add(Pair.of(field.getName(), ff));
return Stream.of(ff);
} else {
final String label = makeLabel(field.getName());
Component component = null;
if (field.getType().equals(String.class)) {
component = new TextField(label);
binder.forField((TextField)component)
.bind(field.getName());
} else if (field.getType().equals(Integer.class) || field.getType().equals(Integer.TYPE)) {
final IntegerField formField = new IntegerField(label);
binder.forField(formField)
.bind(field.getName());
component = formField;
} else if (field.getType().equals(Long.class) || field.getType().equals(Long.TYPE)) {
final LongField formField = new LongField(label);
binder.forField(formField)
.bind(field.getName());
component = formField;
} else if (field.getType().equals(BigInteger.class)) {
var formField = new BigIntegerField(label);
binder.forField(formField)
.bind(field.getName());
component = formField;
} else if (field.getType().equals(Double.class) ||
field.getType().equals(Double.TYPE) ||
field.getType().equals(Float.class) ||
field.getType().equals(Float.TYPE)) {
var formField = new NumberField(label);
binder.forField(formField)
.bind(field.getName());
component = formField;
} else if (field.getType().equals(BigDecimal.class)) {
var formField = new BigDecimalField(label);
binder.forField(formField)
.bind(field.getName());
component = formField;
} else if (field.getType().equals(XMLGregorianCalendar.class)) {
component = new DateTimePicker(label);
} else if (field.getGenericType() instanceof ParameterizedType pt) {
final Class<?> typeArgClazz = (Class<?>) pt.getActualTypeArguments()[0];
if (typeArgClazz.getPackageName().startsWith("com.primefactorsolutions")) {
CollectionContainer<?> child = new CollectionContainer<>(typeArgClazz);
inner.add(Pair.of(field.getName(), child));
return Stream.of(child);
} else {
return Stream.of();
}
}
if (component == null) {
return Stream.of();
} else {
if (component instanceof Focusable<?> cf) {
cf.addFocusListener((e) -> binder.isValid());
}
return Stream.of(component);
}
}
});
}
@Override
@SneakyThrows
public void setBean(Object bean) {
binder.setBean((T) bean);
for(Pair<String, GenericForm<?>> b: inner2) {
Object foo = tClass.getDeclaredMethod("get" + StringUtils.capitalize(b.getKey())).invoke(bean);
if (foo == null) {
foo = tClass.getDeclaredField(b.getKey()).getType().getConstructor().newInstance();
}
b.getValue().setBean(foo);
}
}
@Override
@SneakyThrows
public T getBean() {
T result = binder.getBean();
for(Pair<String, GenericForm<?>> b: inner2) {
Object foo = b.getValue().getBean();
tClass.getDeclaredMethod("set" + StringUtils.capitalize(b.getKey()), b.getValue().getTClass())
.invoke(result, foo);
}
for (Pair<String, CollectionContainer<?>> b: inner) {
List<?> foo = b.getValue().getBean();
tClass.getDeclaredMethod("set" + StringUtils.capitalize(b.getKey()), List.class).invoke(result, foo);
}
return result;
}
}

View File

@ -0,0 +1,191 @@
package com.primefactorsolutions.invoices.views.component;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.shared.HasThemeVariant;
import com.vaadin.flow.component.textfield.AbstractNumberField;
import com.vaadin.flow.component.textfield.TextFieldVariant;
import com.vaadin.flow.function.SerializableFunction;
@Tag("vaadin-integer-field")
@NpmPackage(value = "@vaadin/polymer-legacy-adapter", version = "24.3.10")
@JsModule("@vaadin/polymer-legacy-adapter/style-modules.js")
@NpmPackage(value = "@vaadin/integer-field", version = "24.3.10")
@JsModule("@vaadin/integer-field/src/vaadin-integer-field.js")
public class LongField extends AbstractNumberField<LongField, Long>
implements HasThemeVariant<TextFieldVariant> {
private static final SerializableFunction<String, Long> PARSER = valueFormClient -> {
if (valueFormClient == null || valueFormClient.isEmpty()) {
return null;
}
try {
return Long.parseLong(valueFormClient);
} catch (NumberFormatException e) {
return null;
}
};
private static final SerializableFunction<Long, String> FORMATTER = valueFromModel -> valueFromModel == null
? ""
: valueFromModel.toString();
/**
* Constructs an empty {@code LongField}.
*/
public LongField() {
super(PARSER, FORMATTER, Long.MIN_VALUE, Long.MAX_VALUE);
}
/**
* Constructs an empty {@code LongField} with the given label.
*
* @param label
* the text to set as the label
*/
public LongField(String label) {
this();
setLabel(label);
}
/**
* Constructs an empty {@code LongField} with the given label and
* placeholder text.
*
* @param label
* the text to set as the label
* @param placeholder
* the placeholder text to set
*/
public LongField(String label, String placeholder) {
this(label);
setPlaceholder(placeholder);
}
/**
* Constructs an empty {@code LongField} with a value change listener.
*
* @param listener
* the value change listener
*
* @see #addValueChangeListener(ValueChangeListener)
*/
public LongField(
ValueChangeListener<? super ComponentValueChangeEvent<LongField, Long>> listener) {
this();
addValueChangeListener(listener);
}
/**
* Constructs an empty {@code LongField} with a value change listener and
* a label.
*
* @param label
* the text to set as the label
* @param listener
* the value change listener
*
* @see #setLabel(String)
* @see #addValueChangeListener(ValueChangeListener)
*/
public LongField(String label,
ValueChangeListener<? super ComponentValueChangeEvent<LongField, Long>> listener) {
this(label);
addValueChangeListener(listener);
}
/**
* Constructs a {@code LongField} with a value change listener, a label
* and an initial value.
*
* @param label
* the text to set as the label
* @param initialValue
* the initial value
* @param listener
* the value change listener
*
* @see #setLabel(String)
* @see #setValue(Object)
* @see #addValueChangeListener(ValueChangeListener)
*/
public LongField(String label, Long initialValue,
ValueChangeListener<? super ComponentValueChangeEvent<LongField, Long>> listener) {
this(label);
setValue(initialValue);
addValueChangeListener(listener);
}
/**
* Sets the minimum value of the field. Entering a value which is smaller
* than {@code min} invalidates the field.
*
* @param min
* the min value to set
*/
public void setMin(int min) {
super.setMin(min);
}
/**
* Gets the minimum allowed value of the field.
*
* @return the min property of the field
* @see #setMin(int)
*/
public int getMin() {
return (int) getMinDouble();
}
/**
* Sets the maximum value of the field. Entering a value which is greater
* than {@code max} invalidates the field.
*
* @param max
* the max value to set
*/
public void setMax(int max) {
super.setMax(max);
}
/**
* Gets the maximum allowed value of the field.
*
* @return the max property of the field
* @see #setMax(int)
*/
public int getMax() {
return (int) getMaxDouble();
}
/**
* Sets the allowed number intervals of the field. This specifies how much
* the value will be increased/decreased when clicking on the
* {@link #setStepButtonsVisible(boolean) step buttons}. It is also used to
* invalidate the field, if the value doesn't align with the specified step
* and {@link #setMin(int) min} (if specified by user).
*
* @param step
* the new step to set
* @throws IllegalArgumentException
* if the argument is less or equal to zero.
*/
public void setStep(int step) {
if (step <= 0) {
throw new IllegalArgumentException(
"The step cannot be less or equal to zero.");
}
super.setStep(step);
}
/**
* Gets the allowed number intervals of the field.
*
* @return the step property of the field
* @see #setStep(int)
*/
public int getStep() {
return (int) getStepDouble();
}
}

View File

@ -0,0 +1,48 @@
package com.primefactorsolutions.invoices.views.component.invoice;
import com.primefactorsolutions.invoices.views.component.LongField;
import com.vaadin.flow.component.datetimepicker.DateTimePicker;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.textfield.BigDecimalField;
import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
public class CabeceraForm<T> extends FormLayout {
public Binder<T> binder;
LongField nitEmisor = new LongField("nitEmisor");
TextField razonSocialEmisor = new TextField("razonSocialEmisor");
TextField municipio = new TextField("municipio");
TextField telefono = new TextField("telefono");
IntegerField codigoSucursal = new IntegerField("codigoSucursal");
TextField direccion = new TextField("direccion");
IntegerField codigoPuntoVenta = new IntegerField("codigoPuntoVenta");
DateTimePicker fechaEmision = new DateTimePicker("fechaEmision");
IntegerField codigoMoneda = new IntegerField("codigoMoneda");
BigDecimalField tipoCambio = new BigDecimalField("tipoCambio");
public CabeceraForm(Class<T> clazz) {
binder = new BeanValidationBinder<>(clazz);
binder.bindInstanceFields(this);
setResponsiveSteps(
new ResponsiveStep("0", 1),
new ResponsiveStep("500px", 3),
new ResponsiveStep("800px", 5));
setColspan(razonSocialEmisor, 5);
setColspan(direccion, 5);
add(nitEmisor,
fechaEmision,
razonSocialEmisor,
direccion,
municipio,
telefono,
codigoSucursal,
codigoPuntoVenta,
codigoMoneda,
tipoCambio
);
}
}

View File

@ -0,0 +1,46 @@
package com.primefactorsolutions.invoices.views.component.invoice;
import com.primefactorsolutions.invoices.views.component.LongField;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
public class ClientForm<T> extends FormLayout {
public Binder<T> binder;
TextField nombreRazonSocial = new TextField("nombreRazonSocial");
IntegerField codigoTipoDocumentoIdentidad = new IntegerField("codigoTipoDocumentoIdentidad");
TextField numeroDocumento = new TextField("numeroDocumento");
TextField complemento = new TextField("complemento");
TextField direccionComprador = new TextField("direccionComprador");
TextField codigoCliente = new TextField("codigoCliente");
TextField lugarDestino = new TextField("lugarDestino");
IntegerField codigoPais = new IntegerField("codigoPais");
IntegerField codigoMetodoPago = new IntegerField("codigoMetodoPago");
LongField numeroTarjeta = new LongField("numeroTarjeta");
public ClientForm(Class<T> clazz) {
binder = new BeanValidationBinder<>(clazz);
binder.bindInstanceFields(this);
setResponsiveSteps(
new ResponsiveStep("0", 1),
new ResponsiveStep("500px", 3),
new ResponsiveStep("800px", 5));
setColspan(nombreRazonSocial, 5);
setColspan(direccionComprador, 5);
add(nombreRazonSocial,
direccionComprador,
codigoTipoDocumentoIdentidad,
numeroDocumento,
complemento,
codigoCliente,
lugarDestino,
codigoPais,
codigoMetodoPago,
numeroTarjeta
);
}
}

View File

@ -0,0 +1,42 @@
package com.primefactorsolutions.invoices.views.component.invoice;
import com.google.common.collect.Lists;
import com.primefactorsolutions.invoices.model.Client;
import com.primefactorsolutions.invoices.services.ClientService;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import org.springframework.data.domain.PageRequest;
import java.util.List;
import java.util.function.Consumer;
public class ClientInputForm extends FormLayout {
public Binder<Client> binder;
ComboBox<Client> queryText = new ComboBox<>("correoElectronico o telefono");
List<Consumer<Client>> listeners = Lists.newArrayList();
public ClientInputForm(ClientService clientService) {
binder = new BeanValidationBinder<>(Client.class);
queryText.setPlaceholder("Buscar ...");
queryText.setItemLabelGenerator(Client::getNombreRazonSocial);
queryText.setItemsWithFilterConverter(
query -> {
var limit = Math.min(query.getPageSize(), query.getLimit());
var page = Math.min(query.getPage(), query.getOffset());
return clientService.findAllClients(query.getFilter().orElse(""),
PageRequest.of(page, limit)).stream();
},
personSearchTerm -> personSearchTerm
);
queryText.addValueChangeListener(event -> listeners.forEach(c -> c.accept(event.getValue())));
add(queryText);
}
public void addListener(Consumer<Client> listener) {
listeners.add(listener);
}
}

View File

@ -0,0 +1,47 @@
package com.primefactorsolutions.invoices.views.component.invoice;
import com.primefactorsolutions.invoices.views.component.BigIntegerField;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.Hr;
import com.vaadin.flow.component.textfield.BigDecimalField;
import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import static com.primefactorsolutions.invoices.utils.TextUtils.makeLabel;
public class DetalleForm<T> extends FormLayout {
Binder<T> binder;
TextField actividadEconomica = new TextField(makeLabel("actividadEconomica"));
TextField descripcion = new TextField("descripcion");
BigIntegerField cantidad = new BigIntegerField("cantidad");
IntegerField unidadMedida = new IntegerField("unidadMedida");
BigDecimalField precioUnitario = new BigDecimalField("precioUnitario");
BigDecimalField montoDescuento = new BigDecimalField("montoDescuento");
BigDecimalField subTotal = new BigDecimalField("subTotal");
Hr separator = new Hr();
DetalleForm(Class<T> clazz) {
super();
binder = new BeanValidationBinder<>(clazz);
binder.bindInstanceFields(this);
setResponsiveSteps(
new ResponsiveStep("0", 1),
new ResponsiveStep("500px", 3),
new ResponsiveStep("800px", 6));
setColspan(descripcion, 6);
setColspan(separator, 6);
add(descripcion,
actividadEconomica,
cantidad,
unidadMedida,
precioUnitario,
montoDescuento,
subTotal,
separator
);
}
}

View File

@ -0,0 +1,73 @@
package com.primefactorsolutions.invoices.views.component.invoice;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.shared.Registration;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class DetallesForm<T> extends VerticalLayout {
Class<T> clazz;
Supplier<T> supplier;
public DetallesForm(Class<T> clazz, Supplier<T> supplier) {
this.clazz = clazz;
this.supplier = supplier;
addDetalle();
}
public void addDetalle() {
var content = new VerticalLayout();
var value = supplier.get();
var detalle = new DetalleForm<>(clazz);
detalle.binder.setBean(value);
detalle.binder.addValueChangeListener(v -> {
fireEvent(new ChangeEvent(this, true));
});
content.add(detalle);
var delButton = new Button("Remove");
delButton.addClickListener(c -> this.remove(content));
content.add(delButton);
add(content);
}
public List<T> getValues() {
return getChildren()
.flatMap(d -> {
return d.getChildren()
.map(c -> {
if (c instanceof DetalleForm) {
return ((DetalleForm<T>) c).binder.getBean();
} else {
return null;
}
});
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public Registration addChangeListener(
ComponentEventListener<DetallesForm.ChangeEvent> listener) {
return addListener(DetallesForm.ChangeEvent.class, listener);
}
public static class ChangeEvent
extends ComponentEvent<DetallesForm<?>> {
public ChangeEvent(DetallesForm source,
boolean fromClient) {
super(source, fromClient);
}
}
}

View File

@ -0,0 +1,34 @@
package com.primefactorsolutions.invoices.views.component.invoice;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.textfield.BigDecimalField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
public class TotalesForm<T> extends FormLayout {
public Binder<T> binder;
BigDecimalField montoTotal = new BigDecimalField("montoTotal");
BigDecimalField montoTotalSujetoIva = new BigDecimalField("montoTotalSujetoIva");
BigDecimalField montoTotalMoneda = new BigDecimalField("montoTotalMoneda");
TextField informacionAdicional = new TextField("informacionAdicional");
BigDecimalField descuentoAdicional = new BigDecimalField("descuentoAdicional");
public TotalesForm(Class<T> clazz) {
binder = new BeanValidationBinder<>(clazz);
binder.bindInstanceFields(this);
setResponsiveSteps(
new ResponsiveStep("0", 1),
new ResponsiveStep("500px", 3),
new ResponsiveStep("800px", 5));
setColspan(informacionAdicional, 5);
add(
montoTotalMoneda,
montoTotal,
descuentoAdicional,
montoTotalSujetoIva,
informacionAdicional
);
}
}

View File

@ -0,0 +1,22 @@
package com.primefactorsolutions.invoices.xml.adapters;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
public class DateTimeXmlAdapter extends XmlAdapter<String, LocalDateTime> {
@Override
public String marshal(LocalDateTime v) throws Exception {
return v.toInstant(ZoneOffset.UTC).toString();
}
@Override
public LocalDateTime unmarshal(String v) throws Exception {
return LocalDateTime.ofInstant(Instant.parse(v), ZoneId.of("UTC"));
}
}

View File

@ -0,0 +1,26 @@
package com.primefactorsolutions.invoices.xml.adapters;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateXmlAdapter extends XmlAdapter<String, Date> {
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public String marshal(Date v) throws Exception {
synchronized (dateFormat) {
return dateFormat.format(v);
}
}
@Override
public Date unmarshal(String v) throws Exception {
synchronized (dateFormat) {
return dateFormat.parse(v);
}
}
}

View File

@ -6,5 +6,10 @@ spring.mustache.check-template-location = false
vaadin.launch-browser = true
# To improve the performance during development.
# For more information https://vaadin.com/docs/latest/integrations/spring/configuration#special-configuration-parameters
vaadin.allowed-packages = com.vaadin,org.vaadin,dev.hilla,com.example.application
vaadin.allowed-packages = com.vaadin,org.vaadin,dev.hilla,com.primefactorsolutions.invoices
spring.jpa.defer-datasource-initialization = true
spring.datasource.initialize = false
spring.jpa.show-sql = true
spring.jpa.properties.hibernate.search.backend.directory.type=local-heap
#spring.jpa.properties.hibernate.search.backend.directory.root = target/

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jaxb:bindings
xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:annox="urn:jaxb.jvnet.org:annox"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jaxb https://jakarta.ee/xml/ns/jaxb/bindingschema_3_0.xsd"
jaxb:extensionBindingPrefixes="xjc annox"
version="3.0">
<jaxb:globalBindings typesafeEnumMemberName="generateName">
<xjc:javaType name="java.time.LocalDate" xmlType="xs:date"
adapter="com.primefactorsolutions.invoices.xml.adapters.DateXmlAdapter" />
<xjc:javaType name="java.time.LocalDateTime" xmlType="xs:dateTime"
adapter="com.primefactorsolutions.invoices.xml.adapters.DateTimeXmlAdapter" />
</jaxb:globalBindings>
</jaxb:bindings>

View File

@ -1,64 +1,18 @@
INSERT INTO "STATUS" (ID, VERSION, NAME) VALUES
(1, 1, 'Imported lead'),
(2, 1, 'Not contacted'),
(3, 1, 'Contacted'),
(4, 1, 'Customer'),
(5, 1, 'Closed (lost)');
INSERT INTO "COMPANY" (ID, VERSION, NAME) VALUES
(6, 1, 'Phillips Van Heusen Corp.'),
(7, 1, 'Avaya Inc.'),
(8, 1, 'Laboratory Corporation of America Holdings'),
(9, 1, 'AutoZone, Inc.'),
(10, 1, 'Linens ''n Things Inc.');
INSERT INTO "CONTACT" (ID, VERSION, EMAIL,FIRST_NAME,LAST_NAME,COMPANY_ID,STATUS_ID) VALUES
(11, 1, 'eula.lane@jigrormo.ye', 'Eula', 'Lane', 8, 1),
(12, 1, 'barry.rodriquez@zun.mm', 'Barry', 'Rodriquez', 7, 5),
(13, 1, 'eugenia.selvi@capfad.vn', 'Eugenia', 'Selvi', 6, 3),
(14, 1, 'alejandro.miles@dec.bn', 'Alejandro', 'Miles', 10, 3),
(15, 1, 'cora.tesi@bivo.yt', 'Cora', 'Tesi', 6, 4),
(16, 1, 'marguerite.ishii@judbilo.gn', 'Marguerite', 'Ishii', 10, 2),
(17, 1, 'mildred.jacobs@joraf.wf', 'Mildred', 'Jacobs', 8, 1),
(18, 1, 'gene.goodman@kem.tl', 'Gene', 'Goodman', 8, 5),
(19, 1, 'lettie.bennett@odeter.bb', 'Lettie', 'Bennett', 6, 1),
(20, 1, 'mabel.leach@lisohuje.vi', 'Mabel', 'Leach', 10, 2),
(21, 1, 'jordan.miccinesi@duod.gy', 'Jordan', 'Miccinesi', 8, 3),
(22, 1, 'marie.parkes@nowufpus.ph', 'Marie', 'Parkes', 7, 1),
(23, 1, 'rose.gray@kagu.hr', 'Rose', 'Gray', 9, 4),
(24, 1, 'garrett.stokes@fef.bg', 'Garrett', 'Stokes', 9, 3),
(25, 1, 'barbara.matthieu@derwogi.jm', 'Barbara', 'Matthieu', 7, 5),
(26, 1, 'jean.rhodes@wehovuce.gu', 'Jean', 'Rhodes', 7, 3),
(27, 1, 'jack.romoli@zamum.bw', 'Jack', 'Romoli', 6, 4),
(28, 1, 'pearl.holden@dunebuh.cr', 'Pearl', 'Holden', 8, 1),
(29, 1, 'belle.montero@repiwid.si', 'Belle', 'Montero', 9, 5),
(30, 1, 'olive.molina@razuppa.ga', 'Olive', 'Molina', 6, 2),
(31, 1, 'minerva.todd@kulmenim.ad', 'Minerva', 'Todd', 9, 3),
(32, 1, 'bobby.pearson@ib.kg', 'Bobby', 'Pearson', 9, 1),
(33, 1, 'larry.ciappi@ba.lk', 'Larry', 'Ciappi', 10, 2),
(34, 1, 'ronnie.salucci@tohhij.lv', 'Ronnie', 'Salucci', 9, 1),
(35, 1, 'walter.grossi@tuvo.sa', 'Walter', 'Grossi', 9, 1);
INSERT INTO "CONTACT" (ID, VERSION, EMAIL,FIRST_NAME,LAST_NAME,COMPANY_ID,STATUS_ID) VALUES
(36, 1, 'frances.koopmans@foga.tw', 'Frances', 'Koopmans', 7, 5),
(37, 1, 'frances.fujimoto@uswuzzub.jp', 'Frances', 'Fujimoto', 6, 5),
(38, 1, 'olivia.vidal@hivwerip.vc', 'Olivia', 'Vidal', 9, 2),
(39, 1, 'edna.henry@gugusu.rw', 'Edna', 'Henry', 8, 4),
(40, 1, 'lydia.brun@zedekak.md', 'Lydia', 'Brun', 7, 3),
(41, 1, 'jay.blake@ral.mk', 'Jay', 'Blake', 10, 4),
(42, 1, 'isabel.serafini@turuhu.bh', 'Isabel', 'Serafini', 10, 1),
(43, 1, 'rebecca.carter@omjo.et', 'Rebecca', 'Carter', 8, 4),
(44, 1, 'maurice.fabbrini@rig.bh', 'Maurice', 'Fabbrini', 9, 3),
(45, 1, 'ollie.turnbull@sicewap.org', 'Ollie', 'Turnbull', 6, 1),
(46, 1, 'jerry.hopkins@fo.mh', 'Jerry', 'Hopkins', 9, 5),
(47, 1, 'nora.lyons@gegijap.na', 'Nora', 'Lyons', 10, 1),
(48, 1, 'anne.weis@kuvesa.pe', 'Anne', 'Weis', 7, 4),
(49, 1, 'louise.gauthier@lapahu.mt', 'Louise', 'Gauthier', 6, 2),
(50, 1, 'lloyd.fani@zev.ru', 'Lloyd', 'Fani', 8, 1),
(51, 1, 'maud.dunn@nabeaga.ni', 'Maud', 'Dunn', 6, 1),
(52, 1, 'henry.gigli@kaot.ps', 'Henry', 'Gigli', 6, 5),
(53, 1, 'virgie.werner@tawuctuj.cf', 'Virgie', 'Werner', 10, 4),
(54, 1, 'gregory.cozzi@eh.ru', 'Gregory', 'Cozzi', 8, 2),
(55, 1, 'lucinda.gil@fajjusuz.kr', 'Lucinda', 'Gil', 7, 5),
(56, 1, 'gertrude.verbeek@pave.cc', 'Gertrude', 'Verbeek', 6, 5),
(57, 1, 'mattie.graham@ispaviw.gt', 'Mattie', 'Graham', 7, 2),
(58, 1, 'bryan.shaw@ha.ee', 'Bryan', 'Shaw', 9, 1),
(59, 1, 'essie.adams@iliat.cw', 'Essie', 'Adams', 8, 5),
(60, 1, 'gary.osborne@do.ga', 'Gary', 'Osborne', 7, 5);
INSERT INTO "COMPANY" (ID, VERSION, NIT_EMISOR, RAZON_SOCIAL_EMISOR, TELEFONO, MUNICIPIO, DIRECCION)
VALUES ('108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 1, '1234567890', 'PFS', '4567890', 'Cochabamba', 'Calle Test 123');
INSERT INTO "INVOICE" (ID, VERSION, COMPANY_ID, NOMBRE_RAZON_SOCIAL, FECHA_EMISION, PAYLOAD, STATUS)
VALUES ('0d8c7cc0-d43c-4e7e-9c8a-81f070fbf436', 1, '108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 'Metadao', '2024-01-01T12:00:00', '{}', 0);
INSERT INTO "CLIENT" (ID, VERSION, COMPANY_ID, STATUS, CORREO_ELECTRONICO, TELEFONO, NOMBRE_RAZON_SOCIAL,
CODIGO_TIPO_DOCUMENTO_IDENTIDAD, NUMERO_DOCUMENTO, DIRECCION_COMPRADOR, CODIGO_CLIENTE, CODIGO_PAIS,
CODIGO_METODO_PAGO, NUMERO_TARJETA)
VALUES ('d36b4edf-e7ad-43de-8305-9dc39a61a0d0', 1, '108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 1,
'alexprud@gmail.com', 12345678, 'Alex prudencio', 1, '123123123', 'test av. 123', '1', 1, 1, 45000099988);
INSERT INTO "PRODUCT" (ID, VERSION, COMPANY_ID, STATUS, codigo_Producto_Sin, codigo_Producto, descripcion, unidad_Medida,
precio_Unitario) VALUES ('dcecb825-7bc6-402d-850a-9d4a08fe6663', 1, '108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 1, 1,
'PROD001', 'Test product', 1, 1);
INSERT INTO "USER_" (ID, VERSION, COMPANY_ID, EMAIL, FULL_NAME, ROLE, STATUS) VALUES ('b9f1bbe0-4542-48d3-b88e-452f2891cb68',
1, '108ace19-6d78-4a70-a01f-30b9e3f6b2b8', 'alex@test.com', 'alex prudencio', 1, 1);

View File

@ -0,0 +1,182 @@
{
"sections": [
{
"name": "cabecera",
"entries": [
{
"name": "nitEmisor",
"text": "N\u00famero de NIT registrado en el Padr\u00f3n Nacional de Contribuyentes que corresponde a la persona o empresa que emite la factura. Nota.- Consignar el valor de uno (1) en cantidad consignar 1, 58 (Unidad Servicio) en unidad de medida y el precio del servicio como precio unitario."
},
{
"name": "razonSocialEmisor",
"text": "Raz\u00f3n Social o nombre registrado en el Padr\u00f3n Nacional de Contribuyentes de la persona o empresa que emite la factura."
},
{
"name": "municipio",
"text": "Lugar registrado en el Padr\u00f3n Nacional de contribuyentes"
},
{
"name": "telefono",
"text": "Tel\u00e9fono registrado en el Padr\u00f3n Nacional de contribuyentes"
},
{
"name": "numeroFactura",
"text": "N\u00famero asignado a la factura."
},
{
"name": "cuf",
"text": "C\u00f3digo \u00fanico de facturaci\u00f3n (CUF) debe ser generado por el emisor siguiendo el algoritmo indicado."
},
{
"name": "cufd",
"text": "C\u00f3digo \u00fanico de facturaci\u00f3n diario (CUFD), valor \u00fanico que se obtiene al consumir el servicio web correspondiente."
},
{
"name": "codigoSucursal",
"text": "C\u00f3digo de la sucursal registrada en el Padr\u00f3n y en la cual se est\u00e1 emitiendo la factura. Por ejemplo: sucursal = 0 (casa matriz)."
},
{
"name": "direccion",
"text": "Direcci\u00f3n de la sucursal registrada en el Padr\u00f3n Nacional de Contribuyentes."
},
{
"name": "codigoPuntoVenta",
"text": "C\u00f3digo del punto de Venta creado mediante un servicio web\u00a0y en el cual se emite la factura."
},
{
"name": "fechaEmision",
"text": "Fecha y hora en la cual se emite la factura. Expresada en formato UTC Extendido, por ejemplo: \u201c2020-02-15T08:40:12.215\u201d."
},
{
"name": "nombreRazonSocial",
"text": "Raz\u00f3n Social o nombre de la persona u empresa a la cual se emite la factura."
},
{
"name": "codigoTipoDocumentoIdentidad",
"text": "Valor de la param\u00e9trica que identifica el Tipo de Documento utilizado para la emisi\u00f3n de la factura. Puede contener valores que van del 1 al 5. \u00a0(Ejemplo 1 que representa al CI)."
},
{
"name": "numeroDocumento",
"text": "N\u00famero que corresponde al Tipo de Documento Identidad utilizado y al cual se realizar\u00e1 la facturaci\u00f3n."
},
{
"name": "complemento",
"text": "Valor que otorga el SEGIP en casos de c\u00e9dulas de identidad con n\u00famero duplicado."
},
{
"name": "direccionComprador",
"text": "Direcci\u00f3n donde se est\u00e1 exportando."
},
{
"name": "codigoCliente",
"text": "C\u00f3digo de identificaci\u00f3n \u00fanico del cliente, deber\u00e1 ser asignado por el sistema de facturaci\u00f3n del contribuyente."
},
{
"name": "lugarDestino",
"text": "Lugar al cual se est\u00e1 realizando la exportaci\u00f3n."
},
{
"name": "codigoPais",
"text": "Valor de la param\u00e9trica que identifica el tipo de pa\u00eds."
},
{
"name": "codigoMetodoPago",
"text": "Valor de la param\u00e9trica que identifica el m\u00e9todo de pago utilizado para realizar la compra. Por ejemplo 1 que representa a un pago en efectivo."
},
{
"name": "numeroTarjeta",
"text": "Cuando el m\u00e9todo de pago es 2 (Tarjeta), debe enviarse este valor pero ofuscado con los primeros y \u00faltimos 4 d\u00edgitos en claro y ceros al medio. Ej: 4797000000007896, en otro caso, debe enviarse un valor nulo."
},
{
"name": "montoTotal",
"text": "Monto total por el cual se realiza el hecho generador."
},
{
"name": "montoTotalSujetoIva",
"text": "Seteada a cero (0) por defecto para este sector"
},
{
"name": "codigoMoneda",
"text": "Valor de la param\u00e9trica que identifica la moneda en la cual se realiza la transacci\u00f3n."
},
{
"name": "tipoCambio",
"text": "Tipo de cambio de acuerdo a la moneda en la que se realiza el hecho generador, si el c\u00f3digo de moneda es boliviano deber\u00e1 ser igual a 1."
},
{
"name": "montoTotalMoneda",
"text": "Es el Monto Total expresado en el tipo de moneda, si el c\u00f3digo de moneda es boliviano deber\u00e1 ser igual al monto total."
},
{
"name": "informacionAdicional",
"text": "Informaci\u00f3n Adicional."
},
{
"name": "descuentoAdicional",
"text": "Descuento adicional a ser aplicado. cero (0) si no aplica"
},
{
"name": "codigoExcepcion",
"text": "Por defecto, enviar este campo con un valor de cero (0) o nulo. Solo cuando se desee autorizar al SIN el registro de una factura emitida a un NIT inv\u00e1lido se debe enviar\u00a0el valor de uno (1) en el mismo"
},
{
"name": "cafc",
"text": "C\u00f3digo de Autorizaci\u00f3n de Facturas por Contingencia"
},
{
"name": "leyenda",
"text": "Leyenda asociada a la actividad econ\u00f3mica."
},
{
"name": "usuario",
"text": "Identifica al usuario que emite la factura, deber\u00e1 ser descriptivo. Por ejemplo JPEREZ."
},
{
"name": "codigoDocumentoSector",
"text": "Valor de la param\u00e9trica que identifica el tipo de factura que se est\u00e1 emitiendo. Para este tipo de factura este valor es 28."
}
]
},
{
"name": "detalle",
"entries": [
{
"name": "actividadEconomica",
"text": "Actividad econ\u00f3mica registrada en el Padr\u00f3n Nacional de Contribuyentes relacionada al NIT."
},
{
"name": "codigoProductoSin",
"text": "Homologado a los c\u00f3digos de productos gen\u00e9ricos enviados por el SIN a trav\u00e9s del servicio de sincronizaci\u00f3n."
},
{
"name": "codigoProducto",
"text": "C\u00f3digo que otorga el contribuyente a su servicio o producto."
},
{
"name": "descripcion",
"text": "Descripci\u00f3n que otorga el contribuyente a su servicio o producto."
},
{
"name": "cantidad",
"text": "Cantidad del producto o servicio otorgado. En caso de servicio este valor debe ser 1."
},
{
"name": "unidadMedida",
"text": "Valor de la param\u00e9trica que identifica la unidad de medida."
},
{
"name": "precioUnitario",
"text": "Precio que otorga el contribuyente a su servicio o producto."
},
{
"name": "montoDescuento",
"text": "Monto de descuento sobre el producto o servicio espec\u00edfico, para el caso de la factura comercial de exportaci\u00f3n se deber\u00e1 enviar el valor 0 si no aplica."
},
{
"name": "subTotal",
"text": "El subtotal es igual a la (cantidad * precio unitario) \u2013 descuento."
}
]
}
]
}

View File

@ -0,0 +1,127 @@
<html>
<head>
<style>
@page {
size: 7in 9.25in;
margin: 27mm 16mm 27mm 16mm;
}
</style>
</head>
<body>
<div style="width:100%;">
<table style="float: left;">
<tr>
<td>${cabecera.razonSocial!""}</td>
</tr>
<tr>
<td>${cabecera.direccion!""}</td>
</tr>
<tr>
<td>${cabecera.telefono!""}</td>
</tr>
</table>
<table style="float: right;">
<tr>
<td>NIT</td><td>${cabecera.nitEmisor!""}</td>
</tr>
<tr>
<td>Factura</td><td>${cabecera.numeroFactura!""}</td>
</tr>
<tr>
<td>Cod. Aut.</td><td>${cabecera.cuf!""}</td>
</tr>
</table>
</div>
<div style="clear:both;"></div>
<h3 style="text-align:center;">FACTURA COMERCIAL DE EXPORTACIÓN DE SERVICIOS</h3>
<div style="width: 100%; margin-bottom: 2em; margin-top: 2em;">
<table style="float: left;">
<tr>
<td>Fecha:</td><td>${cabecera.fechaEmision!""}</td>
</tr>
<tr>
<td>Nombre/Razón Social:</td><td>${cabecera.nombreRazonSocial!""}</td>
</tr>
<tr>
<td>Tipo de cambio:</td><td>${cabecera.tipoCambio!""}</td>
</tr>
<tr>
<td>Moneda de la Transaccción Comercial:</td><td>${cabecera.moneda!""}</td>
</tr>
</table>
<table style="float: right;">
<tr>
<td>NIT/CI/CEX:</td><td>${cabecera.numeroDocumento!""}</td>
</tr>
<tr>
<td>Cod. Cliente:</td><td>${cabecera.codigoCliente!""}</td>
</tr>
<tr>
<td>Dirección Comprador:</td><td>${cabecera.direccionComprador!""}</td>
</tr>
<tr>
<td>Lugar Destino:</td><td>${cabecera.lugarDestino!""}</td>
</tr>
</table>
</div>
<div style="clear:both;"></div>
<table style="width: 100%; border: 1px solid black; margin-top: 2em;">
<thead style="border: 1px solid black; background-color: lightgray;">
<tr>
<th>CÓDIGO SERVICIO</th>
<th>CANTIDAD</th>
<th>UNIDAD MEDIDA</th>
<th>DESCRIPCIÓN</th>
<th>PRECIO UNITARIO</th>
<th>DESCUENTO</th>
<th>SUBTOTAL</th>
</tr>
</thead>
<tbody>
<#list detalle as d>
<tr>
<th>${d.codigoProducto!""}</th>
<th>${d.cantidad!""}</th>
<th>${d.unidadMedida!""}</th>
<th>${d.descripcion!""}</th>
<th>${d.precioUnitario!""}</th>
<th>${d.montoDescuento!""}</th>
<th>${d.subtotal!""}</th>
</tr>
</#list>
</tbody>
</table>
<table style="float: right; border: 1px solid black;">
<tbody>
<tr>
<td>SUBTOTAL (DÓLAR ESTADOUNIDENSE)</td>
<td>${cabecera.subtotalMoneda!""}</td>
</tr>
<tr>
<td>DESCUENTO (DÓLAR ESTADOUNIDENSE)</td>
<td>${cabecera.descuentoAdicional!""}</td>
</tr>
<tr>
<td>TOTAL GENERAL (DÓLAR ESTADOUNIDENSE)</td>
<td>${cabecera.montoTotalMoneda!""}</td>
</tr>
<tr>
<td>TOTAL GENERAL (BOLIVIANOS)</td>
<td>${cabecera.montoTotal!""}</td>
</tr>
</tbody>
</table>
<div style="clear:both;"></div>
<h4>Informacion Adicional</h4>
<p style="border: 1px solid black;">${cabecera.informacionAdicional!""}</p>
<hr/>
<p style="font-size: smaller;">
ESTA FACTURA CONTRIBUYE AL DESARROLLO DEL PAÍS, EL USO ILÍCITO SERÁ SANCIONADO PENALMENTE DE ACUERDO A LEY Ley N° 453:
El proveedor debe brindar atención sin discriminación, con respeto, calidez y cordialidad a los usuarios y consumidores.
<em>“Este documento es la Representación Gráfica de un Documento Fiscal Digital emitido en una modalidad de facturación en línea”</em>
</p>
</body>
</html>

View File

@ -0,0 +1,340 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- XSD ver.23/08/2021 -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
attributeFormDefault="unqualified" elementFormDefault="qualified">
<xs:element name="facturaComputarizadaComercialExportacionServicio">
<xs:complexType>
<xs:sequence>
<xs:element name="cabecera">
<xs:complexType>
<xs:sequence>
<xs:element name="nitEmisor">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="9999999999999"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="razonSocialEmisor">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="200"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="municipio">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="25"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="telefono" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="25"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="numeroFactura">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="9999999999"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="cuf">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="100"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="cufd">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="100"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoSucursal">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="9999"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="direccion">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="500"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoPuntoVenta" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="9999"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="fechaEmision" type="xs:dateTime"/>
<xs:element name="nombreRazonSocial" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="500"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoTipoDocumentoIdentidad">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="5"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="numeroDocumento">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="20"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="complemento" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="5"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="direccionComprador">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="500"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoCliente">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="100"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="lugarDestino">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="500"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoPais">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="212"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoMetodoPago">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="308"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="numeroTarjeta" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="9999999999999999"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="montoTotal">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="17"/>
<xs:fractionDigits value="2"/>
<xs:minExclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="montoTotalSujetoIva" type="xs:decimal" fixed="0"/>
<xs:element name="codigoMoneda">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="154"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="tipoCambio">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="17"/>
<xs:fractionDigits value="5"/>
<xs:minExclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="montoTotalMoneda">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="17"/>
<xs:fractionDigits value="2"/>
<xs:minExclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="informacionAdicional"
nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="10000"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="descuentoAdicional" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="17"/>
<xs:fractionDigits value="2"/>
<xs:minInclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoExcepcion" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="cafc" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="50"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="leyenda">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="200"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="usuario">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="100"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoDocumentoSector"
type="xs:integer" fixed="28"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="detalle" minOccurs="1" maxOccurs="500">
<xs:complexType>
<xs:sequence>
<xs:element name="actividadEconomica">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoProductoSin">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="99999999"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoProducto">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="50"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="descripcion">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="500"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="cantidad" type="xs:integer" fixed="1"/>
<xs:element name="unidadMedida">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="200"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="precioUnitario">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="20"/>
<xs:fractionDigits value="5"/>
<xs:minExclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="montoDescuento" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="20"/>
<xs:fractionDigits value="5"/>
<xs:minInclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="subTotal">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="20"/>
<xs:fractionDigits value="5"/>
<xs:minExclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@ -0,0 +1,343 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XSD ver.23/08/2021 -->
<xs:schema xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
attributeFormDefault="unqualified" elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:import namespace="http://www.w3.org/2000/09/xmldsig#"
schemaLocation="http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd"/>
<xs:element name="facturaElectronicaComercialExportacionServicio">
<xs:complexType>
<xs:sequence>
<xs:element name="cabecera">
<xs:complexType>
<xs:sequence>
<xs:element name="nitEmisor">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="9999999999999"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="razonSocialEmisor">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="200"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="municipio">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="25"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="telefono" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="25"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="numeroFactura">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="9999999999"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="cuf">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="100"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="cufd">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="100"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoSucursal">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="9999"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="direccion">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="500"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoPuntoVenta" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="9999"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="fechaEmision" type="xs:dateTime"/>
<xs:element name="nombreRazonSocial" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="500"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoTipoDocumentoIdentidad">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="5"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="numeroDocumento">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="20"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="complemento" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="5"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="direccionComprador">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="500"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoCliente">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="100"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="lugarDestino">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="500"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoPais">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="212"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoMetodoPago">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="308"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="numeroTarjeta" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="9999999999999999"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="montoTotal">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="17"/>
<xs:fractionDigits value="2"/>
<xs:minExclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="montoTotalSujetoIva" type="xs:decimal" fixed="0"/>
<xs:element name="codigoMoneda">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="154"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="tipoCambio">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="17"/>
<xs:fractionDigits value="5"/>
<xs:minExclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="montoTotalMoneda">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="17"/>
<xs:fractionDigits value="2"/>
<xs:minExclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="informacionAdicional"
nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="10000"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="descuentoAdicional" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="17"/>
<xs:fractionDigits value="2"/>
<xs:minInclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoExcepcion" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="cafc" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="50"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="leyenda">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="200"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="usuario">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="100"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoDocumentoSector"
type="xs:integer" fixed="28"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="detalle" minOccurs="1" maxOccurs="500">
<xs:complexType>
<xs:sequence>
<xs:element name="actividadEconomica">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoProductoSin">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="99999999"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="codigoProducto">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="50"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="descripcion">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="500"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="cantidad" type="xs:integer" fixed="1"/>
<xs:element name="unidadMedida">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="200"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="precioUnitario">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="20"/>
<xs:fractionDigits value="5"/>
<xs:minExclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="montoDescuento" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="20"/>
<xs:fractionDigits value="5"/>
<xs:minInclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="subTotal">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="20"/>
<xs:fractionDigits value="5"/>
<xs:minExclusive value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element ref="ds:Signature"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

Some files were not shown because too many files have changed in this diff Show More