├── server
├── signature
│ ├── src
│ │ └── lib.rs
│ └── Cargo.toml
├── ui
│ ├── client
│ │ ├── public
│ │ │ └── .gitkeep
│ │ ├── src
│ │ │ ├── app
│ │ │ │ ├── Constants.ts
│ │ │ │ ├── layout
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── default-layout.tsx
│ │ │ │ ├── assets
│ │ │ │ │ ├── patternfly_avatar.jpg
│ │ │ │ │ ├── pfbg-icon.svg
│ │ │ │ │ └── avatar.svg
│ │ │ │ ├── common
│ │ │ │ │ └── types.ts
│ │ │ │ └── components
│ │ │ │ │ ├── NotificationsContext.tsx
│ │ │ │ │ ├── Notifications.tsx
│ │ │ │ │ └── NotificationsProvider.tsx
│ │ │ ├── env.d.ts
│ │ │ ├── routes
│ │ │ │ ├── about.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── __root.tsx
│ │ │ ├── client
│ │ │ │ ├── models.ts
│ │ │ │ ├── rest.ts
│ │ │ │ └── helpers.ts
│ │ │ ├── index.tsx
│ │ │ └── assets
│ │ │ │ └── favicon.svg
│ │ ├── tsconfig.json
│ │ ├── index.html
│ │ └── package.json
│ ├── branding
│ │ ├── favicon.ico
│ │ ├── images
│ │ │ ├── logo.png
│ │ │ ├── logo192.png
│ │ │ └── logo512.png
│ │ ├── manifest.json
│ │ └── strings.json
│ ├── .gitignore
│ ├── crate
│ │ └── Cargo.toml
│ ├── common
│ │ ├── src
│ │ │ ├── branding-strings-stub.json
│ │ │ ├── index.ts
│ │ │ └── branding.ts
│ │ ├── tsconfig.json
│ │ ├── package.json
│ │ └── rollup.config.js
│ ├── biome.json
│ ├── server
│ │ ├── package.json
│ │ └── rollup.config.js
│ └── package.json
├── common
│ ├── src
│ │ └── lib.rs
│ └── Cargo.toml
├── api
│ ├── src
│ │ ├── lib.rs
│ │ └── system
│ │ │ └── error.rs
│ └── Cargo.toml
├── entity
│ ├── src
│ │ ├── prelude.rs
│ │ ├── mod.rs
│ │ ├── lib.rs
│ │ ├── document.rs
│ │ ├── keystore.rs
│ │ ├── send_rule.rs
│ │ ├── keystore_config.rs
│ │ └── credentials.rs
│ └── Cargo.toml
├── migration
│ ├── src
│ │ ├── main.rs
│ │ ├── lib.rs
│ │ ├── m20240113_213636_create_keystore.rs
│ │ ├── m20240117_142858_create_send_rule.rs
│ │ └── m20240101_104121_create_document.rs
│ ├── Cargo.toml
│ └── README.md
├── deploy
│ └── compose
│ │ └── scripts
│ │ ├── minio
│ │ └── setup.sh
│ │ └── keycloak
│ │ └── setup.sh
├── cli
│ └── Cargo.toml
├── xtask
│ ├── Cargo.toml
│ └── src
│ │ └── main.rs
├── server
│ ├── src
│ │ ├── server
│ │ │ └── health.rs
│ │ └── openapi.rs
│ └── Cargo.toml
├── storage
│ ├── Cargo.toml
│ └── src
│ │ └── config.rs
└── .gitignore
├── .devcontainer
├── .env
├── postCreateCommand.sh
├── postStartCommand.sh
├── onCreateCommand.sh
├── docker-compose.yml
├── entrypoint.sh
├── Dockerfile
└── devcontainer.json
├── xhandler
├── src
│ ├── lib.rs
│ └── prelude.rs
└── Cargo.toml
├── .github
├── FUNDING.yml
├── actions
│ └── install-dependencies
│ │ └── action.yml
├── workflows
│ ├── pr-checks.yml
│ ├── image-build.yaml
│ └── ci-repo.yml
└── dependabot.yml
├── xbuilder
├── src
│ ├── enricher
│ │ ├── bounds
│ │ │ ├── note
│ │ │ │ ├── debitnote
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ └── tipo_nota.rs
│ │ │ │ ├── creditnote
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ └── tipo_nota.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── total_importe.rs
│ │ │ │ └── tipo_comprobante_afectado.rs
│ │ │ ├── invoice
│ │ │ │ ├── mod.rs
│ │ │ │ ├── direccion_entrega.rs
│ │ │ │ ├── tipo_operacion.rs
│ │ │ │ ├── tipo_comprobante.rs
│ │ │ │ ├── forma_de_pago.rs
│ │ │ │ ├── total_importe.rs
│ │ │ │ ├── detraccion.rs
│ │ │ │ ├── anticipos.rs
│ │ │ │ └── descuentos.rs
│ │ │ ├── mod.rs
│ │ │ ├── detalle
│ │ │ │ ├── cantidad.rs
│ │ │ │ ├── icb_aplica.rs
│ │ │ │ ├── icb.rs
│ │ │ │ ├── igv.rs
│ │ │ │ ├── isc.rs
│ │ │ │ ├── igv_tipo.rs
│ │ │ │ ├── isc_tipo.rs
│ │ │ │ ├── precio.rs
│ │ │ │ ├── icb_tasa.rs
│ │ │ │ ├── igv_tasa.rs
│ │ │ │ ├── isc_tasa.rs
│ │ │ │ ├── unidad_medida.rs
│ │ │ │ ├── total_impuestos.rs
│ │ │ │ ├── precio_referencia.rs
│ │ │ │ ├── igv_base_imponible.rs
│ │ │ │ ├── isc_base_imponible.rs
│ │ │ │ ├── precio_con_impuestos.rs
│ │ │ │ ├── precio_referencia_tipo.rs
│ │ │ │ └── mod.rs
│ │ │ ├── serie_numero.rs
│ │ │ ├── moneda.rs
│ │ │ ├── icb.rs
│ │ │ ├── igv.rs
│ │ │ ├── firmante.rs
│ │ │ ├── ivap.rs
│ │ │ ├── proveedor.rs
│ │ │ ├── leyendas.rs
│ │ │ ├── fecha_emision.rs
│ │ │ └── total_impuestos.rs
│ │ ├── rules
│ │ │ ├── phase2process
│ │ │ │ ├── mod.rs
│ │ │ │ └── detalle
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── icb_aplica.rs
│ │ │ │ │ ├── igv.rs
│ │ │ │ │ ├── icb.rs
│ │ │ │ │ └── precio_referencia.rs
│ │ │ ├── phase1fill
│ │ │ │ ├── note
│ │ │ │ │ ├── debitnote
│ │ │ │ │ │ ├── mod.rs
│ │ │ │ │ │ └── tipo_nota.rs
│ │ │ │ │ ├── creditnote
│ │ │ │ │ │ ├── mod.rs
│ │ │ │ │ │ └── tipo_nota.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ └── tipo_comprobante_afectado.rs
│ │ │ │ ├── invoice
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── tipo_comprobante.rs
│ │ │ │ │ └── tipo_operacion.rs
│ │ │ │ ├── detalle
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── icb_tasa.rs
│ │ │ │ │ ├── unidad_medida.rs
│ │ │ │ │ ├── isc_tipo.rs
│ │ │ │ │ ├── igv_tipo.rs
│ │ │ │ │ ├── precio_referencia_tipo.rs
│ │ │ │ │ └── igv_tasa.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── moneda.rs
│ │ │ │ ├── icb_tasa.rs
│ │ │ │ ├── igv_tasa.rs
│ │ │ │ ├── ivap_tasa.rs
│ │ │ │ ├── fecha_emision.rs
│ │ │ │ ├── firmante.rs
│ │ │ │ └── proveedor.rs
│ │ │ ├── mod.rs
│ │ │ └── phase3summary
│ │ │ │ ├── note
│ │ │ │ ├── mod.rs
│ │ │ │ └── total_importe.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── invoice
│ │ │ │ ├── mod.rs
│ │ │ │ └── detraccion.rs
│ │ │ │ └── leyenda.rs
│ │ ├── mod.rs
│ │ └── process.rs
│ ├── models
│ │ ├── mod.rs
│ │ ├── debit_note.rs
│ │ ├── credit_note.rs
│ │ └── invoice.rs
│ ├── lib.rs
│ └── prelude.rs
├── build.rs
├── tests
│ ├── resources
│ │ ├── certificates
│ │ │ ├── LLAMA-PE-CERTIFICADO-DEMO-10467793549.pfx
│ │ │ └── private.key
│ │ └── xsd
│ │ │ ├── 2.0
│ │ │ ├── message.xml
│ │ │ └── message.xsl
│ │ │ └── 2.1
│ │ │ └── common
│ │ │ └── UBL-XAdESv141-2.1.xsd
│ ├── invoice_moneda.rs
│ ├── invoice_issue30.rs
│ ├── invoice_descuentos.rs
│ ├── invoice_percepcion.rs
│ ├── debit_note.rs
│ ├── invoice_detraccion.rs
│ ├── credit_note.rs
│ ├── invoice_orden_compra.rs
│ ├── credit_note_orden_de_compra.rs
│ ├── invoice_fecha_vencimiento.rs
│ ├── invoice_guias.rs
│ ├── invoice_anticipos.rs
│ ├── invoice_documento_relacionado.rs
│ └── invoice_igv_tipo.rs
├── resources
│ └── templates
│ │ ├── ubl
│ │ ├── standard
│ │ │ └── include
│ │ │ │ ├── ubl-extensions.xml
│ │ │ │ ├── guias.xml
│ │ │ │ ├── documentos-relacionados.xml
│ │ │ │ ├── general-data.xml
│ │ │ │ ├── monetary-total.xml
│ │ │ │ ├── contact.xml
│ │ │ │ ├── payment-terms.xml
│ │ │ │ ├── namespaces.xml
│ │ │ │ ├── note
│ │ │ │ └── invoice-reference.xml
│ │ │ │ ├── customer.xml
│ │ │ │ ├── supplier.xml
│ │ │ │ └── address.xml
│ │ ├── sunat
│ │ │ └── include
│ │ │ │ ├── receiver-party.xml
│ │ │ │ ├── supplier.xml
│ │ │ │ └── agent-party.xml
│ │ └── common
│ │ │ └── signature.xml
│ │ └── renderer
│ │ ├── voidedDocuments.xml
│ │ ├── creditNote.xml
│ │ └── debitNote.xml
└── Cargo.toml
├── .clippy.toml
├── xsender
├── README.md
├── build.rs
├── src
│ ├── prelude.rs
│ ├── lib.rs
│ └── soap
│ │ └── mod.rs
├── resources
│ ├── test
│ │ ├── bill_service_response_ticket.xml
│ │ └── bill_service_response_fault.xml
│ └── templates
│ │ ├── verify_ticket.xml
│ │ ├── send_bill.xml
│ │ ├── send_summary.xml
│ │ ├── validate_file.xml
│ │ ├── get_status_crd.xml
│ │ └── validate_cdp_criterios.xml
└── Cargo.toml
├── .dockerignore
├── rust-toolchain.toml
├── xsigner
├── resources
│ └── test
│ │ ├── LLAMA-PE-CERTIFICADO-DEMO-10467793549.pfx
│ │ ├── private.key
│ │ └── public.cer
├── README.md
└── Cargo.toml
├── README.md
├── .gitignore
├── entrypoint.ui.sh
├── Dockerfile.ui
└── Dockerfile.server
/server/signature/src/lib.rs:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/server/ui/client/public/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.devcontainer/.env:
--------------------------------------------------------------------------------
1 | USER_UID=115091
2 |
--------------------------------------------------------------------------------
/server/ui/client/src/app/Constants.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/xhandler/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod prelude;
2 |
--------------------------------------------------------------------------------
/server/common/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod config;
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 |
2 | github: [carlosthe19916]
3 |
--------------------------------------------------------------------------------
/server/api/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod db;
2 | pub mod system;
3 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/note/debitnote/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod tipo_nota;
2 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase2process/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod detalle;
2 |
--------------------------------------------------------------------------------
/.clippy.toml:
--------------------------------------------------------------------------------
1 | allow-unwrap-in-tests = true
2 | allow-expect-in-tests = true
3 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/note/creditnote/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod tipo_nota;
2 |
--------------------------------------------------------------------------------
/server/ui/client/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/note/debitnote/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod tipo_nota;
2 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/note/creditnote/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod tipo_nota;
2 |
--------------------------------------------------------------------------------
/xsender/README.md:
--------------------------------------------------------------------------------
1 | # XSender
2 |
3 | Send files to SUNAT through SOAP or REST
4 |
5 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/node_modules/
2 | **/dist/
3 | **/target/
4 |
5 | **/.idea/
6 | **/.openubl/
--------------------------------------------------------------------------------
/server/entity/src/prelude.rs:
--------------------------------------------------------------------------------
1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15
2 |
--------------------------------------------------------------------------------
/server/ui/client/src/app/layout/index.ts:
--------------------------------------------------------------------------------
1 | export { DefaultLayout } from "./default-layout";
2 |
--------------------------------------------------------------------------------
/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "1.83.0"
3 | components = [ "rustfmt", "clippy" ]
4 |
--------------------------------------------------------------------------------
/.devcontainer/postCreateCommand.sh:
--------------------------------------------------------------------------------
1 | # cd /workspace/server/ui && npm ci --ignore-scripts
2 | # corepack enable pnpm
--------------------------------------------------------------------------------
/server/entity/src/mod.rs:
--------------------------------------------------------------------------------
1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15
2 |
3 | pub mod prelude;
4 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod phase1fill;
2 | pub mod phase2process;
3 | pub mod phase3summary;
4 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase3summary/note/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod total_importe;
2 | pub mod total_impuestos;
3 |
--------------------------------------------------------------------------------
/xbuilder/src/models/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod common;
2 | pub mod credit_note;
3 | pub mod debit_note;
4 | pub mod invoice;
5 |
--------------------------------------------------------------------------------
/xhandler/src/prelude.rs:
--------------------------------------------------------------------------------
1 | pub use xbuilder::prelude::*;
2 | pub use xsender::prelude::*;
3 | pub use xsigner::*;
4 |
--------------------------------------------------------------------------------
/server/ui/branding/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/project-openubl/xhandler-rust/HEAD/server/ui/branding/favicon.ico
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase3summary/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod invoice;
2 | pub mod leyenda;
3 | pub mod note;
4 | pub mod utils;
5 |
--------------------------------------------------------------------------------
/.devcontainer/postStartCommand.sh:
--------------------------------------------------------------------------------
1 | # Git autocomplete
2 | echo "source /usr/share/bash-completion/completions/git" >> ~/.bashrc
3 |
--------------------------------------------------------------------------------
/server/ui/branding/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/project-openubl/xhandler-rust/HEAD/server/ui/branding/images/logo.png
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/note/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod creditnote;
2 | pub mod debitnote;
3 | pub mod tipo_comprobante_afectado;
4 |
--------------------------------------------------------------------------------
/server/ui/branding/images/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/project-openubl/xhandler-rust/HEAD/server/ui/branding/images/logo192.png
--------------------------------------------------------------------------------
/server/ui/branding/images/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/project-openubl/xhandler-rust/HEAD/server/ui/branding/images/logo512.png
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/note/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod creditnote;
2 | pub mod debitnote;
3 | pub mod tipo_comprobante_afectado;
4 | pub mod total_importe;
5 |
--------------------------------------------------------------------------------
/xbuilder/build.rs:
--------------------------------------------------------------------------------
1 | use static_files::resource_dir;
2 |
3 | fn main() -> std::io::Result<()> {
4 | resource_dir("./resources/templates").build()
5 | }
6 |
--------------------------------------------------------------------------------
/xsender/build.rs:
--------------------------------------------------------------------------------
1 | use static_files::resource_dir;
2 |
3 | fn main() -> std::io::Result<()> {
4 | resource_dir("./resources/templates").build()
5 | }
6 |
--------------------------------------------------------------------------------
/xsender/src/prelude.rs:
--------------------------------------------------------------------------------
1 | pub use crate::client_sunat::*;
2 | pub use crate::file_sender::*;
3 | pub use crate::models::*;
4 | pub use crate::ubl_file::*;
5 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase3summary/invoice/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod detraccion;
2 | pub mod percepcion;
3 | pub mod total_importe;
4 | pub mod total_impuestos;
5 |
--------------------------------------------------------------------------------
/server/migration/src/main.rs:
--------------------------------------------------------------------------------
1 | use sea_orm_migration::prelude::*;
2 |
3 | #[async_std::main]
4 | async fn main() {
5 | cli::run_cli(migration::Migrator).await;
6 | }
7 |
--------------------------------------------------------------------------------
/server/ui/client/src/app/assets/patternfly_avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/project-openubl/xhandler-rust/HEAD/server/ui/client/src/app/assets/patternfly_avatar.jpg
--------------------------------------------------------------------------------
/xsigner/resources/test/LLAMA-PE-CERTIFICADO-DEMO-10467793549.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/project-openubl/xhandler-rust/HEAD/xsigner/resources/test/LLAMA-PE-CERTIFICADO-DEMO-10467793549.pfx
--------------------------------------------------------------------------------
/server/ui/.gitignore:
--------------------------------------------------------------------------------
1 | # Local
2 | .DS_Store
3 | *.local
4 | *.log*
5 |
6 | # Dist
7 | node_modules
8 | dist/
9 |
10 | # IDE
11 | .vscode/*
12 | !.vscode/extensions.json
13 | .idea
14 |
--------------------------------------------------------------------------------
/xsender/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod analyzer;
2 | mod client_sunat;
3 | mod constants;
4 | mod file_sender;
5 | mod models;
6 | pub mod prelude;
7 | mod soap;
8 | mod ubl_file;
9 | mod zip_manager;
10 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/invoice/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod anticipos;
2 | pub mod descuentos;
3 | pub mod forma_de_pago;
4 | pub mod leyenda;
5 | pub mod tipo_comprobante;
6 | pub mod tipo_operacion;
7 |
--------------------------------------------------------------------------------
/server/ui/client/src/app/common/types.ts:
--------------------------------------------------------------------------------
1 | export interface Page {
2 | page: number;
3 | perPage: number;
4 | }
5 |
6 | export interface SortBy {
7 | index: number;
8 | direction: "asc" | "desc";
9 | }
10 |
--------------------------------------------------------------------------------
/xbuilder/tests/resources/certificates/LLAMA-PE-CERTIFICADO-DEMO-10467793549.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/project-openubl/xhandler-rust/HEAD/xbuilder/tests/resources/certificates/LLAMA-PE-CERTIFICADO-DEMO-10467793549.pfx
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/standard/include/ubl-extensions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/xsender/src/soap/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod cdr;
2 | pub mod envelope;
3 | pub mod send_file_response;
4 | pub mod verify_ticket_response;
5 |
6 | pub struct SoapFault {
7 | pub code: String,
8 | pub message: String,
9 | }
10 |
--------------------------------------------------------------------------------
/server/common/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "openubl-common"
3 | version.workspace = true
4 | edition.workspace = true
5 | license.workspace = true
6 |
7 | [dependencies]
8 | clap = { workspace = true, features = ["derive", "env"] }
9 |
--------------------------------------------------------------------------------
/xbuilder/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod catalogs;
2 | pub mod enricher;
3 | pub mod models;
4 | pub mod prelude;
5 | pub mod renderer;
6 |
7 | pub const FACTURA_SERIE_REGEX: &str = "^[F|f].*$";
8 | pub const BOLETA_SERIE_REGEX: &str = "^[B|f].*$";
9 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/detalle/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod detalles;
2 | pub mod icb_tasa;
3 | pub mod igv_tasa;
4 | pub mod igv_tipo;
5 | pub mod isc_tasa;
6 | pub mod isc_tipo;
7 | pub mod precio_referencia_tipo;
8 | pub mod unidad_medida;
9 |
--------------------------------------------------------------------------------
/xsigner/README.md:
--------------------------------------------------------------------------------
1 | # XSigner
2 |
3 | Sign your XML files
4 |
5 | ```shell
6 | https://xmlsec.readthedocs.io/en/stable/install.html
7 | ```
8 |
9 | ## Fedora
10 | sudo dnf install pkg-config libxml2-devel xmlsec1-devel xmlsec1-openssl clang-devel
11 |
--------------------------------------------------------------------------------
/server/entity/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
2 |
3 | pub mod prelude;
4 |
5 | pub mod credentials;
6 | pub mod delivery;
7 | pub mod document;
8 | pub mod keystore;
9 | pub mod keystore_config;
10 | pub mod send_rule;
11 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod detalle;
2 | pub mod fecha_emision;
3 | pub mod firmante;
4 | pub mod icb_tasa;
5 | pub mod igv_tasa;
6 | pub mod invoice;
7 | pub mod ivap_tasa;
8 | pub mod moneda;
9 | pub mod note;
10 | pub mod proveedor;
11 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/invoice/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod anticipos;
2 | pub mod descuentos;
3 | pub mod detraccion;
4 | pub mod direccion_entrega;
5 | pub mod forma_de_pago;
6 | pub mod percepcion;
7 | pub mod tipo_comprobante;
8 | pub mod tipo_operacion;
9 | pub mod total_importe;
10 |
--------------------------------------------------------------------------------
/xbuilder/src/prelude.rs:
--------------------------------------------------------------------------------
1 | pub use crate::catalogs::*;
2 | pub use crate::enricher::{Defaults, Enrich};
3 | pub use crate::models::common::*;
4 | pub use crate::models::credit_note::*;
5 | pub use crate::models::debit_note::*;
6 | pub use crate::models::invoice::*;
7 | pub use crate::renderer::Renderer;
8 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/standard/include/guias.xml:
--------------------------------------------------------------------------------
1 |
2 | {%- for it in guias %}
3 |
4 | {{it.serie_numero}}
5 | {{it.tipo_documento}}
6 |
7 | {%- endfor %}
--------------------------------------------------------------------------------
/server/signature/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "openubl-signature"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | anyhow = { workspace = true }
10 | thiserror = { workspace = true }
11 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod detalle;
2 | pub mod fecha_emision;
3 | pub mod firmante;
4 | pub mod icb;
5 | pub mod igv;
6 | pub mod invoice;
7 | pub mod ivap;
8 | pub mod leyendas;
9 | pub mod moneda;
10 | pub mod note;
11 | pub mod proveedor;
12 | pub mod serie_numero;
13 | pub mod total_impuestos;
14 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase2process/detalle/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod detalles;
2 | pub mod icb;
3 | pub mod icb_aplica;
4 | pub mod igv;
5 | pub mod igv_base_imponible;
6 | pub mod isc;
7 | pub mod isc_base_imponible;
8 | pub mod precio;
9 | pub mod precio_con_impuestos;
10 | pub mod precio_referencia;
11 | pub mod total_impuestos;
12 |
--------------------------------------------------------------------------------
/.github/actions/install-dependencies/action.yml:
--------------------------------------------------------------------------------
1 | name: Install XHandler dependencies
2 | description: Install XHandler dependencies.
3 | runs:
4 | using: "composite"
5 | steps:
6 | - name: Install dependencies
7 | shell: bash
8 | run: |
9 | sudo apt-get -y install pkg-config libssl-dev libxml2-dev libclang-dev
10 |
--------------------------------------------------------------------------------
/server/api/src/system/error.rs:
--------------------------------------------------------------------------------
1 | use sea_orm::DbErr;
2 |
3 | #[derive(Debug, thiserror::Error)]
4 | pub enum Error {
5 | #[error(transparent)]
6 | Json(#[from] serde_json::Error),
7 |
8 | #[error(transparent)]
9 | Database(#[from] DbErr),
10 |
11 | #[error(transparent)]
12 | Any(#[from] anyhow::Error),
13 | }
14 |
--------------------------------------------------------------------------------
/server/ui/client/src/routes/about.tsx:
--------------------------------------------------------------------------------
1 | import { createFileRoute } from "@tanstack/react-router";
2 |
3 | export const Route = createFileRoute("/about")({
4 | component: AboutComponent,
5 | });
6 |
7 | function AboutComponent() {
8 | return (
9 |
10 |
About
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/xhandler/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "xhandler"
3 | version = "0.1.0"
4 | edition = "2021"
5 | license = "Apache-2.0"
6 | description = "Creates XML files based on UBL under the standards of Peru."
7 |
8 | [dependencies]
9 | xbuilder = { path = "../xbuilder" }
10 | xsender = { path = "../xsender" }
11 | xsigner = { path = "../xsigner" }
12 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/standard/include/documentos-relacionados.xml:
--------------------------------------------------------------------------------
1 |
2 | {%- for it in documentos_relacionados %}
3 |
4 | {{it.serie_numero}}
5 | {{it.tipo_documento}}
6 |
7 | {%- endfor %}
--------------------------------------------------------------------------------
/server/entity/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "openubl-entity"
3 | version.workspace = true
4 | edition.workspace = true
5 | license.workspace = true
6 | publish = false
7 |
8 | [dependencies]
9 | sea-orm = { workspace = true, features = [
10 | "sqlx-sqlite",
11 | "sqlx-postgres",
12 | "runtime-tokio-rustls",
13 | "macros",
14 | ] }
15 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/cantidad.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detalle;
4 |
5 | pub trait DetalleCantidadGetter {
6 | fn get_cantidad(&self) -> Decimal;
7 | }
8 |
9 | impl DetalleCantidadGetter for Detalle {
10 | fn get_cantidad(&self) -> Decimal {
11 | self.cantidad
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/standard/include/general-data.xml:
--------------------------------------------------------------------------------
1 |
2 | 2.1
3 | 2.0
4 | {{serie_numero}}
5 | {{fecha_emision}}
6 | {%- if hora_emision %}
7 | {{hora_emision}}
8 | {%- endif %}
--------------------------------------------------------------------------------
/server/ui/branding/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "openubl-ui",
3 | "name": "Openubl UI",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
--------------------------------------------------------------------------------
/server/deploy/compose/scripts/minio/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -l
2 |
3 | # Connect to minio
4 | /usr/bin/mc config host add myminio http://minio:9000 admin password;
5 |
6 | # Create buckets
7 | /usr/bin/mc mb myminio/openubl || true;
8 |
9 | # Config events
10 | /usr/bin/mc event add myminio/openubl arn:minio:sqs::OPENUBL:nats --event "put,delete";
11 |
12 | # Restart service
13 | /usr/bin/mc admin service restart myminio;
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/invoice/direccion_entrega.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::Direccion;
2 | use crate::models::invoice::Invoice;
3 |
4 | pub trait InvoiceDireccionEntregaGetter {
5 | fn get_direccion_entrega(&self) -> &Option;
6 | }
7 |
8 | impl InvoiceDireccionEntregaGetter for Invoice {
9 | fn get_direccion_entrega(&self) -> &Option {
10 | &self.direccion_entrega
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/standard/include/monetary-total.xml:
--------------------------------------------------------------------------------
1 |
2 | {{total_importe.importe_sin_impuestos | round_decimal}}
3 | {{total_importe.importe | round_decimal}}
4 | {{total_importe.importe | round_decimal}}
--------------------------------------------------------------------------------
/.github/workflows/pr-checks.yml:
--------------------------------------------------------------------------------
1 | name: PR Checks
2 |
3 | on:
4 | pull_request_target:
5 | types: [opened, edited, reopened, synchronize]
6 |
7 | jobs:
8 | verify:
9 | runs-on: ubuntu-latest
10 | name: Verify PR contents
11 | steps:
12 | - name: Check Title
13 | id: verifier
14 | uses: project-openubl/release-tools/cmd/verify-pr@main
15 | with:
16 | github_token: ${{ secrets.GITHUB_TOKEN }}
17 |
--------------------------------------------------------------------------------
/server/cli/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "openubl-cli"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [[bin]]
7 | name = "server"
8 | path = "src/main.rs"
9 |
10 | [dependencies]
11 | openubl-server = { workspace = true }
12 |
13 | clap = { workspace = true, features = ["derive", "env"] }
14 | anyhow = { workspace = true }
15 | actix-web = { workspace = true }
16 | log = { workspace = true }
17 | tokio = { workspace = true, features = ["full"] }
18 |
--------------------------------------------------------------------------------
/server/ui/client/src/client/models.ts:
--------------------------------------------------------------------------------
1 | export interface Credentials {
2 | id: number;
3 | name: string;
4 | description?: string;
5 | supplier_ids_applied_to: string[];
6 | soap?: {
7 | username_sol: string;
8 | password_sol: string;
9 | url_invoice: string;
10 | url_perception_retention: string;
11 | };
12 | rest?: {
13 | client_id: string;
14 | client_secret: string;
15 | url_despatch: string;
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/sunat/include/receiver-party.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | {cliente.numeroDocumentoIdentidad}
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/xbuilder/tests/resources/xsd/2.0/message.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/xbuilder/tests/resources/xsd/2.0/message.xsl:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/server/ui/crate/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "openubl-ui"
3 | version.workspace = true
4 | edition.workspace = true
5 | license.workspace = true
6 |
7 | [dependencies]
8 | anyhow = { workspace = true }
9 | base64 = { workspace = true }
10 | serde = { workspace = true, features = ["derive"] }
11 | serde_json = { workspace = true }
12 | static-files = { workspace = true }
13 | tera = { workspace = true }
14 |
15 | [build-dependencies]
16 | static-files = { workspace = true }
17 |
--------------------------------------------------------------------------------
/.devcontainer/onCreateCommand.sh:
--------------------------------------------------------------------------------
1 | sudo dnf install -y @development-tools
2 | sudo dnf install -y @c-development
3 | sudo dnf install -y libxml2-devel openssl-devel gcc gcc-c++ cmake perl
4 |
5 | ## Install Rust
6 | sudo dnf install -y rustup
7 |
8 | ## Install NVM
9 | curl -s https://raw.githubusercontent.com/devcontainers/features/refs/heads/main/src/node/install.sh | sudo VERSION=22 bash
10 |
11 | ## Configure Rust
12 | rustup-init -y
13 | . "$HOME/.cargo/env"
14 | rustup update
15 |
--------------------------------------------------------------------------------
/server/ui/common/src/branding-strings-stub.json:
--------------------------------------------------------------------------------
1 | {
2 | "application": {
3 | "title": "Stub to allow package build to work",
4 | "name": "",
5 | "description": ""
6 | },
7 | "about": {
8 | "displayName": "",
9 | "image": "",
10 | "documentationUrl": ""
11 | },
12 | "masthead": {
13 | "leftBrand": {
14 | "src": "",
15 | "alt": "",
16 | "height": ""
17 | },
18 | "leftTitle": null,
19 | "rightBrand": null
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/standard/include/contact.xml:
--------------------------------------------------------------------------------
1 | {%- macro contact(contacto) -%}
2 |
3 | {%- if contacto.telefono %}
4 | {{contacto.telefono}}
5 | {%- endif %}
6 | {%- if contacto.email %}
7 | {{contacto.email}}
8 | {%- endif %}
9 |
10 | {%- endmacro contact -%}
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/sunat/include/supplier.xml:
--------------------------------------------------------------------------------
1 |
2 | {proveedor.ruc}
3 | 6
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/xsender/resources/test/bill_service_response_ticket.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 1703154974517
6 |
7 |
8 |
--------------------------------------------------------------------------------
/server/xtask/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "openubl-xtask"
3 | version.workspace = true
4 | edition.workspace = true
5 | license.workspace = true
6 |
7 | [[bin]]
8 | name = "xtask"
9 | path = "src/main.rs"
10 |
11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12 |
13 | [dependencies]
14 | openubl-server = { workspace = true }
15 |
16 | anyhow = { workspace = true }
17 | clap = { workspace = true, features = ["derive", "env"] }
18 | tokio = { workspace = true, features = ["full"] }
19 |
--------------------------------------------------------------------------------
/server/ui/client/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import { Content, PageSection } from "@patternfly/react-core";
2 | import { createFileRoute } from "@tanstack/react-router";
3 |
4 | export const Route = createFileRoute("/")({
5 | component: HomeComponent,
6 | });
7 |
8 | function HomeComponent() {
9 | return (
10 | <>
11 |
12 |
13 | Openubl
14 | Fully hosted and managed service
15 |
16 |
17 | >
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/server/ui/branding/strings.json:
--------------------------------------------------------------------------------
1 | {
2 | "application": {
3 | "title": "Openubl",
4 | "name": "Openubl UI",
5 | "description": "Openubl UI"
6 | },
7 | "about": {
8 | "displayName": "Openubl",
9 | "imageSrc": "<%= brandingRoot %>/images/masthead-logo.svg",
10 | "documentationUrl": "https://project-openubl.github.io/"
11 | },
12 | "masthead": {
13 | "leftBrand": {
14 | "src": "<%= brandingRoot %>/images/masthead-logo.svg",
15 | "alt": "brand",
16 | "height": "40px"
17 | },
18 | "leftTitle": null,
19 | "rightBrand": null
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/server/ui/common/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "include": ["src/**/*"],
4 | "compilerOptions": {
5 | "strict": true,
6 | "target": "es2020",
7 | "module": "node16",
8 | "moduleResolution": "node16",
9 | "outDir": "./dist",
10 | "declaration": true,
11 | "declarationMap": true,
12 | "sourceMap": true,
13 | "inlineSources": true,
14 | "resolveJsonModule": true,
15 | "paths": {
16 | "@branding/strings.json": ["./src/branding-strings-stub.json"]
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/icb_aplica.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::Detalle;
2 |
3 | pub trait DetalleICBAplicaGetter {
4 | fn get_icb_aplica(&self) -> bool;
5 | }
6 |
7 | pub trait DetalleIcbAplicaSetter {
8 | fn set_icb_aplica(&mut self, val: bool);
9 | }
10 |
11 | impl DetalleICBAplicaGetter for Detalle {
12 | fn get_icb_aplica(&self) -> bool {
13 | self.icb_aplica
14 | }
15 | }
16 |
17 | impl DetalleIcbAplicaSetter for Detalle {
18 | fn set_icb_aplica(&mut self, val: bool) {
19 | self.icb_aplica = val;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/icb.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detalle;
4 |
5 | pub trait DetalleIcbGetter {
6 | fn get_icb(&self) -> &Option;
7 | }
8 |
9 | pub trait DetalleIcbSetter {
10 | fn set_icb(&mut self, val: Decimal);
11 | }
12 |
13 | impl DetalleIcbGetter for Detalle {
14 | fn get_icb(&self) -> &Option {
15 | &self.icb
16 | }
17 | }
18 |
19 | impl DetalleIcbSetter for Detalle {
20 | fn set_icb(&mut self, val: Decimal) {
21 | self.icb = Some(val);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/igv.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detalle;
4 |
5 | pub trait DetalleIgvGetter {
6 | fn get_igv(&self) -> &Option;
7 | }
8 |
9 | pub trait DetalleIgvSetter {
10 | fn set_igv(&mut self, val: Decimal);
11 | }
12 |
13 | impl DetalleIgvGetter for Detalle {
14 | fn get_igv(&self) -> &Option {
15 | &self.igv
16 | }
17 | }
18 |
19 | impl DetalleIgvSetter for Detalle {
20 | fn set_igv(&mut self, val: Decimal) {
21 | self.igv = Some(val);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/isc.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detalle;
4 |
5 | pub trait DetalleIscGetter {
6 | fn get_isc(&self) -> &Option;
7 | }
8 |
9 | pub trait DetalleISCSetter {
10 | fn set_isc(&mut self, val: Decimal);
11 | }
12 |
13 | impl DetalleIscGetter for Detalle {
14 | fn get_isc(&self) -> &Option {
15 | &self.isc
16 | }
17 | }
18 |
19 | impl DetalleISCSetter for Detalle {
20 | fn set_isc(&mut self, val: Decimal) {
21 | self.isc = Some(val);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/moneda.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::enricher::bounds::moneda::{MonedaGetter, MonedaSetter};
4 |
5 | pub trait MonedaFillRule {
6 | fn fill(&mut self) -> Result;
7 | }
8 |
9 | impl MonedaFillRule for T
10 | where
11 | T: MonedaGetter + MonedaSetter,
12 | {
13 | fn fill(&mut self) -> Result {
14 | match &self.get_moneda() {
15 | Some(..) => Ok(false),
16 | None => {
17 | self.set_moneda("PEN");
18 | Ok(true)
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/igv_tipo.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::Detalle;
2 |
3 | pub trait DetalleIgvTipoGetter {
4 | fn get_igv_tipo(&self) -> &Option<&'static str>;
5 | }
6 |
7 | pub trait DetalleIgvTipoSetter {
8 | fn set_igv_tipo(&mut self, val: &'static str);
9 | }
10 |
11 | impl DetalleIgvTipoGetter for Detalle {
12 | fn get_igv_tipo(&self) -> &Option<&'static str> {
13 | &self.igv_tipo
14 | }
15 | }
16 |
17 | impl DetalleIgvTipoSetter for Detalle {
18 | fn set_igv_tipo(&mut self, val: &'static str) {
19 | self.igv_tipo = Some(val);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/isc_tipo.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::Detalle;
2 |
3 | pub trait DetalleIscTipoGetter {
4 | fn get_isc_tipo(&self) -> &Option<&'static str>;
5 | }
6 |
7 | pub trait DetalleIscTipoSetter {
8 | fn set_isc_tipo(&mut self, val: &'static str);
9 | }
10 |
11 | impl DetalleIscTipoGetter for Detalle {
12 | fn get_isc_tipo(&self) -> &Option<&'static str> {
13 | &self.isc_tipo
14 | }
15 | }
16 |
17 | impl DetalleIscTipoSetter for Detalle {
18 | fn set_isc_tipo(&mut self, val: &'static str) {
19 | self.isc_tipo = Some(val);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/precio.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detalle;
4 |
5 | pub trait DetallePrecioGetter {
6 | fn get_precio(&self) -> &Option;
7 | }
8 |
9 | pub trait DetallePrecioSetter {
10 | fn set_precio(&mut self, val: Decimal);
11 | }
12 |
13 | impl DetallePrecioGetter for Detalle {
14 | fn get_precio(&self) -> &Option {
15 | &self.precio
16 | }
17 | }
18 |
19 | impl DetallePrecioSetter for Detalle {
20 | fn set_precio(&mut self, val: Decimal) {
21 | self.precio = Some(val);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.devcontainer/docker-compose.yml:
--------------------------------------------------------------------------------
1 | volumes:
2 | cargo-cache:
3 | npm-cache:
4 |
5 | services:
6 | xhandler-rust:
7 | # https://github.com/microsoft/vscode-remote-release/issues/10215
8 | image: localhost/xhandler-rust_devcontainer_xhandler-rust:latest
9 | build:
10 | context: .
11 | dockerfile: Dockerfile
12 | args:
13 | USER_UID: ${USER_UID}
14 | privileged: true
15 | userns_mode: "keep-id"
16 | command: tail -f /dev/null
17 | volumes:
18 | - ..:/workspace:cached
19 | - cargo-cache:/home/vscode/.cargo
20 | - npm-cache:/home/vscode/.npm
21 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/icb_tasa.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detalle;
4 |
5 | pub trait DetalleIcbTasaGetter {
6 | fn get_icb_tasa(&self) -> &Option;
7 | }
8 |
9 | pub trait DetalleIcbTasaSetter {
10 | fn set_icb_tasa(&mut self, val: Decimal);
11 | }
12 |
13 | impl DetalleIcbTasaGetter for Detalle {
14 | fn get_icb_tasa(&self) -> &Option {
15 | &self.icb_tasa
16 | }
17 | }
18 |
19 | impl DetalleIcbTasaSetter for Detalle {
20 | fn set_icb_tasa(&mut self, val: Decimal) {
21 | self.icb_tasa = Some(val);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/igv_tasa.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detalle;
4 |
5 | pub trait DetalleIgvTasaGetter {
6 | fn get_igv_tasa(&self) -> &Option;
7 | }
8 |
9 | pub trait DetalleIgvTasaSetter {
10 | fn set_igv_tasa(&mut self, val: Decimal);
11 | }
12 |
13 | impl DetalleIgvTasaGetter for Detalle {
14 | fn get_igv_tasa(&self) -> &Option {
15 | &self.igv_tasa
16 | }
17 | }
18 |
19 | impl DetalleIgvTasaSetter for Detalle {
20 | fn set_igv_tasa(&mut self, val: Decimal) {
21 | self.igv_tasa = Some(val);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/isc_tasa.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detalle;
4 |
5 | pub trait DetalleIscTasaGetter {
6 | fn get_isc_tasa(&self) -> &Option;
7 | }
8 |
9 | pub trait DetalleIscTasaSetter {
10 | fn set_isc_tasa(&mut self, val: Decimal);
11 | }
12 |
13 | impl DetalleIscTasaGetter for Detalle {
14 | fn get_isc_tasa(&self) -> &Option {
15 | &self.isc_tasa
16 | }
17 | }
18 |
19 | impl DetalleIscTasaSetter for Detalle {
20 | fn set_isc_tasa(&mut self, val: Decimal) {
21 | self.isc_tasa = Some(val);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/note/debitnote/tipo_nota.rs:
--------------------------------------------------------------------------------
1 | use crate::models::debit_note::DebitNote;
2 |
3 | pub trait DebitNoteTipoGetter {
4 | fn get_tipo_nota(&self) -> &Option<&'static str>;
5 | }
6 |
7 | pub trait DebitNoteTipoSetter {
8 | fn set_tipo_nota(&mut self, val: &'static str);
9 | }
10 |
11 | impl DebitNoteTipoGetter for DebitNote {
12 | fn get_tipo_nota(&self) -> &Option<&'static str> {
13 | &self.tipo_nota
14 | }
15 | }
16 |
17 | impl DebitNoteTipoSetter for DebitNote {
18 | fn set_tipo_nota(&mut self, val: &'static str) {
19 | self.tipo_nota = Some(val);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/note/creditnote/tipo_nota.rs:
--------------------------------------------------------------------------------
1 | use crate::models::credit_note::CreditNote;
2 |
3 | pub trait CreditNoteTipoGetter {
4 | fn get_tipo_nota(&self) -> &Option<&'static str>;
5 | }
6 |
7 | pub trait CreditNoteTipoSetter {
8 | fn set_tipo_nota(&mut self, val: &'static str);
9 | }
10 |
11 | impl CreditNoteTipoGetter for CreditNote {
12 | fn get_tipo_nota(&self) -> &Option<&'static str> {
13 | &self.tipo_nota
14 | }
15 | }
16 |
17 | impl CreditNoteTipoSetter for CreditNote {
18 | fn set_tipo_nota(&mut self, val: &'static str) {
19 | self.tipo_nota = Some(val);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/sunat/include/agent-party.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | {proveedor.ruc}
4 |
5 | {%- if proveedor.nombreComercial %}
6 |
7 |
8 |
9 | {%- endif %}
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/server/server/src/server/health.rs:
--------------------------------------------------------------------------------
1 | use actix_web::{get, web, Responder};
2 |
3 | use crate::server::Error;
4 | use crate::AppState;
5 |
6 | #[utoipa::path(
7 | responses(
8 | (status = 200, description = "Liveness"),
9 | ),
10 | )]
11 | #[get("/health/live")]
12 | pub async fn liveness(_: web::Data) -> Result {
13 | Ok("Live")
14 | }
15 |
16 | #[utoipa::path(
17 | responses(
18 | (status = 200, description = "Readiness"),
19 | ),
20 | )]
21 | #[get("/health/read")]
22 | pub async fn readiness(_: web::Data) -> Result {
23 | Ok("Read")
24 | }
25 |
--------------------------------------------------------------------------------
/xsigner/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "xsigner"
3 | version = "0.1.0"
4 | edition = "2021"
5 | license = "Apache-2.0"
6 | description = "Sign your XML files"
7 |
8 | [dependencies]
9 | thiserror = { workspace = true }
10 | anyhow = { workspace = true }
11 | libxml = { workspace = true }
12 | quick-xml = { workspace = true }
13 | rsa = { workspace = true, features = ["sha2"] }
14 | x509-cert = { workspace = true, features = ["builder"] }
15 | der = { workspace = true, features = ["alloc", "derive", "flagset", "oid"] }
16 | base64 = { workspace = true }
17 | openssl = { workspace = true }
18 | xml_c14n = { workspace = true }
19 |
20 | [dev-dependencies]
21 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/unidad_medida.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::Detalle;
2 |
3 | pub trait DetalleUnidadMedidaGetter {
4 | fn get_unidad_medida(&self) -> &Option<&'static str>;
5 | }
6 |
7 | pub trait DetalleUnidadMedidaSetter {
8 | fn set_unidad_medida(&mut self, val: &'static str);
9 | }
10 |
11 | impl DetalleUnidadMedidaGetter for Detalle {
12 | fn get_unidad_medida(&self) -> &Option<&'static str> {
13 | &self.unidad_medida
14 | }
15 | }
16 |
17 | impl DetalleUnidadMedidaSetter for Detalle {
18 | fn set_unidad_medida(&mut self, val: &'static str) {
19 | self.unidad_medida = Some(val);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/invoice/tipo_operacion.rs:
--------------------------------------------------------------------------------
1 | use crate::models::invoice::Invoice;
2 |
3 | pub trait InvoiceTipoOperacionGetter {
4 | fn get_tipo_operacion(&self) -> &Option<&'static str>;
5 | }
6 |
7 | pub trait InvoiceTipoOperacionSetter {
8 | fn set_tipo_operacion(&mut self, val: &'static str);
9 | }
10 |
11 | impl InvoiceTipoOperacionGetter for Invoice {
12 | fn get_tipo_operacion(&self) -> &Option<&'static str> {
13 | &self.tipo_operacion
14 | }
15 | }
16 |
17 | impl InvoiceTipoOperacionSetter for Invoice {
18 | fn set_tipo_operacion(&mut self, val: &'static str) {
19 | self.tipo_operacion = Some(val);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.devcontainer/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Ensure $HOME exists when starting
4 | if [ ! -d "${HOME}" ]; then
5 | mkdir -p "${HOME}"
6 | fi
7 |
8 | # Setup $PS1 for a consistent and reasonable prompt
9 | if [ -w "${HOME}" ] && [ ! -f "${HOME}"/.bashrc ]; then
10 | echo "PS1='\s-\v \w \$ '" > "${HOME}"/.bashrc
11 | fi
12 |
13 | # Add current (arbitrary) user to /etc/passwd and /etc/group
14 | if ! whoami > /dev/null 2>&1; then
15 | if [ -w /etc/passwd ]; then
16 | echo "update passwd file"
17 | echo "${USER_NAME:-user}:x:$(id -u):0:${USER_NAME:-user} user:${HOME}:/bin/bash" >> /etc/passwd
18 | echo "${USER_NAME:-user}:x:$(id -u):" >> /etc/group
19 | fi
20 | fi
21 |
--------------------------------------------------------------------------------
/server/xtask/src/main.rs:
--------------------------------------------------------------------------------
1 | use clap::{Parser, Subcommand};
2 |
3 | mod openapi;
4 |
5 | #[derive(Debug, Parser)]
6 | pub struct Xtask {
7 | #[command(subcommand)]
8 | command: Command,
9 | }
10 |
11 | impl Xtask {
12 | pub async fn run(self) -> anyhow::Result<()> {
13 | match self.command {
14 | Command::Openapi(command) => command.run().await,
15 | }
16 | }
17 | }
18 |
19 | #[derive(Debug, Subcommand)]
20 | pub enum Command {
21 | /// Used to generate and/or validate the openapi spec
22 | Openapi(openapi::Openapi),
23 | }
24 |
25 | #[tokio::main]
26 | async fn main() -> anyhow::Result<()> {
27 | Xtask::parse().run().await
28 | }
29 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/serie_numero.rs:
--------------------------------------------------------------------------------
1 | use crate::models::credit_note::CreditNote;
2 | use crate::models::debit_note::DebitNote;
3 | use crate::models::invoice::Invoice;
4 |
5 | pub trait SerieNumeroGetter {
6 | fn get_serie_numero(&self) -> &str;
7 | }
8 |
9 | impl SerieNumeroGetter for Invoice {
10 | fn get_serie_numero(&self) -> &str {
11 | self.serie_numero
12 | }
13 | }
14 |
15 | impl SerieNumeroGetter for CreditNote {
16 | fn get_serie_numero(&self) -> &str {
17 | self.serie_numero
18 | }
19 | }
20 |
21 | impl SerieNumeroGetter for DebitNote {
22 | fn get_serie_numero(&self) -> &str {
23 | self.serie_numero
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/invoice/tipo_comprobante.rs:
--------------------------------------------------------------------------------
1 | use crate::models::invoice::Invoice;
2 |
3 | pub trait InvoiceTipoComprobanteGetter {
4 | fn get_tipo_comprobante(&self) -> &Option<&'static str>;
5 | }
6 |
7 | pub trait InvoiceTipoComprobanteSetter {
8 | fn set_tipo_comprobante(&mut self, val: &'static str);
9 | }
10 |
11 | impl InvoiceTipoComprobanteGetter for Invoice {
12 | fn get_tipo_comprobante(&self) -> &Option<&'static str> {
13 | &self.tipo_comprobante
14 | }
15 | }
16 |
17 | impl InvoiceTipoComprobanteSetter for Invoice {
18 | fn set_tipo_comprobante(&mut self, val: &'static str) {
19 | self.tipo_comprobante = Some(val);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/server/ui/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.8.0/schema.json",
3 | "organizeImports": {
4 | "enabled": true
5 | },
6 | "vcs": {
7 | "enabled": true,
8 | "clientKind": "git",
9 | "useIgnoreFile": true
10 | },
11 | "formatter": {
12 | "indentStyle": "space"
13 | },
14 | "javascript": {
15 | "formatter": {
16 | "quoteStyle": "double"
17 | }
18 | },
19 | "css": {
20 | "parser": {
21 | "cssModules": true
22 | }
23 | },
24 | "linter": {
25 | "enabled": true,
26 | "rules": {
27 | "recommended": true
28 | }
29 | },
30 | "files": {
31 | "ignore": ["**/routeTree.gen.ts"]
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/total_impuestos.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detalle;
4 |
5 | pub trait DetalleTotalImpuestosGetter {
6 | fn get_total_impuestos(&self) -> &Option;
7 | }
8 |
9 | pub trait DetalleTotalImpuestosSetter {
10 | fn set_total_impuestos(&mut self, val: Decimal);
11 | }
12 |
13 | impl DetalleTotalImpuestosGetter for Detalle {
14 | fn get_total_impuestos(&self) -> &Option {
15 | &self.total_impuestos
16 | }
17 | }
18 |
19 | impl DetalleTotalImpuestosSetter for Detalle {
20 | fn set_total_impuestos(&mut self, val: Decimal) {
21 | self.total_impuestos = Some(val);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/invoice/forma_de_pago.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::FormaDePago;
2 | use crate::models::invoice::Invoice;
3 |
4 | pub trait InvoiceFormaDePagoGetter {
5 | fn get_forma_de_pago(&self) -> &Option;
6 | }
7 |
8 | pub trait InvoiceFormaDePagoSetter {
9 | fn set_forma_de_pago(&mut self, val: FormaDePago);
10 | }
11 |
12 | impl InvoiceFormaDePagoGetter for Invoice {
13 | fn get_forma_de_pago(&self) -> &Option {
14 | &self.forma_de_pago
15 | }
16 | }
17 |
18 | impl InvoiceFormaDePagoSetter for Invoice {
19 | fn set_forma_de_pago(&mut self, val: FormaDePago) {
20 | self.forma_de_pago = Some(val);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/icb_tasa.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::enricher::bounds::icb::{IcbTasaGetter, IcbTasaSetter};
4 | use crate::enricher::Defaults;
5 |
6 | pub trait IcbTasaFillRule {
7 | fn fill(&mut self, defaults: &Defaults) -> Result;
8 | }
9 |
10 | impl IcbTasaFillRule for T
11 | where
12 | T: IcbTasaGetter + IcbTasaSetter,
13 | {
14 | fn fill(&mut self, defaults: &Defaults) -> Result {
15 | match &self.get_icb_tasa() {
16 | Some(..) => Ok(false),
17 | None => {
18 | self.set_icb_tasa(defaults.icb_tasa);
19 | Ok(true)
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/igv_tasa.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::enricher::bounds::igv::{IgvTasaGetter, IgvTasaSetter};
4 | use crate::enricher::Defaults;
5 |
6 | pub trait IgvTasaFillRule {
7 | fn fill(&mut self, defaults: &Defaults) -> Result;
8 | }
9 |
10 | impl IgvTasaFillRule for T
11 | where
12 | T: IgvTasaGetter + IgvTasaSetter,
13 | {
14 | fn fill(&mut self, defaults: &Defaults) -> Result {
15 | match &self.get_igv_tasa() {
16 | Some(..) => Ok(false),
17 | None => {
18 | self.set_igv_tasa(defaults.igv_tasa);
19 | Ok(true)
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/server/entity/src/document.rs:
--------------------------------------------------------------------------------
1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
2 |
3 | use sea_orm::entity::prelude::*;
4 |
5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
6 | #[sea_orm(table_name = "ubl_document")]
7 | pub struct Model {
8 | #[sea_orm(primary_key)]
9 | pub id: i32,
10 | pub file_id: String,
11 | pub supplier_id: String,
12 | pub identifier: String,
13 | pub r#type: String,
14 | pub voided_document_code: Option,
15 | pub digest_value: Option,
16 | pub sha256: String,
17 | }
18 |
19 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
20 | pub enum Relation {}
21 |
22 | impl ActiveModelBehavior for ActiveModel {}
23 |
--------------------------------------------------------------------------------
/xsender/resources/test/bill_service_response_fault.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | soap-env:Client.0111
7 | No tiene el perfil para enviar comprobantes electronicos - Detalle: Rejected by policy.
8 |
9 |
10 |
--------------------------------------------------------------------------------
/server/ui/client/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { createRoot } from "react-dom/client";
2 |
3 | import { RouterProvider, createRouter } from "@tanstack/react-router";
4 | import { routeTree } from "./routeTree.gen";
5 |
6 | // Set up a Router instance
7 | const router = createRouter({
8 | routeTree,
9 | defaultPreload: "intent",
10 | });
11 |
12 | // Register things for typesafety
13 | declare module "@tanstack/react-router" {
14 | interface Register {
15 | router: typeof router;
16 | }
17 | }
18 |
19 | const rootElement = document.getElementById("root");
20 |
21 | if (rootElement && !rootElement.innerHTML) {
22 | const root = createRoot(rootElement);
23 | root.render();
24 | }
25 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/precio_referencia.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detalle;
4 |
5 | pub trait DetallePrecioReferenciaGetter {
6 | fn get_precio_referencia(&self) -> &Option;
7 | }
8 |
9 | pub trait DetallePrecioReferenciaSetter {
10 | fn set_precio_referencia(&mut self, val: Decimal);
11 | }
12 |
13 | impl DetallePrecioReferenciaGetter for Detalle {
14 | fn get_precio_referencia(&self) -> &Option {
15 | &self.precio_referencia
16 | }
17 | }
18 |
19 | impl DetallePrecioReferenciaSetter for Detalle {
20 | fn set_precio_referencia(&mut self, val: Decimal) {
21 | self.precio_referencia = Some(val);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/ivap_tasa.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use Ok;
3 |
4 | use crate::enricher::bounds::ivap::{IvapTasaGetter, IvapTasaSetter};
5 | use crate::enricher::Defaults;
6 |
7 | pub trait IvapTasaFillRule {
8 | fn fill(&mut self, defaults: &Defaults) -> Result;
9 | }
10 |
11 | impl IvapTasaFillRule for T
12 | where
13 | T: IvapTasaGetter + IvapTasaSetter,
14 | {
15 | fn fill(&mut self, defaults: &Defaults) -> Result {
16 | match &self.get_ivap_tasa() {
17 | Some(..) => Ok(false),
18 | None => {
19 | self.set_ivap_tasa(defaults.ivap_tasa);
20 | Ok(true)
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/igv_base_imponible.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detalle;
4 |
5 | pub trait DetalleIgvBaseImponibleGetter {
6 | fn get_igv_base_imponible(&self) -> &Option;
7 | }
8 |
9 | pub trait DetalleIGVBaseImponibleSetter {
10 | fn set_igv_base_imponible(&mut self, val: Decimal);
11 | }
12 |
13 | impl DetalleIgvBaseImponibleGetter for Detalle {
14 | fn get_igv_base_imponible(&self) -> &Option {
15 | &self.igv_base_imponible
16 | }
17 | }
18 |
19 | impl DetalleIGVBaseImponibleSetter for Detalle {
20 | fn set_igv_base_imponible(&mut self, val: Decimal) {
21 | self.igv_base_imponible = Some(val);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/isc_base_imponible.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detalle;
4 |
5 | pub trait DetalleIscBaseImponibleGetter {
6 | fn get_isc_base_imponible(&self) -> &Option;
7 | }
8 |
9 | pub trait DetalleISCBaseImponibleSetter {
10 | fn set_isc_base_imponible(&mut self, val: Decimal);
11 | }
12 |
13 | impl DetalleIscBaseImponibleGetter for Detalle {
14 | fn get_isc_base_imponible(&self) -> &Option {
15 | &self.isc_base_imponible
16 | }
17 | }
18 |
19 | impl DetalleISCBaseImponibleSetter for Detalle {
20 | fn set_isc_base_imponible(&mut self, val: Decimal) {
21 | self.isc_base_imponible = Some(val);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/common/signature.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{firmante.ruc}}
4 |
5 |
6 | {{firmante.ruc}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | #PROJECT-OPENUBL-SIGN
15 |
16 |
17 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/invoice/total_importe.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::TotalImporteInvoice;
2 | use crate::models::invoice::Invoice;
3 |
4 | pub trait InvoiceTotalImporteGetter {
5 | fn get_total_importe(&self) -> &Option;
6 | }
7 |
8 | pub trait InvoiceTotalImporteSetter {
9 | fn set_total_importe(&mut self, val: TotalImporteInvoice);
10 | }
11 |
12 | impl InvoiceTotalImporteGetter for Invoice {
13 | fn get_total_importe(&self) -> &Option {
14 | &self.total_importe
15 | }
16 | }
17 |
18 | impl InvoiceTotalImporteSetter for Invoice {
19 | fn set_total_importe(&mut self, val: TotalImporteInvoice) {
20 | self.total_importe = Some(val);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/server/entity/src/keystore.rs:
--------------------------------------------------------------------------------
1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
2 |
3 | use sea_orm::entity::prelude::*;
4 |
5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
6 | #[sea_orm(table_name = "keystore")]
7 | pub struct Model {
8 | #[sea_orm(primary_key)]
9 | pub id: i32,
10 | pub name: String,
11 | }
12 |
13 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
14 | pub enum Relation {
15 | #[sea_orm(has_many = "super::keystore_config::Entity")]
16 | KeystoreConfig,
17 | }
18 |
19 | impl Related for Entity {
20 | fn to() -> RelationDef {
21 | Relation::KeystoreConfig.def()
22 | }
23 | }
24 |
25 | impl ActiveModelBehavior for ActiveModel {}
26 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/fecha_emision.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::enricher::bounds::fecha_emision::{FechaEmisionGetter, FechaEmisionSetter};
4 | use crate::enricher::Defaults;
5 |
6 | pub trait FechaEmisionFillRule {
7 | fn fill(&mut self, defaults: &Defaults) -> Result;
8 | }
9 |
10 | impl FechaEmisionFillRule for T
11 | where
12 | T: FechaEmisionGetter + FechaEmisionSetter,
13 | {
14 | fn fill(&mut self, defaults: &Defaults) -> Result {
15 | match &self.get_fecha_emision() {
16 | Some(..) => Ok(false),
17 | None => {
18 | self.set_fecha_emision(defaults.date);
19 | Ok(true)
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/precio_con_impuestos.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detalle;
4 |
5 | pub trait DetallePrecioConImpuestosGetter {
6 | fn get_precio_con_impuestos(&self) -> &Option;
7 | }
8 |
9 | pub trait DetallePrecioConImpuestosSetter {
10 | fn set_precio_con_impuestos(&mut self, val: Decimal);
11 | }
12 |
13 | impl DetallePrecioConImpuestosGetter for Detalle {
14 | fn get_precio_con_impuestos(&self) -> &Option {
15 | &self.precio_con_impuestos
16 | }
17 | }
18 |
19 | impl DetallePrecioConImpuestosSetter for Detalle {
20 | fn set_precio_con_impuestos(&mut self, val: Decimal) {
21 | self.precio_con_impuestos = Some(val);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/precio_referencia_tipo.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::Detalle;
2 |
3 | pub trait DetallePrecioReferenciaTipoGetter {
4 | fn get_precio_referencia_tipo(&self) -> &Option<&'static str>;
5 | }
6 |
7 | pub trait DetallePrecioReferenciaTipoSetter {
8 | fn set_precio_referencia_tipo(&mut self, val: &'static str);
9 | }
10 |
11 | impl DetallePrecioReferenciaTipoGetter for Detalle {
12 | fn get_precio_referencia_tipo(&self) -> &Option<&'static str> {
13 | &self.precio_referencia_tipo
14 | }
15 | }
16 |
17 | impl DetallePrecioReferenciaTipoSetter for Detalle {
18 | fn set_precio_referencia_tipo(&mut self, val: &'static str) {
19 | self.precio_referencia_tipo = Some(val);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/server/ui/client/src/app/assets/pfbg-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/ui/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@openubl-ui/server",
3 | "version": "0.1.0",
4 | "license": "Apache-2.0",
5 | "private": true,
6 | "type": "module",
7 | "scripts": {
8 | "clean": "rimraf ./dist",
9 | "clean:all": "rimraf ./dist ./node_modules",
10 | "check": "biome check .",
11 | "check:write": "biome check --write .",
12 | "prebuild": "npm run clean",
13 | "build": "NODE_ENV=production rollup -c",
14 | "dev": "NODE_ENV=development ROLLUP_RUN=true rollup -c -w",
15 | "start": "npm run build && node --enable-source-maps dist/index.js"
16 | },
17 | "dependencies": {
18 | "cookie-parser": "^1.4.6",
19 | "ejs": "^3.1.10",
20 | "express": "^4.19.2",
21 | "http-terminator": "^3.2.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/note/debitnote/tipo_nota.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::catalogs::{Catalog, Catalog10};
4 | use crate::enricher::bounds::note::debitnote::tipo_nota::{
5 | DebitNoteTipoGetter, DebitNoteTipoSetter,
6 | };
7 |
8 | pub trait DebitNoteTipoFillRule {
9 | fn fill(&mut self) -> Result;
10 | }
11 |
12 | impl DebitNoteTipoFillRule for T
13 | where
14 | T: DebitNoteTipoGetter + DebitNoteTipoSetter,
15 | {
16 | fn fill(&mut self) -> Result {
17 | match &self.get_tipo_nota() {
18 | Some(..) => Ok(false),
19 | None => {
20 | self.set_tipo_nota(Catalog10::AumentoEnElValor.code());
21 | Ok(true)
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/server/ui/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["DOM", "ES2020"],
4 | "jsx": "react-jsx",
5 | "target": "ES2020",
6 | "noEmit": true,
7 | "skipLibCheck": true,
8 | "useDefineForClassFields": true,
9 |
10 | /* modules */
11 | "module": "ESNext",
12 | "isolatedModules": true,
13 | "resolveJsonModule": true,
14 | "moduleResolution": "Bundler",
15 | "allowImportingTsExtensions": true,
16 |
17 | /* type checking */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 |
22 | "paths": {
23 | "@client/*": ["./src/client/*"],
24 | "@queries/*": ["./src/queries/*"],
25 | "@app/*": ["./src/app/*"]
26 | }
27 | },
28 | "include": ["src"]
29 | }
30 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/note/creditnote/tipo_nota.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::catalogs::{Catalog, Catalog9};
4 | use crate::enricher::bounds::note::creditnote::tipo_nota::{
5 | CreditNoteTipoGetter, CreditNoteTipoSetter,
6 | };
7 |
8 | pub trait CreditNoteTipoFillRule {
9 | fn fill(&mut self) -> Result;
10 | }
11 |
12 | impl CreditNoteTipoFillRule for T
13 | where
14 | T: CreditNoteTipoGetter + CreditNoteTipoSetter,
15 | {
16 | fn fill(&mut self) -> Result {
17 | match &self.get_tipo_nota() {
18 | Some(..) => Ok(false),
19 | None => {
20 | self.set_tipo_nota(Catalog9::AnulacionDeLaOperacion.code());
21 | Ok(true)
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 |
4 | # Maintain dependencies for GitHub Actions
5 | - package-ecosystem: "github-actions"
6 | directory: "/"
7 | schedule:
8 | interval: "weekly"
9 | commit-message:
10 | prefix: ":ghost: "
11 |
12 | # Maintain dependencies for Cargo.toml
13 | - package-ecosystem: "cargo"
14 | directory: "/"
15 | schedule:
16 | interval: "weekly"
17 | commit-message:
18 | prefix: ":ghost: "
19 |
20 | # Maintain dependencies for NPM
21 | - package-ecosystem: "npm"
22 | directory: "/server/ui"
23 | schedule:
24 | interval: "weekly"
25 | commit-message:
26 | prefix: ":ghost: "
27 | allow:
28 | - dependency-name: "@patternfly/*"
29 | dependency-type: "direct"
30 |
--------------------------------------------------------------------------------
/server/ui/common/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./environment.js";
2 | export * from "./proxies.js";
3 | export * from "./branding.js";
4 |
5 | /**
6 | * Return a base64 encoded JSON string containing the given `env` object.
7 | */
8 | export const encodeEnv = (env: object, exclude?: string[]): string => {
9 | const filtered = exclude
10 | ? Object.fromEntries(
11 | Object.entries(env).filter(([key]) => !exclude.includes(key)),
12 | )
13 | : env;
14 |
15 | return btoa(JSON.stringify(filtered));
16 | };
17 |
18 | /**
19 | * Return an objects from a base64 encoded JSON string.
20 | */
21 | export const decodeEnv = (env: string): object =>
22 | !env ? {} : JSON.parse(atob(env));
23 |
24 | // TODO: Include `index.html.ejs` to `index.html` template file processing...
25 |
--------------------------------------------------------------------------------
/server/ui/server/rollup.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 |
3 | import commonjs from "@rollup/plugin-commonjs";
4 | import json from "@rollup/plugin-json";
5 | import nodeResolve from "@rollup/plugin-node-resolve";
6 | import run from "@rollup/plugin-run";
7 |
8 | const buildAndRun = process.env?.ROLLUP_RUN === "true";
9 |
10 | export default {
11 | input: "src/index.ts",
12 | output: {
13 | file: "dist/index.js",
14 | format: "esm",
15 | sourcemap: true,
16 | },
17 | watch: {
18 | clearScreen: false,
19 | },
20 |
21 | plugins: [
22 | nodeResolve({
23 | preferBuiltins: true,
24 | }),
25 | commonjs(),
26 | json(),
27 | buildAndRun &&
28 | run({
29 | execArgv: ["-r", "source-map-support/register"],
30 | }),
31 | ],
32 | };
33 |
--------------------------------------------------------------------------------
/server/api/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "openubl-api"
3 | version.workspace = true
4 | edition.workspace = true
5 | license.workspace = true
6 |
7 | [dependencies]
8 | openubl-entity = { workspace = true }
9 | openubl-common = { workspace = true }
10 | openubl-migration = { workspace = true }
11 | openubl-storage = { workspace = true }
12 |
13 | xhandler = { workspace = true }
14 |
15 | sea-orm = { workspace = true, features = [
16 | "sea-query-binder",
17 | "sqlx-sqlite",
18 | "sqlx-postgres",
19 | "runtime-tokio-rustls",
20 | "macros",
21 | ] }
22 | sea-query = { workspace = true }
23 | async-trait = { workspace = true }
24 | anyhow = { workspace = true }
25 | thiserror = { workspace = true }
26 | serde_json = { workspace = true }
27 | serde = { workspace = true, features = ["derive"] }
28 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/standard/include/payment-terms.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | FormaPago
4 | {{forma_de_pago.tipo}}
5 | {%- if forma_de_pago.tipo is credito %}
6 | {{forma_de_pago.total | round_decimal}}
7 | {%- endif %}
8 |
9 | {%- for it in forma_de_pago.cuotas %}
10 |
11 | FormaPago
12 | Cuota{{loop.index | format03d}}
13 | {{it.importe | round_decimal}}
14 | {{it.fecha_pago}}
15 |
16 | {%- endfor %}
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/detalle/icb_tasa.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::enricher::bounds::detalle::icb_tasa::{DetalleIcbTasaGetter, DetalleIcbTasaSetter};
4 | use crate::enricher::rules::phase1fill::detalle::detalles::DetalleDefaults;
5 |
6 | pub trait DetalleICBTasaFillRule {
7 | fn fill(&mut self, defaults: &DetalleDefaults) -> Result;
8 | }
9 |
10 | impl DetalleICBTasaFillRule for T
11 | where
12 | T: DetalleIcbTasaGetter + DetalleIcbTasaSetter,
13 | {
14 | fn fill(&mut self, defaults: &DetalleDefaults) -> Result {
15 | match &self.get_icb_tasa() {
16 | Some(..) => Ok(false),
17 | None => {
18 | self.set_icb_tasa(defaults.icb_tasa);
19 | Ok(true)
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xbuilder/tests/invoice_moneda.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 | use rust_decimal_macros::dec;
3 |
4 | use xbuilder::prelude::*;
5 |
6 | use crate::common::assert_invoice;
7 | use crate::common::invoice_base;
8 |
9 | mod common;
10 |
11 | const BASE: &str = "tests/resources/e2e/renderer/invoice/InvoiceMonedaTest";
12 |
13 | #[serial_test::serial]
14 | #[tokio::test]
15 | async fn invoice_custom_moneda() {
16 | let mut invoice = Invoice {
17 | moneda: Some("USD"),
18 | detalles: vec![Detalle {
19 | descripcion: "Item1",
20 | cantidad: Decimal::ONE,
21 | precio: Some(dec!(100)),
22 | ..Default::default()
23 | }],
24 | ..invoice_base()
25 | };
26 |
27 | assert_invoice(&mut invoice, &format!("{BASE}/customMoneda.xml")).await;
28 | }
29 |
--------------------------------------------------------------------------------
/xsender/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "xsender"
3 | description = "Sends XML files through SOAP - SUNAT"
4 | version.workspace = true
5 | edition.workspace = true
6 | license.workspace = true
7 |
8 | [dependencies]
9 | xml = { workspace = true }
10 | zip = { workspace = true }
11 | tera = { workspace = true }
12 | static-files = { workspace = true }
13 | lazy_static = { workspace = true }
14 | serde = { workspace = true, features = ["derive"] }
15 | reqwest = { workspace = true }
16 | regex = { workspace = true }
17 | base64 = { workspace = true }
18 | thiserror = { workspace = true }
19 | anyhow = { workspace = true }
20 | sha2 = { workspace = true }
21 |
22 | [dev-dependencies]
23 | serial_test = { workspace = true }
24 | tokio = { workspace = true, features = ["macros"] }
25 |
26 | [build-dependencies]
27 | static-files = { workspace = true }
28 |
--------------------------------------------------------------------------------
/server/migration/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "openubl-migration"
3 | version.workspace = true
4 | edition.workspace = true
5 | license.workspace = true
6 | publish = false
7 |
8 | [lib]
9 | name = "migration"
10 | path = "src/lib.rs"
11 |
12 | [dependencies]
13 | async-std = { workspace = true, features = ["attributes", "tokio1"] }
14 |
15 | [dependencies.sea-orm-migration]
16 | version = "1.0.0"
17 | features = [
18 | # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI.
19 | # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime.
20 | # e.g.
21 | "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature
22 | "sqlx-sqlite", # `DATABASE_DRIVER` feature
23 | "sqlx-postgres", # `DATABASE_DRIVER` feature
24 | ]
25 |
--------------------------------------------------------------------------------
/server/ui/client/src/app/components/NotificationsContext.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import type { AlertProps } from "@patternfly/react-core";
4 |
5 | export interface INotification {
6 | title: string;
7 | variant: AlertProps["variant"];
8 | message?: React.ReactNode;
9 | hideCloseButton?: boolean;
10 | timeout?: number | boolean;
11 | }
12 |
13 | export interface INotificationsProvider {
14 | children: React.ReactNode;
15 | }
16 |
17 | interface INotificationsContext {
18 | pushNotification: (notification: INotification) => void;
19 | dismissNotification: (key: string) => void;
20 | notifications: INotification[];
21 | }
22 |
23 | const appContextDefaultValue = {} as INotificationsContext;
24 |
25 | export const NotificationsContext = React.createContext(
26 | appContextDefaultValue,
27 | );
28 |
--------------------------------------------------------------------------------
/server/storage/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "openubl-storage"
3 | version.workspace = true
4 | edition.workspace = true
5 | license.workspace = true
6 |
7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8 |
9 | [dependencies]
10 | clap = { workspace = true, features = ["derive", "env"] }
11 | minio = { workspace = true }
12 | serde = { workspace = true, features = ["derive"] }
13 | anyhow = { workspace = true }
14 | uuid = { workspace = true, features = ["v4"] }
15 | thiserror = { workspace = true }
16 | zip = { workspace = true }
17 | tempfile = { workspace = true }
18 | reqwest = { workspace = true }
19 | aws-sdk-s3 = { workspace = true }
20 | tokio = { workspace = true }
21 | aws-config = { workspace = true }
22 | aws-smithy-runtime = { workspace = true }
23 | aws-smithy-runtime-api = { workspace = true }
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/detalle/unidad_medida.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::enricher::bounds::detalle::unidad_medida::{
4 | DetalleUnidadMedidaGetter, DetalleUnidadMedidaSetter,
5 | };
6 | use crate::enricher::rules::phase1fill::detalle::detalles::DetalleDefaults;
7 |
8 | pub trait DetalleUnidadMedidaFillRule {
9 | fn fill(&mut self, defaults: &DetalleDefaults) -> Result;
10 | }
11 |
12 | impl DetalleUnidadMedidaFillRule for T
13 | where
14 | T: DetalleUnidadMedidaGetter + DetalleUnidadMedidaSetter,
15 | {
16 | fn fill(&mut self, _: &DetalleDefaults) -> Result {
17 | match &self.get_unidad_medida() {
18 | Some(..) => Ok(false),
19 | None => {
20 | self.set_unidad_medida("NIU");
21 | Ok(true)
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/detalle/isc_tipo.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::catalogs::{Catalog, Catalog8};
4 | use crate::enricher::bounds::detalle::isc_tipo::{DetalleIscTipoGetter, DetalleIscTipoSetter};
5 | use crate::enricher::rules::phase1fill::detalle::detalles::DetalleDefaults;
6 |
7 | pub trait DetalleISCTipoFillRule {
8 | fn fill(&mut self, defaults: &DetalleDefaults) -> Result;
9 | }
10 |
11 | impl DetalleISCTipoFillRule for T
12 | where
13 | T: DetalleIscTipoGetter + DetalleIscTipoSetter,
14 | {
15 | fn fill(&mut self, _: &DetalleDefaults) -> Result {
16 | match &self.get_isc_tipo() {
17 | Some(..) => Ok(false),
18 | None => {
19 | self.set_isc_tipo(Catalog8::SistemaAlValor.code());
20 | Ok(true)
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/detalle/igv_tipo.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::catalogs::{Catalog, Catalog7};
4 | use crate::enricher::bounds::detalle::igv_tipo::{DetalleIgvTipoGetter, DetalleIgvTipoSetter};
5 | use crate::enricher::rules::phase1fill::detalle::detalles::DetalleDefaults;
6 |
7 | pub trait DetalleIGVTipoFillRule {
8 | fn fill(&mut self, defaults: &DetalleDefaults) -> Result;
9 | }
10 |
11 | impl DetalleIGVTipoFillRule for T
12 | where
13 | T: DetalleIgvTipoGetter + DetalleIgvTipoSetter,
14 | {
15 | fn fill(&mut self, _: &DetalleDefaults) -> Result {
16 | match &self.get_igv_tipo() {
17 | Some(..) => Ok(false),
18 | None => {
19 | self.set_igv_tipo(Catalog7::GravadoOperacionOnerosa.code());
20 | Ok(true)
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/server/ui/common/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@openubl-ui/common",
3 | "description": "ESM module for code common to client and server",
4 | "version": "0.1.0",
5 | "license": "Apache-2.0",
6 | "private": true,
7 | "type": "module",
8 | "exports": {
9 | ".": {
10 | "types": "./dist/index.d.ts",
11 | "import": "./dist/index.mjs",
12 | "require": "./dist/index.cjs"
13 | },
14 | "./package.json": "./package.json"
15 | },
16 | "types": "./dist",
17 | "main": "./dist/index.cjs",
18 | "module": "./dist/index.mjs",
19 | "scripts": {
20 | "clean": "rimraf ./dist",
21 | "clean:all": "rimraf ./dist ./node_modules",
22 | "check": "biome check .",
23 | "check:write": "biome check --write .",
24 | "prebuild": "npm run clean",
25 | "build": "NODE_ENV=production rollup -c",
26 | "dev": "NODE_ENV=development rollup -c --watch"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/xsender/resources/templates/verify_ticket.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{username}}
6 | {{password}}
7 |
8 |
9 |
10 |
11 |
12 | {{ticket}}
13 |
14 |
15 |
--------------------------------------------------------------------------------
/server/ui/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= branding.application.title %>
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.apache.org/licenses/LICENSE-2.0)
2 | 
3 |
4 | [](https://projectopenubl.zulipchat.com/)
5 |
6 | ## Libraries
7 |
8 | XMLs basados en UBL y SUNAT
9 |
10 | - [x] Crear
11 | - [x] Firmar
12 | - [x] Enviar
13 |
14 | ## Server
15 |
16 | ```shell
17 | cargo run --bin server
18 | ```
19 |
20 | - The server is running at http://localhost:8080
21 | - You can see Swagger UI at http://localhost:8080/swagger-ui
22 |
23 | ## Server UI
24 |
25 | ```shell
26 | npm run dev --prefix server/ui
27 | ```
28 |
29 | - The UI running at http://localhost:3000
30 |
31 | ## License
32 |
33 | - [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
34 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | *.versionsBackup
2 |
3 | # Intellij
4 | ###################
5 | .idea
6 | *.iml
7 |
8 | # Eclipse #
9 | ###########
10 | .project
11 | .settings
12 | .classpath
13 |
14 | # NetBeans #
15 | ############
16 | nbactions.xml
17 | nb-configuration.xml
18 | catalog.xml
19 | nbproject
20 |
21 | # Compiled source #
22 | ###################
23 | *.com
24 | *.class
25 | *.dll
26 | *.exe
27 | *.o
28 | *.so
29 |
30 | # Packages #
31 | ############
32 | # it's better to unpack these files and commit the raw source
33 | # git has its own built in compression methods
34 | *.7z
35 | *.dmg
36 | *.gz
37 | *.iso
38 | *.jar
39 | *.rar
40 | *.tar
41 | *.zip
42 |
43 | # Logs and databases #
44 | ######################
45 | *.log
46 |
47 | # Maven #
48 | #########
49 | target
50 |
51 | # Maven shade
52 | #############
53 | *dependency-reduced-pom.xml
54 |
55 | /lsp/
56 |
57 |
58 | # Added by cargo
59 |
60 | /target
61 |
62 | .openubl/
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/firmante.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::enricher::bounds::firmante::{FirmanteGetter, FirmanteSetter};
4 | use crate::enricher::bounds::proveedor::ProveedorGetter;
5 | use crate::models::common::Firmante;
6 |
7 | pub trait FirmanteFillRule {
8 | fn fill(&mut self) -> Result;
9 | }
10 |
11 | impl FirmanteFillRule for T
12 | where
13 | T: FirmanteGetter + FirmanteSetter + ProveedorGetter,
14 | {
15 | fn fill(&mut self) -> Result {
16 | match &self.get_firmante() {
17 | Some(..) => Ok(false),
18 | None => {
19 | let firmante = Firmante {
20 | ruc: self.get_proveedor().ruc,
21 | razon_social: self.get_proveedor().razon_social,
22 | };
23 | self.set_firmante(firmante);
24 | Ok(true)
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.versionsBackup
2 |
3 | # Intellij
4 | ###################
5 | .idea
6 | *.iml
7 |
8 | # Eclipse #
9 | ###########
10 | .project
11 | .settings
12 | .classpath
13 |
14 | # NetBeans #
15 | ############
16 | nbactions.xml
17 | nb-configuration.xml
18 | catalog.xml
19 | nbproject
20 |
21 | # Compiled source #
22 | ###################
23 | *.com
24 | *.class
25 | *.dll
26 | *.exe
27 | *.o
28 | *.so
29 |
30 | # Packages #
31 | ############
32 | # it's better to unpack these files and commit the raw source
33 | # git has its own built in compression methods
34 | *.7z
35 | *.dmg
36 | *.gz
37 | *.iso
38 | *.jar
39 | *.rar
40 | *.tar
41 | *.zip
42 |
43 | # Logs and databases #
44 | ######################
45 | *.log
46 |
47 | # Maven #
48 | #########
49 | target
50 |
51 | # Maven shade
52 | #############
53 | *dependency-reduced-pom.xml
54 |
55 | /lsp/
56 |
57 |
58 | # Added by cargo
59 |
60 | /target
61 |
62 | .openubl/
63 | .pnpm-store/
64 |
--------------------------------------------------------------------------------
/xbuilder/tests/invoice_issue30.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal_macros::dec;
2 |
3 | use xbuilder::prelude::*;
4 |
5 | use crate::common::assert_invoice;
6 | use crate::common::{invoice_base, proveedor_base};
7 |
8 | mod common;
9 |
10 | const BASE: &str = "tests/resources/e2e/renderer/invoice/InvoiceIssue30Test";
11 |
12 | #[serial_test::serial]
13 | #[tokio::test]
14 | async fn invoice_issue30() {
15 | let mut invoice = Invoice {
16 | proveedor: Proveedor {
17 | ruc: "12345678912",
18 | razon_social: "Project OpenUBL S.A.C.",
19 | ..proveedor_base()
20 | },
21 | detalles: vec![Detalle {
22 | descripcion: "Item1",
23 | cantidad: dec!(10),
24 | precio: Some(dec!(6.68)),
25 | ..Default::default()
26 | }],
27 | ..invoice_base()
28 | };
29 |
30 | assert_invoice(&mut invoice, &format!("{BASE}/with-precioUnitario.xml")).await;
31 | }
32 |
--------------------------------------------------------------------------------
/server/entity/src/send_rule.rs:
--------------------------------------------------------------------------------
1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
2 |
3 | use sea_orm::entity::prelude::*;
4 |
5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
6 | #[sea_orm(table_name = "send_rule")]
7 | pub struct Model {
8 | #[sea_orm(primary_key)]
9 | pub id: i32,
10 | pub supplier_id: String,
11 | pub credentials_id: i32,
12 | }
13 |
14 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
15 | pub enum Relation {
16 | #[sea_orm(
17 | belongs_to = "super::credentials::Entity",
18 | from = "Column::CredentialsId",
19 | to = "super::credentials::Column::Id",
20 | on_update = "NoAction",
21 | on_delete = "Cascade"
22 | )]
23 | Credentials,
24 | }
25 |
26 | impl Related for Entity {
27 | fn to() -> RelationDef {
28 | Relation::Credentials.def()
29 | }
30 | }
31 |
32 | impl ActiveModelBehavior for ActiveModel {}
33 |
--------------------------------------------------------------------------------
/xbuilder/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "xbuilder"
3 | description = "Creates XML files based on UBL under the standards of Peru."
4 | version.workspace = true
5 | edition.workspace = true
6 | license.workspace = true
7 |
8 | [dependencies]
9 | chrono = { workspace = true, features = ["serde"] }
10 | regex = { workspace = true }
11 | log = { workspace = true }
12 | tera = { workspace = true }
13 | static-files = { workspace = true }
14 | lazy_static = { workspace = true }
15 | serde = { workspace = true, features = ["derive"] }
16 | rust_decimal = { workspace = true }
17 | rust_decimal_macros = { workspace = true }
18 | anyhow = { workspace = true }
19 |
20 | [dev-dependencies]
21 | libxml = { workspace = true }
22 |
23 | serial_test = { workspace = true }
24 | tokio = { workspace = true, features = ["macros"] }
25 |
26 | xsender = { path = "../xsender" }
27 | xsigner = { path = "../xsigner" }
28 |
29 | [build-dependencies]
30 | static-files = { workspace = true }
31 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/standard/include/namespaces.xml:
--------------------------------------------------------------------------------
1 |
2 | xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
3 | xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
4 | xmlns:ccts="urn:un:unece:uncefact:documentation:2"
5 | xmlns:cec="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"
6 | xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
7 | xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"
8 | xmlns:qdt="urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2"
9 | xmlns:sac="urn:sunat:names:specification:ubl:peru:schema:xsd:SunatAggregateComponents-1"
10 | xmlns:udt="urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2"
11 | xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
12 |
--------------------------------------------------------------------------------
/xsender/resources/templates/send_bill.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{username}}
6 | {{password}}
7 |
8 |
9 |
10 |
11 |
12 | {{body.filename}}
13 | {{body.file_content}}
14 |
15 |
16 |
--------------------------------------------------------------------------------
/server/entity/src/keystore_config.rs:
--------------------------------------------------------------------------------
1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
2 |
3 | use sea_orm::entity::prelude::*;
4 |
5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
6 | #[sea_orm(table_name = "keystore_config")]
7 | pub struct Model {
8 | #[sea_orm(primary_key)]
9 | pub id: i32,
10 | pub name: String,
11 | pub val: String,
12 | pub keystore_id: i32,
13 | }
14 |
15 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
16 | pub enum Relation {
17 | #[sea_orm(
18 | belongs_to = "super::keystore::Entity",
19 | from = "Column::KeystoreId",
20 | to = "super::keystore::Column::Id",
21 | on_update = "NoAction",
22 | on_delete = "Cascade"
23 | )]
24 | Keystore,
25 | }
26 |
27 | impl Related for Entity {
28 | fn to() -> RelationDef {
29 | Relation::Keystore.def()
30 | }
31 | }
32 |
33 | impl ActiveModelBehavior for ActiveModel {}
34 |
--------------------------------------------------------------------------------
/server/migration/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub use sea_orm_migration::prelude::*;
2 |
3 | mod m20240101_104121_create_document;
4 | mod m20240113_213636_create_keystore;
5 | mod m20240113_213657_create_keystore_config;
6 | mod m20240114_154538_create_credentials;
7 | mod m20240117_142858_create_send_rule;
8 | mod m20240717_214515_create_delivery;
9 | pub struct Migrator;
10 |
11 | #[async_trait::async_trait]
12 | impl MigratorTrait for Migrator {
13 | fn migrations() -> Vec> {
14 | vec![
15 | Box::new(m20240101_104121_create_document::Migration),
16 | Box::new(m20240113_213636_create_keystore::Migration),
17 | Box::new(m20240113_213657_create_keystore_config::Migration),
18 | Box::new(m20240114_154538_create_credentials::Migration),
19 | Box::new(m20240117_142858_create_send_rule::Migration),
20 | Box::new(m20240717_214515_create_delivery::Migration),
21 | ]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/server/ui/client/src/client/rest.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | import type { New } from "./helpers.ts";
4 | import type { Credentials } from "./models";
5 |
6 | const API = "/api";
7 |
8 | export const CREDENTIALS = `${API}/credentials`;
9 |
10 | export const getCredentials = () => {
11 | return axios
12 | .get(CREDENTIALS)
13 | .then((response) => response.data);
14 | };
15 |
16 | export const getCredentialsById = (id: number | string) => {
17 | return axios
18 | .get(`${CREDENTIALS}/${id}`)
19 | .then((response) => response.data);
20 | };
21 |
22 | export const createCredentials = (obj: New) =>
23 | axios.post(`${CREDENTIALS}/credentials`, obj);
24 |
25 | export const updateCredentials = (obj: Credentials) =>
26 | axios.put(`${CREDENTIALS}/${obj.id}`, obj);
27 |
28 | export const deleteCredentials = (id: number | string) =>
29 | axios.delete(`${CREDENTIALS}/${id}`);
30 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/standard/include/note/invoice-reference.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{comprobante_afectado_serie_numero}}
4 | {{comprobante_afectado_tipo}}
5 |
6 |
7 | {%- if orden_de_compra %}
8 |
9 | {{orden_de_compra}}
10 |
11 | {%- endif %}
12 |
13 |
14 | {{comprobante_afectado_serie_numero}}
15 | {{comprobante_afectado_tipo}}
16 |
17 |
--------------------------------------------------------------------------------
/xbuilder/tests/invoice_descuentos.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 | use rust_decimal_macros::dec;
3 |
4 | use xbuilder::prelude::*;
5 |
6 | use crate::common::assert_invoice;
7 | use crate::common::invoice_base;
8 |
9 | mod common;
10 |
11 | const BASE: &str = "tests/resources/e2e/renderer/invoice/InvoiceDescuentosTest";
12 |
13 | #[serial_test::serial]
14 | #[tokio::test]
15 | async fn invoice_anticipos() {
16 | let mut invoice = Invoice {
17 | detalles: vec![Detalle {
18 | descripcion: "Item1",
19 | cantidad: Decimal::ONE,
20 | precio: Some(dec!(100)),
21 | ..Default::default()
22 | }],
23 | descuentos: vec![Descuento {
24 | monto: dec!(50),
25 | tipo: None,
26 | monto_base: None,
27 | factor: None,
28 | }],
29 | ..invoice_base()
30 | };
31 |
32 | assert_invoice(&mut invoice, &format!("{BASE}/descuentoGlobal.xml")).await;
33 | }
34 |
--------------------------------------------------------------------------------
/xsender/resources/templates/send_summary.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{username}}
6 | {{password}}
7 |
8 |
9 |
10 |
11 |
12 | {{body.filename}}
13 | {{body.file_content}}
14 |
15 |
16 |
--------------------------------------------------------------------------------
/xbuilder/tests/invoice_percepcion.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal_macros::dec;
2 |
3 | use xbuilder::prelude::*;
4 |
5 | use crate::common::assert_invoice;
6 | use crate::common::invoice_base;
7 |
8 | mod common;
9 |
10 | const BASE: &str = "tests/resources/e2e/renderer/invoice/InvoicePercepcionTest";
11 |
12 | #[serial_test::serial]
13 | #[tokio::test]
14 | async fn invoice_percepcion() {
15 | let mut invoice = Invoice {
16 | percepcion: Some(Percepcion {
17 | tipo: "51",
18 | porcentaje: Some(dec!(0.02)),
19 | monto: None,
20 | monto_base: None,
21 | monto_total: None,
22 | }),
23 | detalles: vec![Detalle {
24 | descripcion: "Item1",
25 | cantidad: dec!(4),
26 | precio: Some(dec!(200)),
27 | ..Default::default()
28 | }],
29 | ..invoice_base()
30 | };
31 |
32 | assert_invoice(&mut invoice, &format!("{BASE}/percepcion.xml")).await;
33 | }
34 |
--------------------------------------------------------------------------------
/xsender/resources/templates/validate_file.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{username}}
6 | {{password}}
7 |
8 |
9 |
10 |
11 |
12 | {{body.filename}}
13 | {{body.file_content}}
14 |
15 |
16 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase2process/detalle/icb_aplica.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use rust_decimal::Decimal;
3 |
4 | use crate::enricher::bounds::detalle::icb::DetalleIcbGetter;
5 | use crate::enricher::bounds::detalle::icb_aplica::{
6 | DetalleICBAplicaGetter, DetalleIcbAplicaSetter,
7 | };
8 |
9 | pub trait DetalleICBAplicaProcessRule {
10 | fn process(&mut self) -> Result;
11 | }
12 |
13 | impl DetalleICBAplicaProcessRule for T
14 | where
15 | T: DetalleICBAplicaGetter + DetalleIcbAplicaSetter + DetalleIcbGetter,
16 | {
17 | fn process(&mut self) -> Result {
18 | match (&self.get_icb_aplica(), &self.get_icb()) {
19 | (false, Some(icb)) => {
20 | if icb > &Decimal::ZERO {
21 | self.set_icb_aplica(true);
22 | Ok(true)
23 | } else {
24 | Ok(false)
25 | }
26 | }
27 | _ => Ok(false),
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/server/entity/src/credentials.rs:
--------------------------------------------------------------------------------
1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
2 |
3 | use sea_orm::entity::prelude::*;
4 |
5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
6 | #[sea_orm(table_name = "credentials")]
7 | pub struct Model {
8 | #[sea_orm(primary_key)]
9 | pub id: i32,
10 | pub name: String,
11 | pub description: Option,
12 | pub username_sol: String,
13 | pub password_sol: String,
14 | pub client_id: String,
15 | pub client_secret: String,
16 | pub url_invoice: String,
17 | pub url_despatch: String,
18 | pub url_perception_retention: String,
19 | }
20 |
21 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
22 | pub enum Relation {
23 | #[sea_orm(has_many = "super::send_rule::Entity")]
24 | SendRule,
25 | }
26 |
27 | impl Related for Entity {
28 | fn to() -> RelationDef {
29 | Relation::SendRule.def()
30 | }
31 | }
32 |
33 | impl ActiveModelBehavior for ActiveModel {}
34 |
--------------------------------------------------------------------------------
/xbuilder/tests/debit_note.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal_macros::dec;
2 |
3 | use xbuilder::models::common::Detalle;
4 | use xbuilder::prelude::*;
5 |
6 | use crate::common::{assert_debit_note, debit_note_base};
7 |
8 | mod common;
9 |
10 | const BASE: &str = "tests/resources/e2e/renderer/debitnote/DebitNoteTest";
11 |
12 | #[serial_test::serial]
13 | #[tokio::test]
14 | async fn debit_note() {
15 | let mut debit_note = DebitNote {
16 | detalles: vec![
17 | Detalle {
18 | descripcion: "Item1",
19 | cantidad: dec!(10),
20 | precio: Some(dec!(100)),
21 | ..Default::default()
22 | },
23 | Detalle {
24 | descripcion: "Item2",
25 | cantidad: dec!(10),
26 | precio: Some(dec!(100)),
27 | ..Default::default()
28 | },
29 | ],
30 | ..debit_note_base()
31 | };
32 |
33 | assert_debit_note(&mut debit_note, &format!("{BASE}/MinData_RUC.xml")).await;
34 | }
35 |
--------------------------------------------------------------------------------
/xbuilder/tests/invoice_detraccion.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal_macros::dec;
2 |
3 | use xbuilder::prelude::*;
4 |
5 | use crate::common::assert_invoice;
6 | use crate::common::invoice_base;
7 |
8 | mod common;
9 |
10 | const BASE: &str = "tests/resources/e2e/renderer/invoice/InvoiceDetraccionTest";
11 |
12 | #[serial_test::serial]
13 | #[tokio::test]
14 | async fn invoice_detraccion() {
15 | let mut invoice = Invoice {
16 | detalles: vec![Detalle {
17 | descripcion: "Item1",
18 | cantidad: dec!(4),
19 | precio: Some(dec!(200)),
20 | ..Default::default()
21 | }],
22 | detraccion: Some(Detraccion {
23 | medio_de_pago: Catalog59::DepositoEnCuenta.code(),
24 | cuenta_bancaria: "0004-3342343243",
25 | tipo_bien_detraido: "014",
26 | porcentaje: dec!(0.04),
27 | monto: None,
28 | }),
29 | ..invoice_base()
30 | };
31 |
32 | assert_invoice(&mut invoice, &format!("{BASE}/detraccion.xml")).await;
33 | }
34 |
--------------------------------------------------------------------------------
/xbuilder/tests/credit_note.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal_macros::dec;
2 |
3 | use xbuilder::models::common::Detalle;
4 | use xbuilder::prelude::*;
5 |
6 | use crate::common::{assert_credit_note, credit_note_base};
7 |
8 | mod common;
9 |
10 | const BASE: &str = "tests/resources/e2e/renderer/creditnote/CreditNoteTest";
11 |
12 | #[serial_test::serial]
13 | #[tokio::test]
14 | async fn credit_note() {
15 | let mut credit_note = CreditNote {
16 | detalles: vec![
17 | Detalle {
18 | descripcion: "Item1",
19 | cantidad: dec!(10),
20 | precio: Some(dec!(100)),
21 | ..Default::default()
22 | },
23 | Detalle {
24 | descripcion: "Item2",
25 | cantidad: dec!(10),
26 | precio: Some(dec!(100)),
27 | ..Default::default()
28 | },
29 | ],
30 | ..credit_note_base()
31 | };
32 |
33 | assert_credit_note(&mut credit_note, &format!("{BASE}/MinData_RUC.xml")).await;
34 | }
35 |
--------------------------------------------------------------------------------
/server/ui/client/src/client/helpers.ts:
--------------------------------------------------------------------------------
1 | export type WithUiId = T & { _ui_unique_id: string };
2 |
3 | /** Mark an object as "New" therefore does not have an `id` field. */
4 | export type New = Omit;
5 |
6 | export interface HubFilter {
7 | field: string;
8 | operator?: "=" | "!=" | "~" | ">" | ">=" | "<" | "<=";
9 | value:
10 | | string
11 | | number
12 | | {
13 | list: (string | number)[];
14 | operator?: "AND" | "OR";
15 | };
16 | }
17 |
18 | export interface HubRequestParams {
19 | filters?: HubFilter[];
20 | sort?: {
21 | field: string;
22 | direction: "asc" | "desc";
23 | };
24 | page?: {
25 | pageNumber: number; // 1-indexed
26 | itemsPerPage: number;
27 | };
28 | }
29 |
30 | export interface HubPaginatedResult {
31 | data: T[];
32 | total: number;
33 | params: HubRequestParams;
34 | }
35 |
36 | // Common
37 |
38 | export type VulnerabilityStatus =
39 | | "fixed"
40 | | "not_affected"
41 | | "known_not_affected"
42 | | "affected";
43 |
--------------------------------------------------------------------------------
/xbuilder/tests/invoice_orden_compra.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal_macros::dec;
2 |
3 | use xbuilder::prelude::*;
4 |
5 | use crate::common::assert_invoice;
6 | use crate::common::invoice_base;
7 |
8 | mod common;
9 |
10 | const BASE: &str = "tests/resources/e2e/renderer/invoice/InvoiceOrdeDeCompraTest";
11 |
12 | #[serial_test::serial]
13 | #[tokio::test]
14 | async fn invoice_custom_moneda() {
15 | let mut invoice = Invoice {
16 | orden_de_compra: Some("123456"),
17 | detalles: vec![
18 | Detalle {
19 | descripcion: "Item1",
20 | cantidad: dec!(2),
21 | precio: Some(dec!(100)),
22 | ..Default::default()
23 | },
24 | Detalle {
25 | descripcion: "Item2",
26 | cantidad: dec!(2),
27 | precio: Some(dec!(100)),
28 | ..Default::default()
29 | },
30 | ],
31 | ..invoice_base()
32 | };
33 |
34 | assert_invoice(&mut invoice, &format!("{BASE}/ordenDeCompra.xml")).await;
35 | }
36 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase2process/detalle/igv.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::enricher::bounds::detalle::igv::{DetalleIgvGetter, DetalleIgvSetter};
4 | use crate::enricher::bounds::detalle::igv_base_imponible::DetalleIgvBaseImponibleGetter;
5 | use crate::enricher::bounds::detalle::igv_tasa::DetalleIgvTasaGetter;
6 |
7 | pub trait DetalleIGVProcessRule {
8 | fn process(&mut self) -> Result;
9 | }
10 |
11 | impl DetalleIGVProcessRule for T
12 | where
13 | T: DetalleIgvGetter + DetalleIgvSetter + DetalleIgvBaseImponibleGetter + DetalleIgvTasaGetter,
14 | {
15 | fn process(&mut self) -> Result {
16 | match (
17 | &self.get_igv(),
18 | &self.get_igv_base_imponible(),
19 | &self.get_igv_tasa(),
20 | ) {
21 | (None, Some(igv_base_imponible), Some(igv_tasa)) => {
22 | let igv = igv_base_imponible * *igv_tasa;
23 | self.set_igv(igv);
24 | Ok(true)
25 | }
26 | _ => Ok(false),
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM quay.io/fedora/fedora:41
2 |
3 | ARG USERNAME=vscode
4 | ARG USER_UID=1000
5 | ARG USER_GID=$USER_UID
6 |
7 | COPY entrypoint.sh /entrypoint.sh
8 |
9 | RUN dnf -y update && \
10 | dnf install -y @development-tools && \
11 | dnf install -y curl wget
12 |
13 | RUN groupadd --gid $USER_GID $USERNAME && \
14 | useradd --uid $USER_UID --gid $USER_GID -m $USERNAME && \
15 | echo $USERNAME:10000:5000 > /etc/subuid && echo $USERNAME:10000:5000 > /etc/subgid && \
16 | # Allow user to execute 'sudo' without password
17 | usermod -aG wheel $USERNAME && \
18 | echo "%wheel ALL=(ALL) NOPASSWD:ALL" | tee -a /etc/sudoers > /dev/null
19 |
20 | # set permissions
21 | RUN chown $USERNAME:$USERNAME -R /home/$USERNAME
22 |
23 | RUN usermod -aG wheel $USERNAME && \
24 | # Allow user to execute 'sudo' without password
25 | echo "%wheel ALL=(ALL) NOPASSWD:ALL" | tee -a /etc/sudoers > /dev/null
26 |
27 | ENV _CONTAINERS_USERNS_CONFIGURED=""
28 |
29 | ENTRYPOINT [ "/entrypoint.sh" ]
30 | USER $USERNAME
31 | CMD ["tail", "-f", "/dev/null"]
32 |
--------------------------------------------------------------------------------
/server/deploy/compose/scripts/keycloak/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -l
2 | KEYCLOAK_HOME="/opt/keycloak"
3 |
4 | while ! $KEYCLOAK_HOME/bin/kcadm.sh config credentials config --server "$KEYCLOAK_SERVER_URL" --realm master --user "$KEYCLOAK_ADMIN" --password "$KEYCLOAK_ADMIN_PASSWORD" &> /dev/null; do
5 | echo "Waiting for Keycloak to start up..."
6 | sleep 3
7 | done
8 |
9 | # Create realm
10 | $KEYCLOAK_HOME/bin/kcadm.sh create realms -s realm=openubl -s enabled=true -o
11 |
12 | # Create clients
13 | $KEYCLOAK_HOME/bin/kcadm.sh create clients -r openubl -f - << EOF
14 | {
15 | "clientId": "openubl-api",
16 | "secret": "secret"
17 | }
18 | EOF
19 |
20 | $KEYCLOAK_HOME/bin/kcadm.sh create clients -r openubl -f - << EOF
21 | {
22 | "clientId": "openubl-ui",
23 | "publicClient": true,
24 | "redirectUris": ["*"],
25 | "webOrigins": ["*"]
26 | }
27 | EOF
28 |
29 | # Create user
30 | $KEYCLOAK_HOME/bin/kcadm.sh create users -r=openubl -s username=carlos -s enabled=true
31 | $KEYCLOAK_HOME/bin/kcadm.sh set-password -r=openubl --username carlos --new-password carlos
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/note/total_importe.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::TotalImporteNote;
2 | use crate::models::credit_note::CreditNote;
3 | use crate::models::debit_note::DebitNote;
4 |
5 | pub trait NoteTotalImporteGetter {
6 | fn get_total_importe(&self) -> &Option;
7 | }
8 |
9 | pub trait NoteTotalImporteSetter {
10 | fn set_total_importe(&mut self, val: TotalImporteNote);
11 | }
12 |
13 | impl NoteTotalImporteGetter for CreditNote {
14 | fn get_total_importe(&self) -> &Option {
15 | &self.total_importe
16 | }
17 | }
18 |
19 | impl NoteTotalImporteGetter for DebitNote {
20 | fn get_total_importe(&self) -> &Option {
21 | &self.total_importe
22 | }
23 | }
24 |
25 | impl NoteTotalImporteSetter for CreditNote {
26 | fn set_total_importe(&mut self, val: TotalImporteNote) {
27 | self.total_importe = Some(val);
28 | }
29 | }
30 |
31 | impl NoteTotalImporteSetter for DebitNote {
32 | fn set_total_importe(&mut self, val: TotalImporteNote) {
33 | self.total_importe = Some(val);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/server/server/src/openapi.rs:
--------------------------------------------------------------------------------
1 | use actix_web::App;
2 | use utoipa::{
3 | openapi::{Info, License},
4 | OpenApi,
5 | };
6 | use utoipa_actix_web::AppExt;
7 |
8 | use crate::{configure_api, configure_q};
9 |
10 | #[derive(OpenApi)]
11 | #[openapi()]
12 | pub struct ApiDoc;
13 |
14 | pub fn default_openapi_info() -> Info {
15 | let mut info = Info::new("Openubl", env!("CARGO_PKG_VERSION"));
16 | info.description = Some("Enviar archivos XML a la SUNAT API".into());
17 | info.license = {
18 | let mut license = License::new("Apache License, Version 2.0");
19 | license.identifier = Some("Apache-2.0".into());
20 | Some(license)
21 | };
22 | info
23 | }
24 |
25 | pub async fn create_openapi() -> anyhow::Result {
26 | let (_, mut openapi) = App::new()
27 | .into_utoipa_app()
28 | .service(utoipa_actix_web::scope("/q").configure(configure_q))
29 | .service(utoipa_actix_web::scope("/api").configure(configure_api))
30 | .split_for_parts();
31 |
32 | openapi.info = default_openapi_info();
33 |
34 | Ok(openapi)
35 | }
36 |
--------------------------------------------------------------------------------
/xbuilder/tests/credit_note_orden_de_compra.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal_macros::dec;
2 |
3 | use xbuilder::models::common::Detalle;
4 | use xbuilder::prelude::*;
5 |
6 | use crate::common::{assert_credit_note, credit_note_base};
7 |
8 | mod common;
9 |
10 | const BASE: &str = "tests/resources/e2e/renderer/creditnote/CreditNoteOrdenDeCompraTest";
11 |
12 | #[serial_test::serial]
13 | #[tokio::test]
14 | async fn credit_note() {
15 | let mut credit_note = CreditNote {
16 | orden_de_compra: Some("123456"),
17 | detalles: vec![
18 | Detalle {
19 | descripcion: "Item1",
20 | cantidad: dec!(10),
21 | precio: Some(dec!(100)),
22 | ..Default::default()
23 | },
24 | Detalle {
25 | descripcion: "Item2",
26 | cantidad: dec!(10),
27 | precio: Some(dec!(100)),
28 | ..Default::default()
29 | },
30 | ],
31 | ..credit_note_base()
32 | };
33 |
34 | assert_credit_note(&mut credit_note, &format!("{BASE}/ordenDeCompra.xml")).await;
35 | }
36 |
--------------------------------------------------------------------------------
/entrypoint.ui.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | if [[ -z "$OPENUBL_API_URL" ]]; then
6 | echo "You must provide OPENUBL_API_URL environment variable" 1>&2
7 | exit 1
8 | fi
9 |
10 | if [[ -z "${NODE_EXTRA_CA_CERTS}" ]]; then
11 | # Nothing to do
12 | echo "No NODE_EXTRA_CA_CERTS found"
13 | else
14 | # Copy the Kube API and service CA bundle to /opt/app-root/src/ca.crt if they exist
15 |
16 | # Add Kube API CA
17 | if [ -f "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" ]; then
18 | cp /var/run/secrets/kubernetes.io/serviceaccount/ca.crt ${NODE_EXTRA_CA_CERTS}
19 | fi
20 |
21 | # Add service serving CA
22 | if [ -f "/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt" ]; then
23 | cat /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt >>${NODE_EXTRA_CA_CERTS}
24 | fi
25 |
26 | # Add custom ingress CA if it exists
27 | if [ -f "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" ]; then
28 | cat /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem >>${NODE_EXTRA_CA_CERTS}
29 | fi
30 | fi
31 |
32 | exec node --enable-source-maps server/dist/index.js
--------------------------------------------------------------------------------
/xbuilder/tests/invoice_fecha_vencimiento.rs:
--------------------------------------------------------------------------------
1 | use chrono::NaiveDate;
2 | use rust_decimal_macros::dec;
3 |
4 | use xbuilder::prelude::*;
5 |
6 | use crate::common::assert_invoice;
7 | use crate::common::invoice_base;
8 |
9 | mod common;
10 |
11 | const BASE: &str = "tests/resources/e2e/renderer/invoice/InvoiceFechaVencimientoTest";
12 |
13 | #[serial_test::serial]
14 | #[tokio::test]
15 | async fn invoice_custom_moneda() {
16 | let mut invoice = Invoice {
17 | fecha_vencimiento: NaiveDate::from_ymd_opt(2022, 1, 1),
18 | detalles: vec![
19 | Detalle {
20 | descripcion: "Item1",
21 | cantidad: dec!(10),
22 | precio: Some(dec!(100)),
23 | ..Default::default()
24 | },
25 | Detalle {
26 | descripcion: "Item2",
27 | cantidad: dec!(10),
28 | precio: Some(dec!(100)),
29 | ..Default::default()
30 | },
31 | ],
32 | ..invoice_base()
33 | };
34 |
35 | assert_invoice(&mut invoice, &format!("{BASE}/conFechaVencimiento.xml")).await;
36 | }
37 |
--------------------------------------------------------------------------------
/server/ui/client/src/app/layout/default-layout.tsx:
--------------------------------------------------------------------------------
1 | import type React from "react";
2 |
3 | import { Page, SkipToContent } from "@patternfly/react-core";
4 |
5 | import { Notifications } from "@app/components/Notifications";
6 | import { PageContentWithDrawerProvider } from "@app/components/PageDrawerContext";
7 |
8 | import { HeaderApp } from "./header";
9 | import { SidebarApp } from "./sidebar";
10 |
11 | interface DefaultLayoutProps {
12 | children?: React.ReactNode;
13 | }
14 |
15 | export const DefaultLayout: React.FC = ({ children }) => {
16 | const pageId = "main-content-page-layout-horizontal-nav";
17 | const PageSkipToContent = (
18 | Skip to content
19 | );
20 |
21 | return (
22 | }
24 | sidebar={}
25 | isManagedSidebar
26 | skipToContent={PageSkipToContent}
27 | mainContainerId={pageId}
28 | >
29 |
30 | {children}
31 |
32 |
33 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "xhandler-rust",
3 | "dockerComposeFile": "docker-compose.yml",
4 | "overrideCommand": true,
5 | "shutdownAction": "stopCompose",
6 | "service": "xhandler-rust",
7 | "remoteUser": "vscode",
8 | "workspaceFolder": "/workspace",
9 | "onCreateCommand": "bash .devcontainer/onCreateCommand.sh",
10 | "postCreateCommand": "bash .devcontainer/postCreateCommand.sh",
11 | "postStartCommand": "bash .devcontainer/postStartCommand.sh",
12 | "forwardPorts": [
13 | 8080,
14 | 5173
15 | ],
16 | "customizations": {
17 | "jetbrains": {
18 | "backend": "RustRover"
19 | },
20 | "vscode": {
21 | "extensions": [
22 | "k--kato.intellij-idea-keybindings",
23 | "vadimcn.vscode-lldb",
24 | "rust-lang.rust-analyzer",
25 | "tamasfe.even-better-toml",
26 | "dsznajder.es7-react-js-snippets",
27 | "biomejs.biome",
28 | "GitHub.vscode-github-actions"
29 | ],
30 | "settings": {
31 | "biome.searchInPath": false,
32 | "biome.lspBin": "server/ui/node_modules/@biomejs/cli-linux-x64/biome"
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/xsender/resources/templates/get_status_crd.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{username}}
6 | {{password}}
7 |
8 |
9 |
10 |
11 |
12 | {{body.ruc}}
13 | {{body.tipo_comprobante}}
14 | {{body.serie_comprobante}}
15 | {{body.numero_comprobante}}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/xbuilder/tests/invoice_guias.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal_macros::dec;
2 |
3 | use xbuilder::prelude::*;
4 |
5 | use crate::common::assert_invoice;
6 | use crate::common::invoice_base;
7 |
8 | mod common;
9 |
10 | const BASE: &str = "tests/resources/e2e/renderer/invoice/InvoiceGuiasTest";
11 |
12 | #[serial_test::serial]
13 | #[tokio::test]
14 | async fn invoice_guia_serie_t() {
15 | let mut invoice = Invoice {
16 | detalles: vec![
17 | Detalle {
18 | descripcion: "Item1",
19 | cantidad: dec!(2),
20 | precio: Some(dec!(100)),
21 | ..Default::default()
22 | },
23 | Detalle {
24 | descripcion: "Item2",
25 | cantidad: dec!(2),
26 | precio: Some(dec!(100)),
27 | ..Default::default()
28 | },
29 | ],
30 | guias: vec![Guia {
31 | tipo_documento: Catalog1::GuiaRemisionRemitente.code(),
32 | serie_numero: "T001-1",
33 | }],
34 | ..invoice_base()
35 | };
36 |
37 | assert_invoice(&mut invoice, &format!("{BASE}/guiaSerieT.xml")).await;
38 | }
39 |
--------------------------------------------------------------------------------
/server/ui/client/src/app/assets/avatar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/standard/include/customer.xml:
--------------------------------------------------------------------------------
1 | {%- import "ubl/standard/include/address.xml" as macros_address -%}
2 | {%- import "ubl/standard/include/contact.xml" as macros_contact -%}
3 |
4 |
5 |
6 |
7 | {{cliente.numero_documento_identidad}}
8 |
9 |
10 |
11 | {%- if cliente.direccion %}
12 | {{ macros_address::address(direccion=cliente.direccion) }}
13 |
14 | {%- endif %}
15 |
16 | {%- if cliente.contacto %}
17 | {{ macros_contact::contact(contacto=cliente.contacto) }}
18 | {%- endif %}
19 |
20 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/moneda.rs:
--------------------------------------------------------------------------------
1 | use crate::models::credit_note::CreditNote;
2 | use crate::models::debit_note::DebitNote;
3 | use crate::models::invoice::Invoice;
4 |
5 | pub trait MonedaGetter {
6 | fn get_moneda(&self) -> &Option<&str>;
7 | }
8 |
9 | pub trait MonedaSetter {
10 | fn set_moneda(&mut self, val: &'static str);
11 | }
12 |
13 | impl MonedaGetter for Invoice {
14 | fn get_moneda(&self) -> &Option<&str> {
15 | &self.moneda
16 | }
17 | }
18 |
19 | impl MonedaGetter for CreditNote {
20 | fn get_moneda(&self) -> &Option<&str> {
21 | &self.moneda
22 | }
23 | }
24 |
25 | impl MonedaGetter for DebitNote {
26 | fn get_moneda(&self) -> &Option<&str> {
27 | &self.moneda
28 | }
29 | }
30 |
31 | impl MonedaSetter for Invoice {
32 | fn set_moneda(&mut self, val: &'static str) {
33 | self.moneda = Some(val);
34 | }
35 | }
36 |
37 | impl MonedaSetter for CreditNote {
38 | fn set_moneda(&mut self, val: &'static str) {
39 | self.moneda = Some(val);
40 | }
41 | }
42 |
43 | impl MonedaSetter for DebitNote {
44 | fn set_moneda(&mut self, val: &'static str) {
45 | self.moneda = Some(val);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/detalle/mod.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::Detalle;
2 | use crate::models::credit_note::CreditNote;
3 | use crate::models::debit_note::DebitNote;
4 | use crate::models::invoice::Invoice;
5 |
6 | pub mod cantidad;
7 | pub mod icb;
8 | pub mod icb_aplica;
9 | pub mod icb_tasa;
10 | pub mod igv;
11 | pub mod igv_base_imponible;
12 | pub mod igv_tasa;
13 | pub mod igv_tipo;
14 | pub mod isc;
15 | pub mod isc_base_imponible;
16 | pub mod isc_tasa;
17 | pub mod isc_tipo;
18 | pub mod precio;
19 | pub mod precio_con_impuestos;
20 | pub mod precio_referencia;
21 | pub mod precio_referencia_tipo;
22 | pub mod total_impuestos;
23 | pub mod unidad_medida;
24 |
25 | pub trait DetallesGetter {
26 | fn get_detalles(&mut self) -> &mut Vec;
27 | }
28 |
29 | impl DetallesGetter for Invoice {
30 | fn get_detalles(&mut self) -> &mut Vec {
31 | &mut self.detalles
32 | }
33 | }
34 |
35 | impl DetallesGetter for CreditNote {
36 | fn get_detalles(&mut self) -> &mut Vec {
37 | &mut self.detalles
38 | }
39 | }
40 |
41 | impl DetallesGetter for DebitNote {
42 | fn get_detalles(&mut self) -> &mut Vec {
43 | &mut self.detalles
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/xbuilder/tests/invoice_anticipos.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal_macros::dec;
2 |
3 | use xbuilder::prelude::*;
4 |
5 | use crate::common::assert_invoice;
6 | use crate::common::invoice_base;
7 |
8 | mod common;
9 |
10 | const BASE: &str = "tests/resources/e2e/renderer/invoice/InvoiceAnticiposTest";
11 |
12 | #[serial_test::serial]
13 | #[tokio::test]
14 | async fn invoice_anticipos() {
15 | let mut invoice = Invoice {
16 | detalles: vec![
17 | Detalle {
18 | descripcion: "Item1",
19 | cantidad: dec!(2),
20 | precio: Some(dec!(100)),
21 | ..Default::default()
22 | },
23 | Detalle {
24 | descripcion: "Item2",
25 | cantidad: dec!(2),
26 | precio: Some(dec!(100)),
27 | ..Default::default()
28 | },
29 | ],
30 | anticipos: vec![Anticipo {
31 | comprobante_serie_numero: "F001-2",
32 | monto: dec!(100),
33 | tipo: None,
34 | comprobante_tipo: None,
35 | }],
36 | ..invoice_base()
37 | };
38 |
39 | assert_invoice(&mut invoice, &format!("{BASE}/minAnticipos.xml")).await;
40 | }
41 |
--------------------------------------------------------------------------------
/server/ui/client/src/app/components/Notifications.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import {
4 | Alert,
5 | AlertActionCloseButton,
6 | AlertGroup,
7 | } from "@patternfly/react-core";
8 | import { NotificationsContext } from "./NotificationsContext";
9 |
10 | export const Notifications: React.FunctionComponent = () => {
11 | const appContext = React.useContext(NotificationsContext);
12 | return (
13 |
14 | {appContext.notifications.map((notification) => {
15 | return (
16 | {
24 | appContext.dismissNotification(notification.title);
25 | }}
26 | />
27 | ),
28 | })}
29 | timeout={notification.timeout ?? 4000}
30 | >
31 | {notification.message}
32 |
33 | );
34 | })}
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/note/tipo_comprobante_afectado.rs:
--------------------------------------------------------------------------------
1 | use crate::models::credit_note::CreditNote;
2 | use crate::models::debit_note::DebitNote;
3 |
4 | pub trait NoteTipoComprobanteAfectadoGetter {
5 | fn get_comprobante_afectado_tipo(&self) -> &Option<&'static str>;
6 | }
7 |
8 | pub trait NoteTipoComprobanteAfectadoSetter {
9 | fn set_comprobante_afectado_tipo(&mut self, val: &'static str);
10 | }
11 |
12 | impl NoteTipoComprobanteAfectadoGetter for CreditNote {
13 | fn get_comprobante_afectado_tipo(&self) -> &Option<&'static str> {
14 | &self.comprobante_afectado_tipo
15 | }
16 | }
17 |
18 | impl NoteTipoComprobanteAfectadoGetter for DebitNote {
19 | fn get_comprobante_afectado_tipo(&self) -> &Option<&'static str> {
20 | &self.comprobante_afectado_tipo
21 | }
22 | }
23 |
24 | impl NoteTipoComprobanteAfectadoSetter for CreditNote {
25 | fn set_comprobante_afectado_tipo(&mut self, val: &'static str) {
26 | self.comprobante_afectado_tipo = Some(val);
27 | }
28 | }
29 |
30 | impl NoteTipoComprobanteAfectadoSetter for DebitNote {
31 | fn set_comprobante_afectado_tipo(&mut self, val: &'static str) {
32 | self.comprobante_afectado_tipo = Some(val);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/server/ui/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@openubl-ui/client",
3 | "version": "1.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "clean": "rimraf ./dist",
8 | "clean:all": "rimraf ./dist ./node_modules",
9 | "build": "rsbuild build",
10 | "check": "biome check .",
11 | "check:write": "biome check --write .",
12 | "dev": "rsbuild dev",
13 | "start": "rsbuild preview"
14 | },
15 | "dependencies": {
16 | "@patternfly/patternfly": "^6.1.0",
17 | "@patternfly/react-core": "^6.1.0",
18 | "@patternfly/react-table": "^6.1.0",
19 | "@patternfly/react-tokens": "^6.1.0",
20 | "@tanstack/react-query": "^5.66.11",
21 | "@tanstack/react-query-devtools": "^5.66.11",
22 | "@tanstack/react-router": "^1.112.0",
23 | "@tanstack/react-table": "^8.21.2",
24 | "@tanstack/router-devtools": "^1.112.0",
25 | "axios": "^1.8.1",
26 | "react": "^18.3.1",
27 | "react-dom": "^18.3.1"
28 | },
29 | "devDependencies": {
30 | "@rsbuild/core": "^1.2.8",
31 | "@rsbuild/plugin-react": "^1.1.0",
32 | "@tanstack/router-plugin": "^1.99.0",
33 | "@types/react": "^18.3.18",
34 | "@types/react-dom": "^18.3.5",
35 | "raw-loader": "^4.0.2"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.github/workflows/image-build.yaml:
--------------------------------------------------------------------------------
1 | name: Multiple Architecture Image Build
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - "main"
8 | - "release-*"
9 | tags:
10 | - "v*"
11 |
12 | concurrency:
13 | group: build-${{ github.ref }}
14 | cancel-in-progress: true
15 |
16 | jobs:
17 | server-image-build:
18 | uses: project-openubl/release-tools/.github/workflows/build-push-images.yaml@main
19 | with:
20 | registry: "ghcr.io"
21 | image_name: "${{ github.repository_owner }}/openubl-server"
22 | containerfile: "./Dockerfile.server"
23 | architectures: '[ "amd64", "arm64" ]'
24 | secrets:
25 | registry_username: ${{ github.actor }}
26 | registry_password: ${{ secrets.GITHUB_TOKEN }}
27 |
28 | ui-image-build:
29 | uses: project-openubl/release-tools/.github/workflows/build-push-images.yaml@main
30 | with:
31 | registry: "ghcr.io"
32 | image_name: "${{ github.repository_owner }}/openubl-ui"
33 | containerfile: "./Dockerfile.ui"
34 | architectures: '[ "amd64", "arm64" ]'
35 | extra-args: "--ulimit nofile=4096:4096"
36 | secrets:
37 | registry_username: ${{ github.actor }}
38 | registry_password: ${{ secrets.GITHUB_TOKEN }}
39 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/icb.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::credit_note::CreditNote;
4 | use crate::models::debit_note::DebitNote;
5 | use crate::models::invoice::Invoice;
6 |
7 | pub trait IcbTasaGetter {
8 | fn get_icb_tasa(&self) -> &Option;
9 | }
10 |
11 | pub trait IcbTasaSetter {
12 | fn set_icb_tasa(&mut self, val: Decimal);
13 | }
14 |
15 | impl IcbTasaGetter for Invoice {
16 | fn get_icb_tasa(&self) -> &Option {
17 | &self.icb_tasa
18 | }
19 | }
20 |
21 | impl IcbTasaGetter for CreditNote {
22 | fn get_icb_tasa(&self) -> &Option {
23 | &self.icb_tasa
24 | }
25 | }
26 |
27 | impl IcbTasaGetter for DebitNote {
28 | fn get_icb_tasa(&self) -> &Option {
29 | &self.icb_tasa
30 | }
31 | }
32 |
33 | impl IcbTasaSetter for Invoice {
34 | fn set_icb_tasa(&mut self, val: Decimal) {
35 | self.icb_tasa = Some(val);
36 | }
37 | }
38 |
39 | impl IcbTasaSetter for CreditNote {
40 | fn set_icb_tasa(&mut self, val: Decimal) {
41 | self.icb_tasa = Some(val);
42 | }
43 | }
44 |
45 | impl IcbTasaSetter for DebitNote {
46 | fn set_icb_tasa(&mut self, val: Decimal) {
47 | self.icb_tasa = Some(val);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/igv.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::credit_note::CreditNote;
4 | use crate::models::debit_note::DebitNote;
5 | use crate::models::invoice::Invoice;
6 |
7 | pub trait IgvTasaGetter {
8 | fn get_igv_tasa(&self) -> &Option;
9 | }
10 |
11 | pub trait IgvTasaSetter {
12 | fn set_igv_tasa(&mut self, val: Decimal);
13 | }
14 |
15 | impl IgvTasaGetter for Invoice {
16 | fn get_igv_tasa(&self) -> &Option {
17 | &self.igv_tasa
18 | }
19 | }
20 |
21 | impl IgvTasaGetter for CreditNote {
22 | fn get_igv_tasa(&self) -> &Option {
23 | &self.igv_tasa
24 | }
25 | }
26 |
27 | impl IgvTasaGetter for DebitNote {
28 | fn get_igv_tasa(&self) -> &Option {
29 | &self.igv_tasa
30 | }
31 | }
32 |
33 | impl IgvTasaSetter for Invoice {
34 | fn set_igv_tasa(&mut self, val: Decimal) {
35 | self.igv_tasa = Some(val);
36 | }
37 | }
38 |
39 | impl IgvTasaSetter for CreditNote {
40 | fn set_igv_tasa(&mut self, val: Decimal) {
41 | self.igv_tasa = Some(val);
42 | }
43 | }
44 |
45 | impl IgvTasaSetter for DebitNote {
46 | fn set_igv_tasa(&mut self, val: Decimal) {
47 | self.igv_tasa = Some(val);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase3summary/leyenda.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::catalogs::{Catalog, Catalog7};
4 | use crate::enricher::bounds::detalle::DetallesGetter;
5 | use crate::enricher::bounds::leyendas::{LeyendasGetter, LeyendasSetter};
6 |
7 | pub trait LeyendaIVAPSummaryRule {
8 | fn summary(&mut self) -> Result;
9 | }
10 |
11 | fn insert_leyenda(obj: &mut T, code: &'static str, label: &'static str) -> bool
12 | where
13 | T: LeyendasGetter + LeyendasSetter,
14 | {
15 | if !obj.get_leyendas().contains_key(code) {
16 | obj.insert_leyendas(code, label);
17 | true
18 | } else {
19 | false
20 | }
21 | }
22 |
23 | impl LeyendaIVAPSummaryRule for T
24 | where
25 | T: DetallesGetter + LeyendasGetter + LeyendasSetter,
26 | {
27 | fn summary(&mut self) -> Result {
28 | if self
29 | .get_detalles()
30 | .iter()
31 | .any(|e| e.igv_tipo.unwrap_or("") == Catalog7::GravadoIvap.code())
32 | {
33 | Ok(insert_leyenda(
34 | self,
35 | "2007",
36 | "Leyenda: Operacion sujeta a IVAP",
37 | ))
38 | } else {
39 | Ok(false)
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/server/migration/src/m20240113_213636_create_keystore.rs:
--------------------------------------------------------------------------------
1 | use sea_orm_migration::prelude::*;
2 |
3 | #[derive(DeriveMigrationName)]
4 | pub struct Migration;
5 |
6 | #[async_trait::async_trait]
7 | impl MigrationTrait for Migration {
8 | async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
9 | manager
10 | .create_table(
11 | Table::create()
12 | .table(Keystore::Table)
13 | .if_not_exists()
14 | .col(
15 | ColumnDef::new(Keystore::Id)
16 | .integer()
17 | .not_null()
18 | .auto_increment()
19 | .primary_key(),
20 | )
21 | .col(ColumnDef::new(Keystore::Name).string().not_null())
22 | .to_owned(),
23 | )
24 | .await
25 | }
26 |
27 | async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
28 | manager
29 | .drop_table(Table::drop().table(Keystore::Table).to_owned())
30 | .await
31 | }
32 | }
33 |
34 | #[derive(DeriveIden)]
35 | pub enum Keystore {
36 | Table,
37 | Id,
38 | Name,
39 | }
40 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/firmante.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::Firmante;
2 | use crate::models::credit_note::CreditNote;
3 | use crate::models::debit_note::DebitNote;
4 | use crate::models::invoice::Invoice;
5 |
6 | pub trait FirmanteGetter {
7 | fn get_firmante(&self) -> &Option;
8 | }
9 |
10 | pub trait FirmanteSetter {
11 | fn set_firmante(&mut self, val: Firmante);
12 | }
13 |
14 | impl FirmanteGetter for Invoice {
15 | fn get_firmante(&self) -> &Option {
16 | &self.firmante
17 | }
18 | }
19 |
20 | impl FirmanteGetter for CreditNote {
21 | fn get_firmante(&self) -> &Option {
22 | &self.firmante
23 | }
24 | }
25 |
26 | impl FirmanteGetter for DebitNote {
27 | fn get_firmante(&self) -> &Option {
28 | &self.firmante
29 | }
30 | }
31 |
32 | impl FirmanteSetter for Invoice {
33 | fn set_firmante(&mut self, val: Firmante) {
34 | self.firmante = Some(val);
35 | }
36 | }
37 |
38 | impl FirmanteSetter for CreditNote {
39 | fn set_firmante(&mut self, val: Firmante) {
40 | self.firmante = Some(val);
41 | }
42 | }
43 |
44 | impl FirmanteSetter for DebitNote {
45 | fn set_firmante(&mut self, val: Firmante) {
46 | self.firmante = Some(val);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/ivap.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::credit_note::CreditNote;
4 | use crate::models::debit_note::DebitNote;
5 | use crate::models::invoice::Invoice;
6 |
7 | pub trait IvapTasaGetter {
8 | fn get_ivap_tasa(&self) -> &Option;
9 | }
10 |
11 | pub trait IvapTasaSetter {
12 | fn set_ivap_tasa(&mut self, val: Decimal);
13 | }
14 |
15 | impl IvapTasaGetter for Invoice {
16 | fn get_ivap_tasa(&self) -> &Option {
17 | &self.ivap_tasa
18 | }
19 | }
20 |
21 | impl IvapTasaGetter for CreditNote {
22 | fn get_ivap_tasa(&self) -> &Option {
23 | &self.ivap_tasa
24 | }
25 | }
26 |
27 | impl IvapTasaGetter for DebitNote {
28 | fn get_ivap_tasa(&self) -> &Option {
29 | &self.ivap_tasa
30 | }
31 | }
32 |
33 | impl IvapTasaSetter for Invoice {
34 | fn set_ivap_tasa(&mut self, val: Decimal) {
35 | self.ivap_tasa = Some(val);
36 | }
37 | }
38 |
39 | impl IvapTasaSetter for CreditNote {
40 | fn set_ivap_tasa(&mut self, val: Decimal) {
41 | self.ivap_tasa = Some(val);
42 | }
43 | }
44 |
45 | impl IvapTasaSetter for DebitNote {
46 | fn set_ivap_tasa(&mut self, val: Decimal) {
47 | self.ivap_tasa = Some(val);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/invoice/detraccion.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Detraccion;
4 | use crate::models::invoice::Invoice;
5 |
6 | pub trait InvoiceDetraccionGetter {
7 | fn get_detraccion(&mut self) -> &mut Option;
8 | }
9 |
10 | impl InvoiceDetraccionGetter for Invoice {
11 | fn get_detraccion(&mut self) -> &mut Option {
12 | &mut self.detraccion
13 | }
14 | }
15 |
16 | // Monto
17 |
18 | pub trait InvoiceDetraccionMontoGetter {
19 | fn get_monto(&self) -> &Option;
20 | }
21 |
22 | pub trait InvoiceDetraccionMontoSetter {
23 | fn set_monto(&mut self, val: Decimal);
24 | }
25 |
26 | impl InvoiceDetraccionMontoGetter for Detraccion {
27 | fn get_monto(&self) -> &Option {
28 | &self.monto
29 | }
30 | }
31 |
32 | impl InvoiceDetraccionMontoSetter for Detraccion {
33 | fn set_monto(&mut self, val: Decimal) {
34 | self.monto = Some(val);
35 | }
36 | }
37 |
38 | // Porcentaje
39 |
40 | // Monto
41 |
42 | pub trait InvoiceDetraccionPorcentajeGetter {
43 | fn get_porcentaje(&self) -> &Decimal;
44 | }
45 |
46 | impl InvoiceDetraccionPorcentajeGetter for Detraccion {
47 | fn get_porcentaje(&self) -> &Decimal {
48 | &self.porcentaje
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/server/server/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "openubl-server"
3 | description = "Web service for managing UBL files from SUNAT"
4 | version.workspace = true
5 | edition.workspace = true
6 | license.workspace = true
7 |
8 | [dependencies]
9 | openubl-api = { workspace = true }
10 | openubl-common = { workspace = true }
11 | openubl-entity = { workspace = true }
12 | openubl-storage = { workspace = true }
13 | openubl-ui = { workspace = true }
14 |
15 | xhandler = { workspace = true }
16 |
17 | actix-web = { workspace = true }
18 | serde = { workspace = true, features = ["derive"] }
19 | sea-orm = { workspace = true, features = [
20 | "sqlx-sqlite",
21 | "sqlx-postgres",
22 | "runtime-tokio-rustls",
23 | "macros",
24 | ] }
25 | clap = { workspace = true, features = ["derive", "env"] }
26 | anyhow = { workspace = true }
27 | env_logger = { workspace = true }
28 | thiserror = { workspace = true }
29 | utoipa = { workspace = true, features = ["actix_extras", "yaml"] }
30 | utoipa-actix-web = { workspace = true }
31 | utoipa-swagger-ui = { workspace = true, features = ["actix-web"] }
32 | actix-web-httpauth = { workspace = true }
33 | actix-4-jwt-auth = { workspace = true }
34 | actix-multipart = { workspace = true }
35 | actix-web-static-files = { workspace = true }
36 | minio = { workspace = true }
37 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase3summary/note/total_importe.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::enricher::bounds::detalle::DetallesGetter;
4 | use crate::enricher::bounds::note::total_importe::{
5 | NoteTotalImporteGetter, NoteTotalImporteSetter,
6 | };
7 | use crate::enricher::rules::phase3summary::utils::{importe_sin_impuestos, total_impuestos};
8 | use crate::models::common::TotalImporteNote;
9 |
10 | pub trait NoteTotalImporteSummaryRule {
11 | fn summary(&mut self) -> Result;
12 | }
13 |
14 | impl NoteTotalImporteSummaryRule for T
15 | where
16 | T: NoteTotalImporteGetter + NoteTotalImporteSetter + DetallesGetter,
17 | {
18 | fn summary(&mut self) -> Result {
19 | match &self.get_total_importe() {
20 | Some(..) => Ok(false),
21 | None => {
22 | let total_impuestos = total_impuestos(self.get_detalles());
23 | let importe_sin_impuestos = importe_sin_impuestos(self.get_detalles());
24 |
25 | let importe = importe_sin_impuestos + total_impuestos;
26 |
27 | self.set_total_importe(TotalImporteNote {
28 | importe_sin_impuestos,
29 | importe,
30 | });
31 | Ok(true)
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/xbuilder/tests/invoice_documento_relacionado.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal_macros::dec;
2 |
3 | use xbuilder::prelude::*;
4 |
5 | use crate::common::assert_invoice;
6 | use crate::common::invoice_base;
7 |
8 | mod common;
9 |
10 | const BASE: &str = "tests/resources/e2e/renderer/invoice/InvoiceDocumentoRelacionadoTest";
11 |
12 | #[serial_test::serial]
13 | #[tokio::test]
14 | async fn invoice_documento_relacionado_y_orden_de_compra() {
15 | let mut invoice = Invoice {
16 | detalles: vec![
17 | Detalle {
18 | descripcion: "Item1",
19 | cantidad: dec!(2),
20 | precio: Some(dec!(100)),
21 | ..Default::default()
22 | },
23 | Detalle {
24 | descripcion: "Item2",
25 | cantidad: dec!(2),
26 | precio: Some(dec!(100)),
27 | ..Default::default()
28 | },
29 | ],
30 | documentos_relacionados: vec![DocumentoRelacionado {
31 | serie_numero: "B111-1",
32 | tipo_documento: Catalog12::DeclaracionSimplificadaDeImportacion.code(),
33 | }],
34 | orden_de_compra: Some("123456"),
35 | ..invoice_base()
36 | };
37 |
38 | assert_invoice(&mut invoice, &format!("{BASE}/documentoRelacionado.xml")).await;
39 | }
40 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/mod.rs:
--------------------------------------------------------------------------------
1 | use chrono::NaiveDate;
2 | use rust_decimal::Decimal;
3 |
4 | use crate::enricher::fill::Fill;
5 | use crate::enricher::process::Process;
6 | use crate::enricher::summary::Summary;
7 | use crate::models::credit_note::CreditNote;
8 | use crate::models::debit_note::DebitNote;
9 | use crate::models::invoice::Invoice;
10 |
11 | mod bounds;
12 | mod fill;
13 | mod process;
14 | mod rules;
15 | mod summary;
16 |
17 | pub struct Defaults {
18 | pub icb_tasa: Decimal,
19 | pub igv_tasa: Decimal,
20 | pub ivap_tasa: Decimal,
21 | pub date: NaiveDate,
22 | }
23 |
24 | pub trait Enrich {
25 | fn enrich(&mut self, defaults: &Defaults);
26 | }
27 |
28 | impl Enrich for Invoice {
29 | fn enrich(&mut self, defaults: &Defaults) {
30 | Fill::fill(self, defaults);
31 | Process::process(self);
32 | Summary::summary(self);
33 | }
34 | }
35 |
36 | impl Enrich for CreditNote {
37 | fn enrich(&mut self, defaults: &Defaults) {
38 | Fill::fill(self, defaults);
39 | Process::process(self);
40 | Summary::summary(self);
41 | }
42 | }
43 |
44 | impl Enrich for DebitNote {
45 | fn enrich(&mut self, defaults: &Defaults) {
46 | Fill::fill(self, defaults);
47 | Process::process(self);
48 | Summary::summary(self);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/proveedor.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::{Direccion, Proveedor};
2 | use crate::models::credit_note::CreditNote;
3 | use crate::models::debit_note::DebitNote;
4 | use crate::models::invoice::Invoice;
5 |
6 | pub trait ProveedorGetter {
7 | fn get_proveedor(&self) -> &Proveedor;
8 | }
9 |
10 | pub trait ProveedorSetter {
11 | fn set_proveedor_direccion(&mut self, val: Direccion);
12 | }
13 |
14 | impl ProveedorGetter for Invoice {
15 | fn get_proveedor(&self) -> &Proveedor {
16 | &self.proveedor
17 | }
18 | }
19 |
20 | impl ProveedorGetter for CreditNote {
21 | fn get_proveedor(&self) -> &Proveedor {
22 | &self.proveedor
23 | }
24 | }
25 |
26 | impl ProveedorGetter for DebitNote {
27 | fn get_proveedor(&self) -> &Proveedor {
28 | &self.proveedor
29 | }
30 | }
31 |
32 | impl ProveedorSetter for Invoice {
33 | fn set_proveedor_direccion(&mut self, val: Direccion) {
34 | self.proveedor.direccion = Some(val);
35 | }
36 | }
37 |
38 | impl ProveedorSetter for CreditNote {
39 | fn set_proveedor_direccion(&mut self, val: Direccion) {
40 | self.proveedor.direccion = Some(val);
41 | }
42 | }
43 |
44 | impl ProveedorSetter for DebitNote {
45 | fn set_proveedor_direccion(&mut self, val: Direccion) {
46 | self.proveedor.direccion = Some(val);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/renderer/voidedDocuments.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 | {% include "ubl/standard/include/ubl-extensions.xml" %}
7 | 2.0
8 | 1.0
9 | RA-{fechaEmision.format('yyyyMMdd')}-{numero}
10 | {fechaEmisionComprobantes}
11 | {fechaEmision}
12 | {% include "ubl/common/signature.xml" %}
13 | {% include "ubl/sunat/include/supplier.xml" %}
14 | {%- for it in comprobantes %}
15 |
16 | {{loop.index}}
17 | {it.tipo_comprobante}
18 | {it.serie}
19 | {it.numero}
20 | {it.descripcionSustento}
21 |
22 | {%- endfor %}
23 |
24 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/invoice/tipo_comprobante.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use regex::Regex;
3 |
4 | use crate::catalogs::{Catalog, Catalog1};
5 | use crate::enricher::bounds::invoice::tipo_comprobante::{
6 | InvoiceTipoComprobanteGetter, InvoiceTipoComprobanteSetter,
7 | };
8 | use crate::enricher::bounds::serie_numero::SerieNumeroGetter;
9 | use crate::{BOLETA_SERIE_REGEX, FACTURA_SERIE_REGEX};
10 |
11 | pub trait InvoiceTipoComprobanteFillRule {
12 | fn fill(&mut self) -> Result;
13 | }
14 |
15 | impl InvoiceTipoComprobanteFillRule for T
16 | where
17 | T: InvoiceTipoComprobanteGetter + InvoiceTipoComprobanteSetter + SerieNumeroGetter,
18 | {
19 | fn fill(&mut self) -> Result {
20 | match &self.get_tipo_comprobante() {
21 | Some(..) => Ok(false),
22 | None => {
23 | if Regex::new(FACTURA_SERIE_REGEX)?.is_match(self.get_serie_numero()) {
24 | self.set_tipo_comprobante(Catalog1::Factura.code());
25 |
26 | return Ok(true);
27 | } else if Regex::new(BOLETA_SERIE_REGEX)?.is_match(self.get_serie_numero()) {
28 | self.set_tipo_comprobante(Catalog1::Boleta.code());
29 |
30 | return Ok(true);
31 | }
32 |
33 | Ok(false)
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/standard/include/supplier.xml:
--------------------------------------------------------------------------------
1 | {%- import "ubl/standard/include/address.xml" as macros_address -%}
2 | {%- import "ubl/standard/include/contact.xml" as macros_contact -%}
3 |
4 |
5 |
6 |
7 | {{proveedor.ruc}}
8 |
9 | {%- if proveedor.nombre_comercial %}
10 |
11 | {{proveedor.nombre_comercial}}
12 |
13 | {%- endif %}
14 |
15 |
16 | {%- if proveedor.direccion %}
17 | {{ macros_address::address(direccion=proveedor.direccion) }}
18 |
19 | {%- endif %}
20 |
21 | {%- if proveedor.contacto %}
22 | {{ macros_contact::contact(contacto=proveedor.contacto) }}
23 | {%- endif %}
24 |
25 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/leyendas.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use crate::models::credit_note::CreditNote;
4 | use crate::models::debit_note::DebitNote;
5 | use crate::models::invoice::Invoice;
6 |
7 | pub trait LeyendasGetter {
8 | fn get_leyendas(&self) -> &HashMap<&str, &str>;
9 | }
10 |
11 | pub trait LeyendasSetter {
12 | fn insert_leyendas(&mut self, k: &'static str, v: &'static str);
13 | }
14 |
15 | impl LeyendasGetter for Invoice {
16 | fn get_leyendas(&self) -> &HashMap<&str, &str> {
17 | &self.leyendas
18 | }
19 | }
20 |
21 | impl LeyendasGetter for CreditNote {
22 | fn get_leyendas(&self) -> &HashMap<&str, &str> {
23 | &self.leyendas
24 | }
25 | }
26 |
27 | impl LeyendasGetter for DebitNote {
28 | fn get_leyendas(&self) -> &HashMap<&str, &str> {
29 | &self.leyendas
30 | }
31 | }
32 |
33 | impl LeyendasSetter for Invoice {
34 | fn insert_leyendas(&mut self, k: &'static str, v: &'static str) {
35 | self.leyendas.insert(k, v);
36 | }
37 | }
38 |
39 | impl LeyendasSetter for CreditNote {
40 | fn insert_leyendas(&mut self, k: &'static str, v: &'static str) {
41 | self.leyendas.insert(k, v);
42 | }
43 | }
44 |
45 | impl LeyendasSetter for DebitNote {
46 | fn insert_leyendas(&mut self, k: &'static str, v: &'static str) {
47 | self.leyendas.insert(k, v);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/xbuilder/src/models/debit_note.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use chrono::NaiveDate;
4 | use rust_decimal::Decimal;
5 | use serde::Serialize;
6 |
7 | use crate::models::common::{
8 | Cliente, Detalle, DocumentoRelacionado, Firmante, Guia, Proveedor, TotalImporteNote,
9 | TotalImpuestos,
10 | };
11 |
12 | /// Nota de debito
13 | #[derive(Debug, Serialize, Default)]
14 | pub struct DebitNote {
15 | pub leyendas: HashMap<&'static str, &'static str>,
16 |
17 | pub serie_numero: &'static str,
18 | pub moneda: Option<&'static str>,
19 | pub fecha_emision: Option,
20 | pub proveedor: Proveedor,
21 | pub cliente: Cliente,
22 | pub firmante: Option,
23 | pub icb_tasa: Option,
24 | pub igv_tasa: Option,
25 | pub ivap_tasa: Option,
26 |
27 | // Catalog10
28 | pub tipo_nota: Option<&'static str>,
29 | pub comprobante_afectado_serie_numero: &'static str,
30 | // Catalog1
31 | pub comprobante_afectado_tipo: Option<&'static str>,
32 | pub sustento_descripcion: &'static str,
33 |
34 | pub detalles: Vec,
35 |
36 | pub total_importe: Option,
37 | pub total_impuestos: Option,
38 |
39 | pub guias: Vec,
40 | pub documentos_relacionados: Vec,
41 |
42 | pub orden_de_compra: Option<&'static str>,
43 | }
44 |
--------------------------------------------------------------------------------
/xbuilder/src/models/credit_note.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use chrono::NaiveDate;
4 | use rust_decimal::Decimal;
5 | use serde::Serialize;
6 |
7 | use crate::models::common::{
8 | Cliente, Detalle, DocumentoRelacionado, Firmante, Guia, Proveedor, TotalImporteNote,
9 | TotalImpuestos,
10 | };
11 |
12 | /// Nota de credito
13 | #[derive(Debug, Serialize, Default)]
14 | pub struct CreditNote {
15 | pub leyendas: HashMap<&'static str, &'static str>,
16 |
17 | pub serie_numero: &'static str,
18 | pub moneda: Option<&'static str>,
19 | pub fecha_emision: Option,
20 | pub proveedor: Proveedor,
21 | pub cliente: Cliente,
22 | pub firmante: Option,
23 | pub icb_tasa: Option,
24 | pub igv_tasa: Option,
25 | pub ivap_tasa: Option,
26 |
27 | /// Catalog9
28 | pub tipo_nota: Option<&'static str>,
29 | pub comprobante_afectado_serie_numero: &'static str,
30 | /// Catalog1
31 | pub comprobante_afectado_tipo: Option<&'static str>,
32 | pub sustento_descripcion: &'static str,
33 |
34 | pub detalles: Vec,
35 |
36 | pub total_importe: Option,
37 | pub total_impuestos: Option,
38 |
39 | pub guias: Vec,
40 | pub documentos_relacionados: Vec,
41 |
42 | pub orden_de_compra: Option<&'static str>,
43 | }
44 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/fecha_emision.rs:
--------------------------------------------------------------------------------
1 | use chrono::NaiveDate;
2 |
3 | use crate::models::credit_note::CreditNote;
4 | use crate::models::debit_note::DebitNote;
5 | use crate::models::invoice::Invoice;
6 |
7 | pub trait FechaEmisionGetter {
8 | fn get_fecha_emision(&self) -> &Option;
9 | }
10 |
11 | pub trait FechaEmisionSetter {
12 | fn set_fecha_emision(&mut self, val: NaiveDate);
13 | }
14 |
15 | //
16 |
17 | impl FechaEmisionGetter for Invoice {
18 | fn get_fecha_emision(&self) -> &Option {
19 | &self.fecha_emision
20 | }
21 | }
22 |
23 | impl FechaEmisionGetter for CreditNote {
24 | fn get_fecha_emision(&self) -> &Option {
25 | &self.fecha_emision
26 | }
27 | }
28 |
29 | impl FechaEmisionGetter for DebitNote {
30 | fn get_fecha_emision(&self) -> &Option {
31 | &self.fecha_emision
32 | }
33 | }
34 |
35 | impl FechaEmisionSetter for Invoice {
36 | fn set_fecha_emision(&mut self, val: NaiveDate) {
37 | self.fecha_emision = Some(val);
38 | }
39 | }
40 |
41 | impl FechaEmisionSetter for CreditNote {
42 | fn set_fecha_emision(&mut self, val: NaiveDate) {
43 | self.fecha_emision = Some(val);
44 | }
45 | }
46 |
47 | impl FechaEmisionSetter for DebitNote {
48 | fn set_fecha_emision(&mut self, val: NaiveDate) {
49 | self.fecha_emision = Some(val);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/process.rs:
--------------------------------------------------------------------------------
1 | use crate::enricher::rules::phase2process::detalle::detalles::DetallesProcessRule;
2 | use crate::models::credit_note::CreditNote;
3 | use crate::models::debit_note::DebitNote;
4 | use crate::models::invoice::Invoice;
5 |
6 | pub trait Process {
7 | fn process(&mut self);
8 | }
9 |
10 | trait ProcessCommon {
11 | fn process_common(&mut self);
12 | }
13 |
14 | // trait ProcessInvoice {
15 | // fn process_invoice(&mut self);
16 | // }
17 | //
18 | // trait ProcessCreditNote {
19 | // fn process_creditnote(&mut self);
20 | // }
21 | //
22 | // trait ProcessDebitNote {
23 | // fn process_debitnote(&mut self);
24 | // }
25 |
26 | impl Process for Invoice {
27 | fn process(&mut self) {
28 | self.process_common();
29 | }
30 | }
31 |
32 | impl Process for CreditNote {
33 | fn process(&mut self) {
34 | self.process_common();
35 | }
36 | }
37 |
38 | impl Process for DebitNote {
39 | fn process(&mut self) {
40 | self.process_common();
41 | }
42 | }
43 |
44 | impl ProcessCommon for T
45 | where
46 | T: DetallesProcessRule,
47 | {
48 | fn process_common(&mut self) {
49 | let mut changed = true;
50 |
51 | while changed {
52 | let results = [DetallesProcessRule::process(self).unwrap_or_default()];
53 |
54 | changed = results.contains(&true);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/server/migration/README.md:
--------------------------------------------------------------------------------
1 | # CLI
2 |
3 | Generate migration and apply it:
4 |
5 | ```shell
6 | sea-orm-cli migrate generate NAME_OF_MIGRATION
7 | sea-orm-cli migrate -u postgres://user:password@localhost/openubl
8 | ```
9 |
10 | Generate entity files of database `openubl` to `entity/src`
11 |
12 | ```shell
13 | sea-orm-cli generate entity -u postgres://user:password@localhost/openubl -o entity/src
14 | ```
15 |
16 | # Running Migrator CLI
17 |
18 | - Generate a new migration file
19 | ```sh
20 | cargo run -- generate MIGRATION_NAME
21 | ```
22 | - Apply all pending migrations
23 | ```sh
24 | cargo run
25 | ```
26 | ```sh
27 | cargo run -- up
28 | ```
29 | - Apply first 10 pending migrations
30 | ```sh
31 | cargo run -- up -n 10
32 | ```
33 | - Rollback last applied migrations
34 | ```sh
35 | cargo run -- down
36 | ```
37 | - Rollback last 10 applied migrations
38 | ```sh
39 | cargo run -- down -n 10
40 | ```
41 | - Drop all tables from the database, then reapply all migrations
42 | ```sh
43 | cargo run -- fresh
44 | ```
45 | - Rollback all applied migrations, then reapply all migrations
46 | ```sh
47 | cargo run -- refresh
48 | ```
49 | - Rollback all applied migrations
50 | ```sh
51 | cargo run -- reset
52 | ```
53 | - Check the status of all migrations
54 | ```sh
55 | cargo run -- status
56 | ```
57 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/proveedor.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::enricher::bounds::proveedor::{ProveedorGetter, ProveedorSetter};
4 | use crate::models::common::Direccion;
5 |
6 | pub trait ProveedorFillRule {
7 | fn fill(&mut self) -> Result;
8 | }
9 |
10 | impl ProveedorFillRule for T
11 | where
12 | T: ProveedorGetter + ProveedorSetter,
13 | {
14 | fn fill(&mut self) -> Result {
15 | match &self.get_proveedor().direccion {
16 | Some(direccion) => match direccion.codigo_local {
17 | None => {
18 | self.set_proveedor_direccion(Direccion {
19 | codigo_local: Some("0000"),
20 | ..*direccion
21 | });
22 | Ok(true)
23 | }
24 | Some(_) => Ok(false),
25 | },
26 | None => {
27 | self.set_proveedor_direccion(Direccion {
28 | codigo_pais: None,
29 | departamento: None,
30 | provincia: None,
31 | distrito: None,
32 | direccion: None,
33 | urbanizacion: None,
34 | ubigeo: None,
35 | codigo_local: Some("0000"),
36 | });
37 | Ok(true)
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/invoice/tipo_operacion.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::catalogs::{Catalog, Catalog51};
4 | use crate::enricher::bounds::invoice::detraccion::InvoiceDetraccionGetter;
5 | use crate::enricher::bounds::invoice::percepcion::InvoicePercepcionGetter;
6 | use crate::enricher::bounds::invoice::tipo_operacion::{
7 | InvoiceTipoOperacionGetter, InvoiceTipoOperacionSetter,
8 | };
9 |
10 | pub trait InvoiceTipoOperacionFillRule {
11 | fn fill(&mut self) -> Result;
12 | }
13 |
14 | impl InvoiceTipoOperacionFillRule for T
15 | where
16 | T: InvoiceTipoOperacionGetter
17 | + InvoiceTipoOperacionSetter
18 | + InvoiceDetraccionGetter
19 | + InvoicePercepcionGetter,
20 | {
21 | fn fill(&mut self) -> Result {
22 | match &self.get_tipo_operacion() {
23 | Some(..) => Ok(false),
24 | None => {
25 | if self.get_detraccion().is_some() {
26 | self.set_tipo_operacion(Catalog51::OperacionSujetaADetraccion.code());
27 | } else if self.get_percepcion().is_some() {
28 | self.set_tipo_operacion(Catalog51::OperacionSujetaAPercepcion.code());
29 | } else {
30 | self.set_tipo_operacion(Catalog51::VentaInterna.code());
31 | }
32 |
33 | Ok(false)
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/server/ui/client/src/app/components/NotificationsProvider.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import {
4 | type INotification,
5 | type INotificationsProvider,
6 | NotificationsContext,
7 | } from "./NotificationsContext.tsx";
8 |
9 | const notificationDefault: Pick = {
10 | hideCloseButton: false,
11 | };
12 |
13 | export const NotificationsProvider: React.FunctionComponent<
14 | INotificationsProvider
15 | > = ({ children }: INotificationsProvider) => {
16 | const [notifications, setNotifications] = React.useState([]);
17 |
18 | const pushNotification = (
19 | notification: INotification,
20 | clearNotificationDelay?: number,
21 | ) => {
22 | setNotifications([
23 | ...notifications,
24 | { ...notificationDefault, ...notification },
25 | ]);
26 | setTimeout(() => setNotifications([]), clearNotificationDelay ?? 10000);
27 | };
28 |
29 | const dismissNotification = (title: string) => {
30 | const remainingNotifications = notifications.filter(
31 | (n) => n.title !== title,
32 | );
33 | setNotifications(remainingNotifications);
34 | };
35 |
36 | return (
37 |
44 | {children}
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/invoice/anticipos.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::Anticipo;
2 | use crate::models::invoice::Invoice;
3 |
4 | pub trait InvoiceAnticiposGetter {
5 | fn get_anticipos(&mut self) -> &mut Vec;
6 | }
7 |
8 | impl InvoiceAnticiposGetter for Invoice {
9 | fn get_anticipos(&mut self) -> &mut Vec {
10 | &mut self.anticipos
11 | }
12 | }
13 |
14 | //
15 |
16 | pub trait AnticipoGetter {
17 | fn get_tipo(&self) -> &Option<&'static str>;
18 | fn get_comprobante_tipo(&self) -> &Option<&'static str>;
19 | fn get_comprobante_serie_numero(&self) -> &'static str;
20 | }
21 |
22 | pub trait AnticipoSetter {
23 | fn set_tipo(&mut self, val: &'static str);
24 | fn set_comprobante_tipo(&mut self, val: &'static str);
25 | }
26 |
27 | impl AnticipoGetter for Anticipo {
28 | fn get_tipo(&self) -> &Option<&'static str> {
29 | &self.tipo
30 | }
31 |
32 | fn get_comprobante_tipo(&self) -> &Option<&'static str> {
33 | &self.comprobante_tipo
34 | }
35 |
36 | fn get_comprobante_serie_numero(&self) -> &'static str {
37 | self.comprobante_serie_numero
38 | }
39 | }
40 |
41 | impl AnticipoSetter for Anticipo {
42 | fn set_tipo(&mut self, val: &'static str) {
43 | self.tipo = Some(val);
44 | }
45 |
46 | fn set_comprobante_tipo(&mut self, val: &'static str) {
47 | self.comprobante_tipo = Some(val);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase2process/detalle/icb.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use rust_decimal::Decimal;
3 |
4 | use crate::enricher::bounds::detalle::cantidad::DetalleCantidadGetter;
5 | use crate::enricher::bounds::detalle::icb::{DetalleIcbGetter, DetalleIcbSetter};
6 | use crate::enricher::bounds::detalle::icb_aplica::DetalleICBAplicaGetter;
7 | use crate::enricher::bounds::detalle::icb_tasa::DetalleIcbTasaGetter;
8 |
9 | pub trait DetalleICBProcessRule {
10 | fn process(&mut self) -> Result;
11 | }
12 |
13 | impl DetalleICBProcessRule for T
14 | where
15 | T: DetalleIcbGetter
16 | + DetalleIcbSetter
17 | + DetalleICBAplicaGetter
18 | + DetalleCantidadGetter
19 | + DetalleIcbTasaGetter,
20 | {
21 | fn process(&mut self) -> Result {
22 | match &self.get_icb() {
23 | Some(..) => Ok(false),
24 | None => {
25 | if self.get_icb_aplica() {
26 | if let Some(icb_tasa) = self.get_icb_tasa() {
27 | let icb = self.get_cantidad() * *icb_tasa;
28 | self.set_icb(icb);
29 | Ok(true)
30 | } else {
31 | Ok(false)
32 | }
33 | } else {
34 | self.set_icb(Decimal::ZERO);
35 | Ok(true)
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/xbuilder/tests/resources/xsd/2.1/common/UBL-XAdESv141-2.1.xsd:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/server/ui/client/src/assets/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/total_impuestos.rs:
--------------------------------------------------------------------------------
1 | use crate::models::common::TotalImpuestos;
2 | use crate::models::credit_note::CreditNote;
3 | use crate::models::debit_note::DebitNote;
4 | use crate::models::invoice::Invoice;
5 |
6 | pub trait TotalImpuestosGetter {
7 | fn get_total_impuestos(&self) -> &Option;
8 | }
9 |
10 | pub trait TotalImpuestosSetter {
11 | fn set_total_impuestos(&mut self, val: TotalImpuestos);
12 | }
13 |
14 | impl TotalImpuestosGetter for Invoice {
15 | fn get_total_impuestos(&self) -> &Option {
16 | &self.total_impuestos
17 | }
18 | }
19 |
20 | impl TotalImpuestosGetter for CreditNote {
21 | fn get_total_impuestos(&self) -> &Option {
22 | &self.total_impuestos
23 | }
24 | }
25 |
26 | impl TotalImpuestosGetter for DebitNote {
27 | fn get_total_impuestos(&self) -> &Option {
28 | &self.total_impuestos
29 | }
30 | }
31 |
32 | impl TotalImpuestosSetter for Invoice {
33 | fn set_total_impuestos(&mut self, val: TotalImpuestos) {
34 | self.total_impuestos = Some(val);
35 | }
36 | }
37 |
38 | impl TotalImpuestosSetter for CreditNote {
39 | fn set_total_impuestos(&mut self, val: TotalImpuestos) {
40 | self.total_impuestos = Some(val);
41 | }
42 | }
43 |
44 | impl TotalImpuestosSetter for DebitNote {
45 | fn set_total_impuestos(&mut self, val: TotalImpuestos) {
46 | self.total_impuestos = Some(val);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/xsender/resources/templates/validate_cdp_criterios.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{username}}
6 | {{password}}
7 |
8 |
9 |
10 |
11 |
12 | {{body.ruc}}
13 | {{body.tipo_comprobante}}
14 | {{body.serie_comprobante}}
15 | {{body.numero_comprobante}}
16 | {{body.tipo_documento_identidad_receptor}}
17 | {{body.numero_documento_identidad_receptor}}
18 | {{body.fecha_emision}}
19 | {{body.importe_total}}
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Dockerfile.ui:
--------------------------------------------------------------------------------
1 | # Builder image
2 | FROM registry.access.redhat.com/ubi9/nodejs-22:latest AS builder
3 |
4 | USER 1001
5 | COPY --chown=1001 ./server/ui .
6 | COPY --chown=1001 ./entrypoint.ui.sh ./entrypoint.sh
7 | RUN npm install -g npm@9
8 | RUN npm clean-install --ignore-scripts && npm run build && npm run dist
9 |
10 | # Runner image
11 | FROM registry.access.redhat.com/ubi9/nodejs-22-minimal:latest
12 |
13 | # Add ps package to allow liveness probe for k8s cluster
14 | # Add tar package to allow copying files with kubectl scp
15 | USER 0
16 | RUN microdnf -y install tar procps-ng && microdnf clean all
17 |
18 | USER 1001
19 |
20 | LABEL name="openubl/openubl-ui" \
21 | description="Openubl - User Interface" \
22 | help="For more information visit https://project-openubl.github.io/" \
23 | license="Apache License 2.0" \
24 | maintainer="carlosthe19916@gmail.com" \
25 | summary="Openubl - User Interface" \
26 | url="https://ghcr.io/project-openubl/openubl-ui" \
27 | usage="podman run -p 80 -v project-openubl/openubl-ui:latest" \
28 | io.k8s.display-name="openubl-ui" \
29 | io.k8s.description="Openubl - User Interface" \
30 | io.openshift.expose-services="80:http" \
31 | io.openshift.tags="operator,openubl,ui,nodejs22" \
32 | io.openshift.min-cpu="100m" \
33 | io.openshift.min-memory="350Mi"
34 |
35 | COPY --from=builder /opt/app-root/src/dist /opt/app-root/dist/
36 |
37 | ENV DEBUG=1
38 |
39 | WORKDIR /opt/app-root/dist
40 | ENTRYPOINT ["./entrypoint.sh"]
--------------------------------------------------------------------------------
/server/ui/common/src/branding.ts:
--------------------------------------------------------------------------------
1 | export interface MastheadBrand {
2 | src: string;
3 | alt: string;
4 | height: string;
5 | }
6 |
7 | export interface MastheadTitle {
8 | text: string;
9 | heading?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
10 | size?: "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl";
11 | }
12 |
13 | export interface BrandingStrings {
14 | application: {
15 | title: string;
16 | name?: string;
17 | description?: string;
18 | };
19 |
20 | about: {
21 | displayName: string;
22 | imageSrc?: string;
23 | documentationUrl?: string;
24 | };
25 |
26 | masthead: {
27 | leftBrand?: MastheadBrand;
28 | leftTitle?: MastheadTitle;
29 | rightBrand?: MastheadBrand;
30 | };
31 | }
32 |
33 | // Note: Typescript will look at the `paths` definition to resolve this import
34 | // to a stub JSON file. In the next rollup build step, that import will
35 | // be replaced by the rollup virtual plugin with a dynamically generated
36 | // JSON import with the actual branding information.
37 | import * as stringsJson from "@branding/strings.json";
38 |
39 | export const brandingStrings =
40 | stringsJson.default as unknown as BrandingStrings;
41 |
42 | /**
43 | * Return the `node_modules/` resolved path for the branding assets.
44 | */
45 | import { createRequire } from "node:module";
46 | const require = createRequire(import.meta.url);
47 |
48 | export const brandingAssetPath = () =>
49 | `${require
50 | .resolve("@openubl-ui/common/package.json")
51 | .replace(/(.)\/package.json$/, "$1")}/dist/branding`;
52 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/detalle/precio_referencia_tipo.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::catalogs::{Catalog, Catalog16, Catalog7, FromCode};
4 | use crate::enricher::bounds::detalle::igv_tipo::DetalleIgvTipoGetter;
5 | use crate::enricher::bounds::detalle::precio_referencia_tipo::{
6 | DetallePrecioReferenciaTipoGetter, DetallePrecioReferenciaTipoSetter,
7 | };
8 | use crate::enricher::rules::phase1fill::detalle::detalles::DetalleDefaults;
9 |
10 | pub trait DetallePrecioReferenciaTipoFillRule {
11 | fn fill(&mut self, defaults: &DetalleDefaults) -> Result;
12 | }
13 |
14 | impl DetallePrecioReferenciaTipoFillRule for T
15 | where
16 | T: DetallePrecioReferenciaTipoGetter + DetallePrecioReferenciaTipoSetter + DetalleIgvTipoGetter,
17 | {
18 | fn fill(&mut self, _: &DetalleDefaults) -> Result {
19 | match (self.get_precio_referencia_tipo(), *self.get_igv_tipo()) {
20 | (None, Some(igv_tipo)) => {
21 | if let Ok(catalog) = Catalog7::from_code(igv_tipo) {
22 | let catalog16 = if catalog.onerosa() {
23 | &Catalog16::PrecioUnitarioIncluyeIgv
24 | } else {
25 | &Catalog16::ValorReferencialUnitarioEnOperacionesNoOnerosas
26 | };
27 | self.set_precio_referencia_tipo(catalog16.code());
28 | Ok(true)
29 | } else {
30 | Ok(false)
31 | }
32 | }
33 | _ => Ok(false),
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/note/tipo_comprobante_afectado.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use log::warn;
3 | use regex::Regex;
4 |
5 | use crate::catalogs::{Catalog, Catalog1};
6 | use crate::enricher::bounds::note::tipo_comprobante_afectado::{
7 | NoteTipoComprobanteAfectadoGetter, NoteTipoComprobanteAfectadoSetter,
8 | };
9 | use crate::enricher::bounds::serie_numero::SerieNumeroGetter;
10 | use crate::{BOLETA_SERIE_REGEX, FACTURA_SERIE_REGEX};
11 |
12 | pub trait NoteComprobanteAfectadoTipoFillRule {
13 | fn fill(&mut self) -> Result;
14 | }
15 |
16 | impl NoteComprobanteAfectadoTipoFillRule for T
17 | where
18 | T: NoteTipoComprobanteAfectadoGetter + NoteTipoComprobanteAfectadoSetter + SerieNumeroGetter,
19 | {
20 | fn fill(&mut self) -> Result {
21 | match &self.get_comprobante_afectado_tipo() {
22 | Some(..) => Ok(false),
23 | None => {
24 | if Regex::new(FACTURA_SERIE_REGEX)?.is_match(self.get_serie_numero()) {
25 | self.set_comprobante_afectado_tipo(Catalog1::Factura.code());
26 | Ok(true)
27 | } else if Regex::new(BOLETA_SERIE_REGEX)?.is_match(self.get_serie_numero()) {
28 | self.set_comprobante_afectado_tipo(Catalog1::Boleta.code());
29 | Ok(true)
30 | } else {
31 | warn!(
32 | "tipo_comprobante_afectado no pudo ser inferido a partir de serie-numero"
33 | );
34 | Ok(false)
35 | }
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/bounds/invoice/descuentos.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 |
3 | use crate::models::common::Descuento;
4 | use crate::models::invoice::Invoice;
5 |
6 | pub trait InvoiceDescuentosGetter {
7 | fn get_descuentos(&mut self) -> &mut Vec;
8 | }
9 |
10 | impl InvoiceDescuentosGetter for Invoice {
11 | fn get_descuentos(&mut self) -> &mut Vec {
12 | &mut self.descuentos
13 | }
14 | }
15 |
16 | //
17 |
18 | pub trait DescuentoGetter {
19 | fn get_tipo(&self) -> &Option<&'static str>;
20 | fn get_monto(&self) -> Decimal;
21 | fn get_monto_base(&self) -> &Option;
22 | fn get_factor(&self) -> &Option;
23 | }
24 |
25 | pub trait DescuentoSetter {
26 | fn set_tipo(&mut self, val: &'static str);
27 | fn set_monto_base(&mut self, val: Decimal);
28 | fn set_factor(&mut self, val: Decimal);
29 | }
30 |
31 | impl DescuentoGetter for Descuento {
32 | fn get_tipo(&self) -> &Option<&'static str> {
33 | &self.tipo
34 | }
35 |
36 | fn get_monto(&self) -> Decimal {
37 | self.monto
38 | }
39 |
40 | fn get_monto_base(&self) -> &Option {
41 | &self.monto_base
42 | }
43 |
44 | fn get_factor(&self) -> &Option {
45 | &self.factor
46 | }
47 | }
48 |
49 | impl DescuentoSetter for Descuento {
50 | fn set_tipo(&mut self, val: &'static str) {
51 | self.tipo = Some(val);
52 | }
53 |
54 | fn set_monto_base(&mut self, val: Decimal) {
55 | self.monto_base = Some(val);
56 | }
57 |
58 | fn set_factor(&mut self, val: Decimal) {
59 | self.factor = Some(val);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/ubl/standard/include/address.xml:
--------------------------------------------------------------------------------
1 | {%- macro address(direccion) -%}
2 | {%- if direccion.ubigeo %}
3 | {{direccion.ubigeo}}
4 | {%- endif %}
5 | {%- if direccion.codigo_local %}
6 | {{direccion.codigo_local}}
7 | {%- endif %}
8 | {%- if direccion.urbanizacion %}
9 | {{direccion.urbanizacion}}
10 | {%- endif %}
11 | {%- if direccion.provincia %}
12 | {{direccion.provincia}}
13 | {%- endif %}
14 | {%- if direccion.departamento %}
15 | {{direccion.departamento}}
16 | {%- endif %}
17 | {%- if direccion.distrito %}
18 | {{direccion.distrito}}
19 | {%- endif %}
20 | {%- if direccion.direccion %}
21 |
22 |
23 |
24 | {%- endif %}
25 | {%- if direccion.codigo_pais %}
26 |
27 | {{direccion.codigo_pais}}
28 |
29 | {%- endif %}
30 | {%- endmacro address -%}
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase1fill/detalle/igv_tasa.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use rust_decimal::Decimal;
3 |
4 | use crate::catalogs::{Catalog7, Catalog7Group, FromCode};
5 | use crate::enricher::bounds::detalle::igv_tasa::{DetalleIgvTasaGetter, DetalleIgvTasaSetter};
6 | use crate::enricher::bounds::detalle::igv_tipo::DetalleIgvTipoGetter;
7 | use crate::enricher::rules::phase1fill::detalle::detalles::DetalleDefaults;
8 |
9 | pub trait DetalleIGVTasaFillRule {
10 | fn fill(&mut self, defaults: &DetalleDefaults) -> Result;
11 | }
12 |
13 | impl DetalleIGVTasaFillRule for T
14 | where
15 | T: DetalleIgvTasaGetter + DetalleIgvTasaSetter + DetalleIgvTipoGetter,
16 | {
17 | fn fill(&mut self, defaults: &DetalleDefaults) -> Result {
18 | match (self.get_igv_tasa(), *self.get_igv_tipo()) {
19 | (None, Some(igv_tipo)) => {
20 | if let Ok(catalog) = Catalog7::from_code(igv_tipo) {
21 | let tasa = match catalog {
22 | Catalog7::GravadoIvap => defaults.ivap_tasa,
23 | _ => match catalog.group() {
24 | Catalog7Group::Gravado | Catalog7Group::Gratuita => defaults.igv_tasa,
25 | Catalog7Group::Exonerado
26 | | Catalog7Group::Inafecto
27 | | Catalog7Group::Exportacion => Decimal::ZERO,
28 | },
29 | };
30 | self.set_igv_tasa(tasa);
31 | Ok(true)
32 | } else {
33 | Ok(false)
34 | }
35 | }
36 | _ => Ok(false),
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/server/ui/client/src/routes/__root.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense } from "react";
2 |
3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4 | import { Outlet, createRootRoute } from "@tanstack/react-router";
5 |
6 | import { NotificationsProvider } from "@app/components/NotificationsProvider.tsx";
7 | import { DefaultLayout } from "@app/layout";
8 |
9 | import "@patternfly/patternfly/patternfly-addons.css";
10 | import "@patternfly/patternfly/patternfly.css";
11 |
12 | const TanStackQueryDevtools =
13 | process.env.NODE_ENV === "production"
14 | ? () => null
15 | : React.lazy(() =>
16 | import("@tanstack/react-query-devtools").then((res) => ({
17 | default: res.ReactQueryDevtools,
18 | })),
19 | );
20 |
21 | const TanStackRouterDevtools =
22 | process.env.NODE_ENV === "production"
23 | ? () => null
24 | : React.lazy(() =>
25 | import("@tanstack/router-devtools").then((res) => ({
26 | default: res.TanStackRouterDevtools,
27 | })),
28 | );
29 |
30 | const queryClient = new QueryClient();
31 |
32 | export const Route = createRootRoute({
33 | component: RootComponent,
34 | });
35 |
36 | function RootComponent() {
37 | return (
38 | <>
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | >
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/server/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@openubl-ui/root",
3 | "version": "1.0.0",
4 | "license": "Apache-2.0",
5 | "private": true,
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/project-openubl/xhandler-rust.git"
9 | },
10 | "scripts": {
11 | "clean": "rimraf ./dist && npm run clean -ws --if-present",
12 | "clean:all": "npm run clean:all -ws --if-present && rimraf ./dist ./node_modules",
13 | "dist": "rimraf ./dist && copyfiles -e 'node_modules/**' entrypoint.sh '**/package.json' '*/dist/**/*' ./dist",
14 | "check": "npm run check -ws --if-present",
15 | "check:write": "npm run check:write -ws --if-present",
16 | "build": "npm run build -ws --if-present",
17 | "dev:common": "npm run dev -w common",
18 | "dev:server": "npm run dev -w server",
19 | "dev:client": "npm run dev -w client",
20 | "dev": "concurrently -n common,client -c 'white.bold.inverse,green.bold.inverse,blue.bold.inverse' 'npm:dev:common' 'npm:dev:client'",
21 | "start": "npm run build -w common -w client && npm run start -w server"
22 | },
23 | "workspaces": ["common", "client", "server"],
24 | "dependencies": {
25 | "@types/express": "^4.17.21",
26 | "http-proxy-middleware": "^2.0.6"
27 | },
28 | "devDependencies": {
29 | "@biomejs/biome": "^1.9.4",
30 | "@rollup/plugin-commonjs": "^28.0.2",
31 | "@rollup/plugin-json": "^6.1.0",
32 | "@rollup/plugin-node-resolve": "^16.0.0",
33 | "@rollup/plugin-run": "^3.1.0",
34 | "@rollup/plugin-typescript": "^12.1.2",
35 | "@rollup/plugin-virtual": "^3.0.2",
36 | "@types/node": "^22.8.1",
37 | "concurrently": "^9.1.2",
38 | "copyfiles": "^2.4.1",
39 | "rimraf": "^6.0.1",
40 | "rollup": "^3.29.5",
41 | "rollup-plugin-copy": "^3.5.0",
42 | "typescript": "^5.7.3"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/server/storage/src/config.rs:
--------------------------------------------------------------------------------
1 | #[derive(clap::Subcommand, Debug)]
2 | pub enum Storage {
3 | Local(LocalStorage),
4 | Minio(MinioStorage),
5 | S3(S3Storage),
6 | }
7 |
8 | #[derive(clap::Args, Debug)]
9 | pub struct LocalStorage {
10 | #[arg(
11 | id = "storage-local-dir",
12 | long,
13 | env = "STORAGE_LOCAL_DIR",
14 | default_value = "storage"
15 | )]
16 | pub local_dir: String,
17 | }
18 |
19 | #[derive(clap::Args, Debug)]
20 | pub struct MinioStorage {
21 | #[arg(id = "storage-minio-host", long, env = "STORAGE_MINIO_HOST")]
22 | pub host: String,
23 |
24 | #[arg(
25 | id = "storage-minio-bucket",
26 | long,
27 | env = "STORAGE_MINIO_BUCKET",
28 | default_value = "openubl"
29 | )]
30 | pub bucket: String,
31 |
32 | #[arg(
33 | id = "storage-minio-access-key",
34 | long,
35 | env = "STORAGE_MINIO_ACCESS_KEY"
36 | )]
37 | pub access_key: String,
38 |
39 | #[arg(
40 | id = "storage-minio-secret-key",
41 | long,
42 | env = "STORAGE_MINIO_SECRET_KEY"
43 | )]
44 | pub secret_key: String,
45 | }
46 |
47 | #[derive(clap::Args, Debug)]
48 | pub struct S3Storage {
49 | #[arg(id = "storage-s3-region", long, env = "STORAGE_S3_REGION")]
50 | pub region: String,
51 |
52 | #[arg(
53 | id = "storage-s3-bucket",
54 | long,
55 | env = "STORAGE_S3_BUCKET",
56 | default_value = "openubl"
57 | )]
58 | pub bucket: String,
59 |
60 | #[arg(id = "storage-s3-access-key", long, env = "STORAGE_S3_ACCESS_KEY")]
61 | pub access_key: String,
62 |
63 | #[arg(id = "storage-s3-secret-key", long, env = "STORAGE_S3_SECRET_KEY")]
64 | pub secret_key: String,
65 | }
66 |
--------------------------------------------------------------------------------
/Dockerfile.server:
--------------------------------------------------------------------------------
1 | ######################################################################
2 | # UI
3 | ######################################################################
4 | FROM registry.access.redhat.com/ubi9/nodejs-22:latest AS ui-source
5 | USER 1001
6 | COPY --chown=1001 . .
7 | RUN cd server/ui/ && \
8 | npm install -g npm@9 && \
9 | npm clean-install --ignore-scripts && npm run build && npm run dist && \
10 | rm -rf node_modules
11 |
12 | ######################################################################
13 | # Build server
14 | ######################################################################
15 | FROM registry.access.redhat.com/ubi9/ubi:latest AS server-builder
16 |
17 | # Dependencies
18 | RUN dnf install -y libxml2-devel openssl-devel gcc
19 |
20 | RUN mkdir /stage/ && \
21 | dnf install --installroot /stage/ --setop install_weak_deps=false --nodocs -y zlib openssl && \
22 | dnf clean all --installroot /stage/
23 |
24 | # Setup Rust
25 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
26 | ENV PATH=${PATH}:/root/.cargo/bin
27 | RUN rustup target add $(uname -m)-unknown-linux-gnu
28 |
29 | # Build source code
30 | COPY --from=ui-source /opt/app-root/src/ /code/openubl/
31 | RUN cd /code/openubl/ && \
32 | cargo build --no-default-features --release --target=$(uname -m)-unknown-linux-gnu && \
33 | find /code/openubl/target/ -name "server" -exec cp -av {} /stage/usr/local/bin \;
34 |
35 | ######################################################################
36 | # Builder runner
37 | ######################################################################
38 | FROM registry.access.redhat.com/ubi9/ubi-micro:latest AS server-runner
39 | COPY --from=server-builder /stage/ .
40 | ENTRYPOINT ["/usr/local/bin/server"]
--------------------------------------------------------------------------------
/server/migration/src/m20240117_142858_create_send_rule.rs:
--------------------------------------------------------------------------------
1 | use crate::m20240114_154538_create_credentials::Credentials;
2 | use sea_orm_migration::prelude::*;
3 |
4 | #[derive(DeriveMigrationName)]
5 | pub struct Migration;
6 |
7 | #[async_trait::async_trait]
8 | impl MigrationTrait for Migration {
9 | async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
10 | manager
11 | .create_table(
12 | Table::create()
13 | .table(SendRule::Table)
14 | .if_not_exists()
15 | .col(
16 | ColumnDef::new(SendRule::Id)
17 | .integer()
18 | .not_null()
19 | .auto_increment()
20 | .primary_key(),
21 | )
22 | .col(ColumnDef::new(SendRule::SupplierId).string().not_null())
23 | .col(ColumnDef::new(SendRule::CredentialsId).integer().not_null())
24 | .foreign_key(
25 | ForeignKey::create()
26 | .from_col(SendRule::CredentialsId)
27 | .to(Credentials::Table, Credentials::Id)
28 | .on_delete(ForeignKeyAction::Cascade),
29 | )
30 | .to_owned(),
31 | )
32 | .await
33 | }
34 |
35 | async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
36 | manager
37 | .drop_table(Table::drop().table(SendRule::Table).to_owned())
38 | .await
39 | }
40 | }
41 |
42 | #[derive(DeriveIden)]
43 | enum SendRule {
44 | Table,
45 | Id,
46 | SupplierId,
47 | CredentialsId,
48 | }
49 |
--------------------------------------------------------------------------------
/xsigner/resources/test/private.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEArhO3H48lGkRNcPNhA6uTd804NnMxBkXKKTgR8DldX8vTmrf0
3 | JqNGMLxUlqSG1KlRelHQXvIz7GWO0NgE0DZ9eMEULS7S8YMuj6RZFCudDb/aasxH
4 | yCvjVfdKJUF4BIPPKN2dvjFBAQz4fI/3/PceptIqzwzl+8SryXEbJgAUmjaS2POE
5 | 65RePRIINOV1Vi7lwvLzH0Zl1sr+oytOnXAI1YRlKZhgcS5v5XeX/qfRpbIQdqxc
6 | loVAQX/voN8QsLT6chZr/gEZbUnDs2HD286/Xzg27Rw8Bwy7HIbhhKYPK2TsFanp
7 | MhsTVtC3gxp6umLb3Fuala7RBC76nDZC9A+95QIDAQABAoIBABhHrbIcMCuivT50
8 | 4+I0K0R5fk6x8HOUhmcLaA0eozR6ZJBe+hHtkhu4GQBOAHRnDXNHOA4WMEHXxHzC
9 | tKEqCIQwQhUvQ8Ll7jegz7/teWFykg91YMm9vV6/ODtMD2Zp0Bo+FwNxMUTpPzt4
10 | hTlmaoMQK2JnxShBvUhCm2vIdRcxLHV63HjRWqHu98vKYxQ5ByQX3nVBP757zRI2
11 | rhC5g0yzQucGj2GMeD3t8W/NozNaUx9qXq2YaqhIYfhbzKZH41ZeIpE0Au7aNS4W
12 | BTpWkO1patCpSZHhTV9RIbBCG7al0ukLs3FfbWoHCAJAHUyuEvG4htSb0WqudlJn
13 | /rPNdP0CgYEA4SK8NgON4wvdi42dr43NdcOVbWes4HM4M8f1pi7W9RSracuAXj7o
14 | eyirKPnUMJchRNOlF/aTghbgtbgAYBFxYYfFbc12BURiAgo6firu6ILD7696V/ux
15 | iQPSg/chVrBkN1rYYf1sTgcJB9N7uuiBQZAh8NJWJNvviPVxNfFhoO8CgYEAxfEM
16 | CnEBiiOiKi72eclGGAAQr/JAdoaCXZPi1lmbAkWULtyqoo3MzyuyJ3GDwb1j8e2J
17 | qPEvqAW8w993Z5vqk/N9MA7rlSE6UPxTHs8ZKNWcdci0rReurG+evrGRRJmKuvqK
18 | 0/7Nqr/f039VuRqvgtWxJeFoBNZVpwGG/LeCJmsCgYAVcjyhnJcQkNnK6HOj/Isc
19 | 88OxR1YFj5REAoFZEk8xy4VEr7kLwUxeJxKe9aWL92mY59xrOvb0Rn+jb+LBRAgb
20 | 9VYOTqs2dzwq25SU3jwh9Ar8MyghZ32TAsU0Av+vBWCWkVXZh82gZTUsBK5dsLZX
21 | a4aALVk9a6IW1uKw88yMCwKBgFk3e2jdZIdB5l7DCh78ZFZ++QaE1x9VIz9QX8aj
22 | XqWYfODeXx6jcTPTixoSJQPW/ExX91spUoSWCW3ztBsEAKgs8DkQEIkIEAPepwxU
23 | 5g8ssLe5/g2ihf181f03hbV4yznZoWdKCqMyloz6cMXczEzZSl47iancfYCnxJL1
24 | l3j/AoGBAIQDUua/Ia2LLJE24kamiLmdtECHsXg/Wrp++YaGc2btHblAN5TNQfy3
25 | S4yvQIOzaVp7AQMXq/AUdua1YcLS1Op/CsocgVMzpckZ7FVS8BFuQnQx8ltnAcqb
26 | nCo6UzUdOPKNRw2EDyk9yK83wEtvkvlHOVdRsOlYN5ZSrkq1X92A
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/server/ui/common/rollup.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 |
3 | import { readFileSync } from "node:fs";
4 | import path from "node:path";
5 | import { fileURLToPath } from "node:url";
6 | import util from "node:util";
7 |
8 | import nodeResolve from "@rollup/plugin-node-resolve";
9 | import typescript from "@rollup/plugin-typescript";
10 | import virtual from "@rollup/plugin-virtual";
11 | import ejs from "ejs";
12 | import copy from "rollup-plugin-copy";
13 |
14 | const __dirname = fileURLToPath(new URL(".", import.meta.url));
15 | const pathTo = (...relativePath) => path.resolve(__dirname, ...relativePath);
16 |
17 | const baseBrandingPath = process.env.BRANDING ?? "./branding";
18 | const brandingPath = pathTo("../", baseBrandingPath);
19 | const jsonStrings = JSON.parse(
20 | readFileSync(path.resolve(brandingPath, "./strings.json"), "utf8"),
21 | );
22 | const stringModule = ejs.render(
23 | `
24 | export const strings = ${util.inspect(jsonStrings)};
25 | export default strings;
26 | `,
27 | {
28 | brandingRoot: "branding",
29 | },
30 | );
31 |
32 | console.log("Using branding assets from:", brandingPath);
33 |
34 | const config = {
35 | input: "src/index.ts",
36 |
37 | output: [
38 | {
39 | file: "dist/index.mjs",
40 | format: "esm",
41 | sourcemap: true,
42 | },
43 | {
44 | file: "dist/index.cjs",
45 | format: "cjs",
46 | sourcemap: true,
47 | },
48 | ],
49 |
50 | watch: {
51 | clearScreen: false,
52 | },
53 |
54 | plugins: [
55 | copy({
56 | targets: [{ src: `${brandingPath}/**/*`, dest: "dist/branding" }],
57 | }),
58 | nodeResolve(),
59 | typescript(),
60 | virtual({
61 | "@branding/strings.json": stringModule,
62 | }),
63 | ],
64 | };
65 |
66 | export default config;
67 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase2process/detalle/precio_referencia.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::catalogs::{Catalog7, FromCode};
4 | use crate::enricher::bounds::detalle::igv_tipo::DetalleIgvTipoGetter;
5 | use crate::enricher::bounds::detalle::precio::DetallePrecioGetter;
6 | use crate::enricher::bounds::detalle::precio_con_impuestos::DetallePrecioConImpuestosGetter;
7 | use crate::enricher::bounds::detalle::precio_referencia::{
8 | DetallePrecioReferenciaGetter, DetallePrecioReferenciaSetter,
9 | };
10 |
11 | pub trait DetallePrecioReferenciaProcessRule {
12 | fn process(&mut self) -> Result;
13 | }
14 |
15 | impl DetallePrecioReferenciaProcessRule for T
16 | where
17 | T: DetallePrecioReferenciaGetter
18 | + DetallePrecioReferenciaSetter
19 | + DetallePrecioGetter
20 | + DetallePrecioConImpuestosGetter
21 | + DetalleIgvTipoGetter,
22 | {
23 | fn process(&mut self) -> Result {
24 | match (
25 | &self.get_precio_referencia(),
26 | &self.get_igv_tipo(),
27 | &self.get_precio(),
28 | &self.get_precio_con_impuestos(),
29 | ) {
30 | (None, Some(igv_tipo), Some(precio), Some(precio_con_impuestos)) => {
31 | if let Ok(catalog) = Catalog7::from_code(igv_tipo) {
32 | let precio_referencia = if catalog.onerosa() {
33 | precio_con_impuestos
34 | } else {
35 | precio
36 | };
37 | self.set_precio_referencia(*precio_referencia);
38 | Ok(true)
39 | } else {
40 | Ok(false)
41 | }
42 | }
43 | _ => Ok(false),
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/xbuilder/src/models/invoice.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use chrono::{NaiveDate, NaiveTime};
4 | use rust_decimal::Decimal;
5 | use serde::Serialize;
6 |
7 | use crate::models::common::{
8 | Anticipo, Cliente, Descuento, Detalle, Detraccion, Direccion, DocumentoRelacionado, Firmante,
9 | FormaDePago, Guia, Percepcion, Proveedor, TotalImporteInvoice, TotalImpuestos,
10 | };
11 |
12 | /// Boleta o Factura
13 | #[derive(Debug, Serialize, Default)]
14 | pub struct Invoice {
15 | pub leyendas: HashMap<&'static str, &'static str>,
16 |
17 | pub serie_numero: &'static str,
18 | pub moneda: Option<&'static str>,
19 | pub fecha_emision: Option,
20 | pub hora_emision: Option,
21 | pub fecha_vencimiento: Option,
22 | pub proveedor: Proveedor,
23 | pub cliente: Cliente,
24 | pub firmante: Option,
25 | pub icb_tasa: Option,
26 | pub igv_tasa: Option,
27 | pub ivap_tasa: Option,
28 |
29 | /// Catalog1
30 | pub tipo_comprobante: Option<&'static str>,
31 |
32 | /// Catalog51
33 | pub tipo_operacion: Option<&'static str>,
34 |
35 | pub detraccion: Option,
36 | pub percepcion: Option,
37 |
38 | pub direccion_entrega: Option,
39 | pub forma_de_pago: Option,
40 |
41 | pub anticipos: Vec,
42 | pub descuentos: Vec,
43 |
44 | pub detalles: Vec,
45 |
46 | pub total_importe: Option,
47 | pub total_impuestos: Option,
48 |
49 | pub guias: Vec,
50 | pub documentos_relacionados: Vec,
51 |
52 | pub observaciones: Option<&'static str>,
53 | pub orden_de_compra: Option<&'static str>,
54 | }
55 |
--------------------------------------------------------------------------------
/xbuilder/tests/resources/certificates/private.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEArhO3H48lGkRNcPNhA6uTd804NnMxBkXKKTgR8DldX8vTmrf0
3 | JqNGMLxUlqSG1KlRelHQXvIz7GWO0NgE0DZ9eMEULS7S8YMuj6RZFCudDb/aasxH
4 | yCvjVfdKJUF4BIPPKN2dvjFBAQz4fI/3/PceptIqzwzl+8SryXEbJgAUmjaS2POE
5 | 65RePRIINOV1Vi7lwvLzH0Zl1sr+oytOnXAI1YRlKZhgcS5v5XeX/qfRpbIQdqxc
6 | loVAQX/voN8QsLT6chZr/gEZbUnDs2HD286/Xzg27Rw8Bwy7HIbhhKYPK2TsFanp
7 | MhsTVtC3gxp6umLb3Fuala7RBC76nDZC9A+95QIDAQABAoIBABhHrbIcMCuivT50
8 | 4+I0K0R5fk6x8HOUhmcLaA0eozR6ZJBe+hHtkhu4GQBOAHRnDXNHOA4WMEHXxHzC
9 | tKEqCIQwQhUvQ8Ll7jegz7/teWFykg91YMm9vV6/ODtMD2Zp0Bo+FwNxMUTpPzt4
10 | hTlmaoMQK2JnxShBvUhCm2vIdRcxLHV63HjRWqHu98vKYxQ5ByQX3nVBP757zRI2
11 | rhC5g0yzQucGj2GMeD3t8W/NozNaUx9qXq2YaqhIYfhbzKZH41ZeIpE0Au7aNS4W
12 | BTpWkO1patCpSZHhTV9RIbBCG7al0ukLs3FfbWoHCAJAHUyuEvG4htSb0WqudlJn
13 | /rPNdP0CgYEA4SK8NgON4wvdi42dr43NdcOVbWes4HM4M8f1pi7W9RSracuAXj7o
14 | eyirKPnUMJchRNOlF/aTghbgtbgAYBFxYYfFbc12BURiAgo6firu6ILD7696V/ux
15 | iQPSg/chVrBkN1rYYf1sTgcJB9N7uuiBQZAh8NJWJNvviPVxNfFhoO8CgYEAxfEM
16 | CnEBiiOiKi72eclGGAAQr/JAdoaCXZPi1lmbAkWULtyqoo3MzyuyJ3GDwb1j8e2J
17 | qPEvqAW8w993Z5vqk/N9MA7rlSE6UPxTHs8ZKNWcdci0rReurG+evrGRRJmKuvqK
18 | 0/7Nqr/f039VuRqvgtWxJeFoBNZVpwGG/LeCJmsCgYAVcjyhnJcQkNnK6HOj/Isc
19 | 88OxR1YFj5REAoFZEk8xy4VEr7kLwUxeJxKe9aWL92mY59xrOvb0Rn+jb+LBRAgb
20 | 9VYOTqs2dzwq25SU3jwh9Ar8MyghZ32TAsU0Av+vBWCWkVXZh82gZTUsBK5dsLZX
21 | a4aALVk9a6IW1uKw88yMCwKBgFk3e2jdZIdB5l7DCh78ZFZ++QaE1x9VIz9QX8aj
22 | XqWYfODeXx6jcTPTixoSJQPW/ExX91spUoSWCW3ztBsEAKgs8DkQEIkIEAPepwxU
23 | 5g8ssLe5/g2ihf181f03hbV4yznZoWdKCqMyloz6cMXczEzZSl47iancfYCnxJL1
24 | l3j/AoGBAIQDUua/Ia2LLJE24kamiLmdtECHsXg/Wrp++YaGc2btHblAN5TNQfy3
25 | S4yvQIOzaVp7AQMXq/AUdua1YcLS1Op/CsocgVMzpckZ7FVS8BFuQnQx8ltnAcqb
26 | nCo6UzUdOPKNRw2EDyk9yK83wEtvkvlHOVdRsOlYN5ZSrkq1X92A
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/.github/workflows/ci-repo.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches-ignore:
6 | - "dependabot/**"
7 | paths-ignore:
8 | - 'README.md'
9 | pull_request: { }
10 |
11 | env:
12 | CI: true
13 |
14 | jobs:
15 | cargo-lint:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v5
19 | - uses: Swatinem/rust-cache@v2
20 | - uses: actions/setup-node@v6
21 | with:
22 | node-version: 22
23 | cache: npm
24 | cache-dependency-path: server/ui/package-lock.json
25 | - name: Install dependencies
26 | uses: ./.github/actions/install-dependencies
27 | - name: Install clippy
28 | run: rustup component add clippy
29 | - run: cargo fmt --check
30 | - run: cargo check
31 | - run: cargo clippy --all-targets --all-features -- -D warnings
32 |
33 | cargo-test:
34 | runs-on: ubuntu-latest
35 | steps:
36 | - uses: actions/checkout@v5
37 | - uses: Swatinem/rust-cache@v2
38 | - uses: actions/setup-node@v6
39 | with:
40 | node-version: 22
41 | cache: npm
42 | cache-dependency-path: server/ui/package-lock.json
43 | - name: Install dependencies
44 | uses: ./.github/actions/install-dependencies
45 | - run: cargo test
46 |
47 | npm-check:
48 | runs-on: ubuntu-latest
49 | steps:
50 | - uses: actions/checkout@v5
51 | - uses: actions/setup-node@v6
52 | with:
53 | node-version: 22
54 | cache: npm
55 | cache-dependency-path: server/ui/package-lock.json
56 | - working-directory: server/ui
57 | run: npm ci
58 | - working-directory: server/ui
59 | run: npm run check
60 | - working-directory: server/ui
61 | run: npm run build
62 |
--------------------------------------------------------------------------------
/xbuilder/tests/invoice_igv_tipo.rs:
--------------------------------------------------------------------------------
1 | use rust_decimal::Decimal;
2 | use rust_decimal_macros::dec;
3 |
4 | use xbuilder::models::common::Detalle;
5 | use xbuilder::prelude::*;
6 |
7 | use crate::common::assert_invoice;
8 | use crate::common::invoice_base;
9 |
10 | mod common;
11 |
12 | const BASE: &str = "tests/resources/e2e/renderer/invoice/InvoiceTipoIgvTest";
13 |
14 | static CATALOG7_VARIANTS: &[Catalog7] = &[
15 | Catalog7::GravadoOperacionOnerosa,
16 | Catalog7::GravadoRetiroPorPremio,
17 | Catalog7::GravadoRetiroPorDonacion,
18 | Catalog7::GravadoRetiro,
19 | Catalog7::GravadoRetiroPorPublicidad,
20 | Catalog7::GravadoBonificaciones,
21 | Catalog7::GravadoRetiroPorEntregaATrabajadores,
22 | Catalog7::GravadoIvap,
23 | Catalog7::ExoneradoOperacionOnerosa,
24 | Catalog7::ExoneradoTransferenciaGratuita,
25 | Catalog7::InafectoOperacionOnerosa,
26 | Catalog7::InafectoRetiroPorBonificacion,
27 | Catalog7::InafectoRetiro,
28 | Catalog7::InafectoRetiroPorMuestrasMedicas,
29 | Catalog7::InafectoRetiroPorConvenioColectivo,
30 | Catalog7::InafectoRetiroPorPremio,
31 | Catalog7::InafectoRetiroPorPublicidad,
32 | Catalog7::Exportacion,
33 | ];
34 |
35 | #[serial_test::serial]
36 | #[tokio::test]
37 | async fn invoice_precio_unitario() {
38 | for catalog7 in CATALOG7_VARIANTS {
39 | let mut invoice = Invoice {
40 | detalles: vec![Detalle {
41 | descripcion: "Item1",
42 | cantidad: Decimal::ONE,
43 | precio: Some(dec!(100)),
44 | igv_tipo: Some(catalog7.code()),
45 | ..Default::default()
46 | }],
47 | ..invoice_base()
48 | };
49 |
50 | assert_invoice(&mut invoice, &format!("{BASE}/invoice_pu_{catalog7}.xml")).await;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/xbuilder/src/enricher/rules/phase3summary/invoice/detraccion.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | use crate::enricher::bounds::invoice::detraccion::{
4 | InvoiceDetraccionGetter, InvoiceDetraccionMontoGetter, InvoiceDetraccionMontoSetter,
5 | InvoiceDetraccionPorcentajeGetter,
6 | };
7 | use crate::enricher::bounds::invoice::total_importe::InvoiceTotalImporteGetter;
8 | use crate::models::common::TotalImporteInvoice;
9 |
10 | pub trait DetraccionSummaryRule {
11 | fn summary(&mut self) -> Result;
12 | }
13 |
14 | impl DetraccionSummaryRule for T
15 | where
16 | T: InvoiceDetraccionGetter + InvoiceTotalImporteGetter,
17 | {
18 | fn summary(&mut self) -> Result {
19 | match (self.get_total_importe().clone(), self.get_detraccion()) {
20 | (Some(total_importe), Some(detraccion)) => {
21 | let results = [
22 | DetraccionMontoRule::summary(detraccion, &total_importe).unwrap_or_default()
23 | ];
24 | Ok(results.contains(&true))
25 | }
26 | _ => Ok(false),
27 | }
28 | }
29 | }
30 |
31 | //
32 |
33 | pub trait DetraccionMontoRule {
34 | fn summary(&mut self, total_importe: &TotalImporteInvoice) -> Result;
35 | }
36 |
37 | impl DetraccionMontoRule for T
38 | where
39 | T: InvoiceDetraccionMontoGetter
40 | + InvoiceDetraccionMontoSetter
41 | + InvoiceDetraccionPorcentajeGetter,
42 | {
43 | fn summary(&mut self, total_importe: &TotalImporteInvoice) -> Result {
44 | match &self.get_monto() {
45 | Some(_) => Ok(false),
46 | None => {
47 | let monto = total_importe.importe_con_impuestos * self.get_porcentaje();
48 | self.set_monto(monto);
49 | Ok(true)
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/renderer/creditNote.xml:
--------------------------------------------------------------------------------
1 | {%- import "ubl/standard/include/document-line.xml" as macros_document_line -%}
2 | {%- import "ubl/standard/include/address.xml" as macros_address -%}
3 |
4 |
7 | {%- include "ubl/standard/include/ubl-extensions.xml" -%}
8 | {%- include "ubl/standard/include/general-data.xml" -%}
9 | {%- if leyendas %}
10 | {%- for key,value in leyendas %}
11 |
12 | {%- endfor %}
13 | {%- endif %}
14 | {{moneda}}
15 | {%- include "ubl/standard/include/note/invoice-reference.xml" -%}
16 | {%- include "ubl/standard/include/guias.xml" -%}
17 | {%- include "ubl/standard/include/documentos-relacionados.xml" -%}
18 | {%- include "ubl/common/signature.xml" %}
19 | {% include "ubl/standard/include/supplier.xml" %}
20 | {% include "ubl/standard/include/customer.xml" -%}
21 | {% include "ubl/standard/include/tax-total.xml" %}
22 |
23 | {%- include "ubl/standard/include/monetary-total.xml" %}
24 |
25 | {%- for it in detalles %}
26 |
27 | {{loop.index}}
28 | {{it.cantidad}}{{ macros_document_line::line(item=it) }}
29 |
30 | {%- endfor %}
31 |
32 |
--------------------------------------------------------------------------------
/xbuilder/resources/templates/renderer/debitNote.xml:
--------------------------------------------------------------------------------
1 | {%- import "ubl/standard/include/document-line.xml" as macros_document_line -%}
2 | {%- import "ubl/standard/include/address.xml" as macros_address -%}
3 |
4 |
7 | {%- include "ubl/standard/include/ubl-extensions.xml" -%}
8 | {%- include "ubl/standard/include/general-data.xml" -%}
9 | {%- if leyendas %}
10 | {%- for key,value in leyendas %}
11 |
12 | {%- endfor %}
13 | {%- endif %}
14 | {{moneda}}
15 | {%- include "ubl/standard/include/note/invoice-reference.xml" -%}
16 | {%- include "ubl/standard/include/guias.xml" -%}
17 | {%- include "ubl/standard/include/documentos-relacionados.xml" -%}
18 | {%- include "ubl/common/signature.xml" %}
19 | {% include "ubl/standard/include/supplier.xml" %}
20 | {% include "ubl/standard/include/customer.xml" -%}
21 | {% include "ubl/standard/include/tax-total.xml" %}
22 |
23 | {%- include "ubl/standard/include/monetary-total.xml" %}
24 |
25 | {%- for it in detalles %}
26 |
27 | {{loop.index}}
28 | {{it.cantidad}}{{ macros_document_line::line(item=it) }}
29 |
30 | {%- endfor %}
31 |
32 |
--------------------------------------------------------------------------------
/server/migration/src/m20240101_104121_create_document.rs:
--------------------------------------------------------------------------------
1 | use sea_orm_migration::prelude::*;
2 |
3 | #[derive(DeriveMigrationName)]
4 | pub struct Migration;
5 |
6 | #[async_trait::async_trait]
7 | impl MigrationTrait for Migration {
8 | async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
9 | manager
10 | .create_table(
11 | Table::create()
12 | .table(Document::Table)
13 | .if_not_exists()
14 | .col(
15 | ColumnDef::new(Document::Id)
16 | .integer()
17 | .not_null()
18 | .auto_increment()
19 | .primary_key(),
20 | )
21 | .col(ColumnDef::new(Document::FileId).string().not_null())
22 | .col(ColumnDef::new(Document::SupplierId).string().not_null())
23 | .col(ColumnDef::new(Document::Identifier).string().not_null())
24 | .col(ColumnDef::new(Document::Type).string().not_null())
25 | .col(ColumnDef::new(Document::VoidedDocumentCode).string())
26 | .col(ColumnDef::new(Document::DigestValue).string())
27 | .col(ColumnDef::new(Document::Sha256).string().not_null())
28 | .to_owned(),
29 | )
30 | .await
31 | }
32 |
33 | async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
34 | manager
35 | .drop_table(Table::drop().table(Document::Table).to_owned())
36 | .await
37 | }
38 | }
39 |
40 | #[derive(DeriveIden)]
41 | pub enum Document {
42 | Table,
43 | Id,
44 | FileId,
45 | Type,
46 | Identifier,
47 | SupplierId,
48 | VoidedDocumentCode,
49 | DigestValue,
50 | Sha256,
51 | }
52 |
--------------------------------------------------------------------------------
/xsigner/resources/test/public.cer:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIE+DCCA+CgAwIBAgIJALURz7AYmJ5+MA0GCSqGSIb3DQEBBQUAMIIBDTEbMBkG
3 | CgmSJomT8ixkARkWC0xMQU1BLlBFIFNBMQswCQYDVQQGEwJQRTENMAsGA1UECAwE
4 | TElNQTENMAsGA1UEBwwETElNQTEYMBYGA1UECgwPVFUgRU1QUkVTQSBTLkEuMUUw
5 | QwYDVQQLDDxETkkgOTk5OTk5OSBSVUMgMTA0Njc3OTM1NDkgLSBDRVJUSUZJQ0FE
6 | TyBQQVJBIERFTU9TVFJBQ0nDk04xRDBCBgNVBAMMO05PTUJSRSBSRVBSRVNFTlRB
7 | TlRFIExFR0FMIC0gQ0VSVElGSUNBRE8gUEFSQSBERU1PU1RSQUNJw5NOMRwwGgYJ
8 | KoZIhvcNAQkBFg1kZW1vQGxsYW1hLnBlMB4XDTE5MTEwODEyNTY1MFoXDTIxMTEw
9 | NzEyNTY1MFowggENMRswGQYKCZImiZPyLGQBGRYLTExBTUEuUEUgU0ExCzAJBgNV
10 | BAYTAlBFMQ0wCwYDVQQIDARMSU1BMQ0wCwYDVQQHDARMSU1BMRgwFgYDVQQKDA9U
11 | VSBFTVBSRVNBIFMuQS4xRTBDBgNVBAsMPEROSSA5OTk5OTk5IFJVQyAxMDQ2Nzc5
12 | MzU0OSAtIENFUlRJRklDQURPIFBBUkEgREVNT1NUUkFDScOTTjFEMEIGA1UEAww7
13 | Tk9NQlJFIFJFUFJFU0VOVEFOVEUgTEVHQUwgLSBDRVJUSUZJQ0FETyBQQVJBIERF
14 | TU9TVFJBQ0nDk04xHDAaBgkqhkiG9w0BCQEWDWRlbW9AbGxhbWEucGUwggEiMA0G
15 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCuE7cfjyUaRE1w82EDq5N3zTg2czEG
16 | RcopOBHwOV1fy9Oat/Qmo0YwvFSWpIbUqVF6UdBe8jPsZY7Q2ATQNn14wRQtLtLx
17 | gy6PpFkUK50Nv9pqzEfIK+NV90olQXgEg88o3Z2+MUEBDPh8j/f89x6m0irPDOX7
18 | xKvJcRsmABSaNpLY84TrlF49Egg05XVWLuXC8vMfRmXWyv6jK06dcAjVhGUpmGBx
19 | Lm/ld5f+p9GlshB2rFyWhUBBf++g3xCwtPpyFmv+ARltScOzYcPbzr9fODbtHDwH
20 | DLschuGEpg8rZOwVqekyGxNW0LeDGnq6YtvcW5qVrtEELvqcNkL0D73lAgMBAAGj
21 | VzBVMB0GA1UdDgQWBBTe18LHVKkeRrWs3Bxp1ikK50l96jAfBgNVHSMEGDAWgBTe
22 | 18LHVKkeRrWs3Bxp1ikK50l96jATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG
23 | 9w0BAQUFAAOCAQEASBWcP4AiqUUZSG2/Z3RU3BgvOVV3if8xYAaZT99n5PsvyBiZ
24 | 3Gh6VpAW9ezoe25ZNSqGMmGfq+R4mEuiqK4h6jDJp0fN47IwRhWjttB9dwpxIDEk
25 | WW7zPdueGx+BY8EuyfNDWR/C7GPfu6azSHiapzeKC57AAZ8xo8kDdhXxDy8hTqNG
26 | olkqnc6QutW8cYPeonqyhi2THN163lZ3Cx5OV8vGFQ3ou2msF0klY9qXopI9i8wQ
27 | jEeOG6bCvVxdID9ZjTbuGbO9pAN9hH7hZ41XEG/CspSWbFf1/Wbnlfusne9v9NgR
28 | j0MM+LAHM7AO5/7j1XwRq4x+U9TSVPgpoU9l5Q==
29 | -----END CERTIFICATE-----
30 |
--------------------------------------------------------------------------------