Compare commits
13 Commits
bfa223a0f0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 57a5fa6d18 | |||
| 0e30684c2f | |||
| 3be8de28c6 | |||
| e9de3fde2c | |||
| 9f92c6e3ae | |||
| 627df17b2b | |||
| c8b5f78757 | |||
| 5889b327bd | |||
| cb0e69169e | |||
| df4c782fbb | |||
| 04e668ff83 | |||
| 1106b9648d | |||
| 1f06a73422 |
30
changelog.md
30
changelog.md
@@ -1,5 +1,35 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-12-11 - 2.0.2 - fix(page)
|
||||||
|
Use theme variables for page styling, make pagecontainer background theme-aware, and use accent background for footer separator
|
||||||
|
|
||||||
|
- Extract documentSettings.theme to a local variable in dedocument-page and use safe optional checks when emitting CSS custom properties
|
||||||
|
- Use theme pageBackground / coverPageBackground values consistently for page background-image fallback logic
|
||||||
|
- Make pagecontainer background follow the --text-bg-color CSS variable instead of a hard #ffffff color
|
||||||
|
- Switch footer separator color variable to --color-accent-bg so the footer stripe uses the theme accent background
|
||||||
|
|
||||||
|
## 2025-12-10 - 2.0.1 - fix(core)
|
||||||
|
Migrate file I/O to @push.rocks/smartfs, adopt TC39 decorators v3 accessor in web components, and update docs/tests
|
||||||
|
|
||||||
|
- Added dependency @push.rocks/smartfs and replaced usages of @push.rocks/smartfile.fs with the SmartFs class-based API
|
||||||
|
- Updated test helpers and test code to use SmartFs (SmartFsProviderNode) and new file read/write methods
|
||||||
|
- Replaced legacy property declarations with TC39 decorators v3 accessor keyword across web components (ts_web/elements/*)
|
||||||
|
- Replaced synchronous smartfile directory operations with async SmartFs directory.exists/create flow for .nogit
|
||||||
|
- Updated README and added readme.hints.md with SmartFs usage examples, decorator guidance, and other developer hints
|
||||||
|
|
||||||
|
## 2025-12-10 - 2.0.0 - BREAKING CHANGE(build)
|
||||||
|
Upgrade dependencies and dev tooling; adjust TypeScript config
|
||||||
|
|
||||||
|
- Bumped runtime dependencies: @design.estate/*, @push.rocks/*, @git.zone/tsrun (to ^2.0.0), @tsclass/tsclass, puppeteer, @types/node, qrcode types, and others.
|
||||||
|
- Updated devDependencies: @git.zone/tsbuild, tsbundle, tstest, tswatch to newer releases.
|
||||||
|
- Removed experimentalDecorators and useDefineForClassFields from tsconfig.json — this may change decorator handling and affect compilation.
|
||||||
|
- These dependency and config changes may introduce breaking behavior; recommend a major version bump.
|
||||||
|
|
||||||
|
## 2025-01-01 - 1.6.11 - fix(license)
|
||||||
|
Update copyright notice in license to reflect new ownership
|
||||||
|
|
||||||
|
- Updated copyright from Lossless GmbH to Task Venture Capital GmbH.
|
||||||
|
|
||||||
## 2024-12-08 - 1.6.10 - fix(core)
|
## 2024-12-08 - 1.6.10 - fix(core)
|
||||||
Improve stability and performance of document generation
|
Improve stability and performance of document generation
|
||||||
|
|
||||||
|
|||||||
2
license
2
license
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2022 Lossless GmbH (hello@lossless.com)
|
Copyright (c) 2022 Task Venture Capital GmbH (hello@task.vc)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
40
package.json
40
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@design.estate/dees-document",
|
"name": "@design.estate/dees-document",
|
||||||
"version": "1.6.10",
|
"version": "2.0.2",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.",
|
"description": "A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.",
|
||||||
"main": "dist_ts_web/index.js",
|
"main": "dist_ts_web/index.js",
|
||||||
@@ -21,27 +21,29 @@
|
|||||||
"author": "Lossless GmbH",
|
"author": "Lossless GmbH",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@design.estate/dees-domtools": "^2.0.65",
|
"@design.estate/dees-catalog": "^3.3.0",
|
||||||
"@design.estate/dees-element": "^2.0.39",
|
"@design.estate/dees-domtools": "^2.3.6",
|
||||||
"@design.estate/dees-wcctools": "^1.0.90",
|
"@design.estate/dees-element": "^2.1.3",
|
||||||
"@git.zone/tsrun": "^1.3.3",
|
"@design.estate/dees-wcctools": "^1.2.1",
|
||||||
"@push.rocks/smartfile": "^11.0.21",
|
"@git.zone/tsrun": "^2.0.0",
|
||||||
"@push.rocks/smartjson": "^5.0.20",
|
"@push.rocks/smartfile": "^13.1.0",
|
||||||
"@push.rocks/smartpath": "^5.0.18",
|
"@push.rocks/smartfs": "^1.2.0",
|
||||||
"@push.rocks/smartpdf": "^3.1.8",
|
"@push.rocks/smartjson": "^6.0.0",
|
||||||
"@push.rocks/smarttime": "^4.0.8",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@tsclass/tsclass": "^4.1.2",
|
"@push.rocks/smartpdf": "^4.1.1",
|
||||||
"@types/node": "^22.10.1",
|
"@push.rocks/smarttime": "^4.1.1",
|
||||||
"@types/qrcode": "^1.5.5",
|
"@tsclass/tsclass": "^9.3.0",
|
||||||
|
"@types/node": "^25.0.0",
|
||||||
|
"@types/qrcode": "^1.5.6",
|
||||||
|
"puppeteer": "^24.32.1",
|
||||||
"qrcode": "^1.5.4"
|
"qrcode": "^1.5.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.2.0",
|
"@git.zone/tsbuild": "^3.1.2",
|
||||||
"@git.zone/tsbundle": "^2.1.0",
|
"@git.zone/tsbundle": "^2.6.3",
|
||||||
"@git.zone/tstest": "^1.0.90",
|
"@git.zone/tstest": "^3.1.3",
|
||||||
"@git.zone/tswatch": "^2.0.34",
|
"@git.zone/tswatch": "^2.3.7",
|
||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.0.2"
|
||||||
"@push.rocks/tapbundle": "^5.5.3"
|
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
|
|||||||
10488
pnpm-lock.yaml
generated
10488
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1 +1,40 @@
|
|||||||
|
# Development Hints
|
||||||
|
|
||||||
|
## Dependencies (v2.0.0)
|
||||||
|
|
||||||
|
- Uses `@push.rocks/smartfs` (v1.2.0) for filesystem operations - SmartFs is class-based with fluent API
|
||||||
|
- Uses `@push.rocks/smartfile` (v13.1.0) - note: v13 removed the `.fs` namespace, use smartfs instead
|
||||||
|
- Uses `@git.zone/tstest/tapbundle` for tests (NOT @push.rocks/tapbundle)
|
||||||
|
- Test files must end with `export default tap.start()`
|
||||||
|
|
||||||
|
## TC39 Decorators v3
|
||||||
|
|
||||||
|
All decorated properties in web components use the `accessor` keyword:
|
||||||
|
```typescript
|
||||||
|
@property({ type: Object })
|
||||||
|
accessor letterData: TInvoice;
|
||||||
|
```
|
||||||
|
|
||||||
|
## SmartFs Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs';
|
||||||
|
const smartfs = new SmartFs(new SmartFsProviderNode());
|
||||||
|
|
||||||
|
// Reading files
|
||||||
|
await smartfs.file('/path/to/file').encoding('utf8').read();
|
||||||
|
|
||||||
|
// Writing files
|
||||||
|
await smartfs.file('/path/to/file').write(buffer);
|
||||||
|
|
||||||
|
// Directory operations
|
||||||
|
await smartfs.directory('/path').exists();
|
||||||
|
await smartfs.directory('/path').create();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
- `ts/` - Node.js server-side code (PdfService)
|
||||||
|
- `ts_web/` - Browser web components (Lit-based)
|
||||||
|
- `ts_shared/` - Shared code (translations, interfaces)
|
||||||
|
- `test/` - Test files
|
||||||
|
|||||||
327
readme.md
327
readme.md
@@ -1,199 +1,276 @@
|
|||||||
# @design.estate/dees-document
|
# @design.estate/dees-document
|
||||||
|
|
||||||
A comprehensive tool for dynamically generating and rendering business documents like invoices using modern web technologies.
|
A powerful TypeScript framework for dynamically generating professional business documents like invoices with web components and PDF export capabilities. 🧾
|
||||||
|
|
||||||
## Install
|
## Issue Reporting and Security
|
||||||
|
|
||||||
To incorporate `@design.estate/dees-document` into your project, execute the following command in your terminal:
|
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npm install @design.estate/dees-document --save
|
pnpm install @design.estate/dees-document
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will install the package and add it to your project's dependencies, thus making all the necessary modules available within your `node_modules` directory.
|
## Features
|
||||||
|
|
||||||
|
- 📄 **PDF Generation** - Server-side PDF creation from structured data using Puppeteer
|
||||||
|
- 🎨 **Web Components** - Lit-based custom elements for document rendering
|
||||||
|
- 🌍 **Multi-language Support** - Built-in translation system (EN, DE, ES)
|
||||||
|
- 📱 **Automatic Pagination** - Smart content overflow handling across pages
|
||||||
|
- 💳 **QR Payment Codes** - EPC QR codes for SEPA payments
|
||||||
|
- 🖨️ **Print Mode** - Optimized rendering for both screen and print
|
||||||
|
- 📐 **A4 Format** - Precise DIN A4 document dimensions
|
||||||
|
- ✨ **Theming** - Customizable colors, backgrounds, and branding
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The `@design.estate/dees-document` package serves as a robust framework to facilitate the generation of business documents, such as invoices, contracts, and reports. Leveraging modern web technologies, this package integrates seamlessly with TypeScript and ES modules, offering a type-safe environment conducive to efficient, dynamic document creation.
|
### Server-Side PDF Generation
|
||||||
|
|
||||||
Below, we provide a detailed guide for utilizing this package, from initializing your environment to generating complete PDF documents.
|
|
||||||
|
|
||||||
### Setting Up the Environment
|
|
||||||
|
|
||||||
Before diving into document creation, ensure your environment is properly configured. Make sure to import necessary dependencies and setup configurations:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { PdfService, IPdfServiceConstructorOptions } from '@design.estate/dees-document';
|
import { PdfService } from '@design.estate/dees-document';
|
||||||
|
import type { TInvoice } from '@tsclass/tsclass/finance';
|
||||||
|
|
||||||
async function setupPdfService() {
|
// Initialize the PDF service
|
||||||
const options: IPdfServiceConstructorOptions = {
|
const pdfService = new PdfService({});
|
||||||
// Configure your options here
|
await pdfService.start();
|
||||||
};
|
|
||||||
|
|
||||||
const pdfService = await PdfService.createAndStart(options);
|
// Create invoice data
|
||||||
console.log('PDF Service started successfully.');
|
const invoice: TInvoice = {
|
||||||
return pdfService;
|
type: 'invoice',
|
||||||
}
|
invoiceType: 'debitnote',
|
||||||
```
|
id: 'INV-2024-001',
|
||||||
|
invoiceId: 'INV-2024-001',
|
||||||
In this setup snippet, you set up the `PdfService` which is the core component for PDF generation. The configuration options should be adjusted to fit your needs, especially considering options such as file paths, languages, and template directories.
|
date: Date.now(),
|
||||||
|
currency: 'EUR',
|
||||||
### Creating a Document Template
|
dueInDays: 30,
|
||||||
|
reverseCharge: false,
|
||||||
To create documents, particularly invoices, you'll first need to define a template. This template describes the structure of your document:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { ILetter } from '@design.estate/dees-document';
|
|
||||||
|
|
||||||
const invoiceTemplate: ILetter = {
|
|
||||||
from: {
|
from: {
|
||||||
name: 'Your Company Name',
|
name: 'Your Company GmbH',
|
||||||
|
type: 'company',
|
||||||
|
status: 'active',
|
||||||
address: {
|
address: {
|
||||||
streetName: 'Your Street',
|
streetName: 'Business Street',
|
||||||
houseNumber: '123',
|
houseNumber: '123',
|
||||||
city: 'Your City',
|
city: 'Berlin',
|
||||||
country: 'Your Country',
|
country: 'Germany',
|
||||||
postalCode: '12345',
|
postalCode: '10115',
|
||||||
|
},
|
||||||
|
sepaConnection: {
|
||||||
|
bic: 'DEUTDEFF',
|
||||||
|
iban: 'DE89370400440532013000',
|
||||||
|
},
|
||||||
|
registrationDetails: {
|
||||||
|
vatId: 'DE123456789',
|
||||||
|
registrationName: 'Amtsgericht Berlin',
|
||||||
|
registrationId: 'HRB 12345',
|
||||||
},
|
},
|
||||||
email: 'your-email@example.com',
|
|
||||||
phone: '123-456-7890',
|
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
name: 'Recipient Company Name',
|
name: 'Customer Inc.',
|
||||||
|
type: 'company',
|
||||||
|
status: 'active',
|
||||||
address: {
|
address: {
|
||||||
streetName: 'Recipient Street',
|
streetName: 'Client Avenue',
|
||||||
houseNumber: '456',
|
houseNumber: '456',
|
||||||
city: 'Recipient City',
|
city: 'Munich',
|
||||||
country: 'Recipient Country',
|
country: 'Germany',
|
||||||
postalCode: '67890',
|
postalCode: '80331',
|
||||||
|
},
|
||||||
|
registrationDetails: {
|
||||||
|
vatId: 'DE987654321',
|
||||||
},
|
},
|
||||||
email: 'recipient-email@example.com',
|
|
||||||
phone: '098-765-4321',
|
|
||||||
},
|
},
|
||||||
content: {
|
|
||||||
invoiceData: {
|
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Service or Product Name',
|
name: 'Web Development Services',
|
||||||
unitQuantity: 2,
|
unitQuantity: 40,
|
||||||
unitNetPrice: 100.0,
|
unitNetPrice: 95,
|
||||||
unitType: 'service',
|
unitType: 'hours',
|
||||||
vatPercentage: 19,
|
vatPercentage: 19,
|
||||||
currency: 'EUR',
|
position: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Hosting (Annual)',
|
||||||
|
unitQuantity: 1,
|
||||||
|
unitNetPrice: 299,
|
||||||
|
unitType: 'item',
|
||||||
|
vatPercentage: 19,
|
||||||
|
position: 2,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
subject: 'Invoice INV-2024-001',
|
||||||
},
|
|
||||||
subject: 'Invoice for Services Rendered',
|
|
||||||
date: new Date().getTime(),
|
|
||||||
versionInfo: {
|
versionInfo: {
|
||||||
type: 'final',
|
type: 'final',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Generate PDF
|
||||||
|
const pdfResult = await pdfService.createPdfFromLetterObject({
|
||||||
|
letterData: invoice,
|
||||||
|
documentSettings: {
|
||||||
|
languageCode: 'EN',
|
||||||
|
enableDefaultHeader: true,
|
||||||
|
enableDefaultFooter: true,
|
||||||
|
enableFoldMarks: true,
|
||||||
|
dateStyle: 'long',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save to file
|
||||||
|
import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs';
|
||||||
|
const fs = new SmartFs(new SmartFsProviderNode());
|
||||||
|
await fs.file('./invoice.pdf').write(Buffer.from(pdfResult.buffer));
|
||||||
|
|
||||||
|
// Don't forget to stop the service when done
|
||||||
|
await pdfService.stop();
|
||||||
```
|
```
|
||||||
|
|
||||||
The `invoiceTemplate` object utilizes the `ILetter` interface which specifies the structure for invoice data, including sender and recipient details, itemization, and pricing.
|
### Document Settings
|
||||||
|
|
||||||
### Generating the Document
|
Customize document appearance with `IDocumentSettings`:
|
||||||
|
|
||||||
Once a template is established, the next step is generating a PDF:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
async function generateInvoice(pdfService: PdfService, invoiceData: ILetter) {
|
const documentSettings = {
|
||||||
const pdfBuffer = await pdfService.createPdfFromLetterObject(invoiceData);
|
// Language for translations
|
||||||
console.log('Invoice PDF generated successfully.');
|
languageCode: 'DE', // 'EN' | 'DE' | 'ES'
|
||||||
|
|
||||||
// Here you could save the PDF to your filesystem, send it via email, etc.
|
// Layout options
|
||||||
}
|
enableDefaultHeader: true,
|
||||||
|
enableDefaultFooter: true,
|
||||||
|
enableFoldMarks: true,
|
||||||
|
enableTopDraftText: true,
|
||||||
|
enableInvoiceContractRefSection: true,
|
||||||
|
|
||||||
|
// Display options
|
||||||
|
vatGroupPositions: true,
|
||||||
|
dateStyle: 'long', // 'short' | 'medium' | 'long' | 'full'
|
||||||
|
|
||||||
|
// Theming
|
||||||
|
theme: {
|
||||||
|
colorPrimaryForeground: '#ffffff',
|
||||||
|
colorPrimaryBackground: '#e4002b',
|
||||||
|
colorAccentForeground: '#333333',
|
||||||
|
colorAccentBackground: '#f5f5f5',
|
||||||
|
pageBackground: 'url(your-watermark.png)',
|
||||||
|
coverPageBackground: 'url(your-cover.png)',
|
||||||
|
},
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
In this function, the `createPdfFromLetterObject` method converts your invoice data into a `Buffer` containing the PDF. This `Buffer` can subsequently be saved as a file or sent over HTTP.
|
### Web Component Usage
|
||||||
|
|
||||||
### Comprehensive Example
|
For browser-based document viewing:
|
||||||
|
|
||||||
Below is an example integrating all previous steps into a single coherent script:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
async function main() {
|
import '@design.estate/dees-document/web';
|
||||||
const pdfService = await setupPdfService();
|
|
||||||
const invoiceData: ILetter = {
|
|
||||||
// Populate your invoice object
|
|
||||||
};
|
|
||||||
await generateInvoice(pdfService, invoiceData);
|
|
||||||
}
|
|
||||||
|
|
||||||
main().then(() => console.log('Invoice generation process completed.'));
|
// In your HTML/Lit template
|
||||||
|
html`
|
||||||
|
<dedocument-viewer
|
||||||
|
.letterData=${invoiceData}
|
||||||
|
.documentSettings=${documentSettings}
|
||||||
|
></dedocument-viewer>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Or render the document directly
|
||||||
|
html`
|
||||||
|
<dedocument-dedocument
|
||||||
|
.letterData=${invoiceData}
|
||||||
|
.documentSettings=${documentSettings}
|
||||||
|
></dedocument-dedocument>
|
||||||
|
`;
|
||||||
```
|
```
|
||||||
|
|
||||||
This script encompasses initializing services and generating a PDF in a streamlined, efficient workflow.
|
### Translation System
|
||||||
|
|
||||||
### Advanced Features
|
The package includes a translation system for multi-language document generation:
|
||||||
|
|
||||||
`@design.estate/dees-document` provides several advanced functionalities, enabling rich document creation:
|
|
||||||
|
|
||||||
1. **Custom Templates & Styling**
|
|
||||||
|
|
||||||
- Customize the styling through CSS or using inline styles in TypeScript.
|
|
||||||
- Templates can be adjusted to present different document types (e.g., contracts, reports).
|
|
||||||
|
|
||||||
2. **Modular Components and Reuse**
|
|
||||||
|
|
||||||
- Utilize modular components to create reusable parts across different documents, enhancing maintainability and reducing redundancy.
|
|
||||||
|
|
||||||
3. **Interactive Documents**
|
|
||||||
|
|
||||||
- Integrate interactivities like forms, buttons, and interactive charts within your documents.
|
|
||||||
|
|
||||||
4. **Localization Support**
|
|
||||||
|
|
||||||
- Documents can be localized to support multiple languages, enhancing accessibility and usability.
|
|
||||||
|
|
||||||
5. **Responsive and Adaptive Designs**
|
|
||||||
|
|
||||||
- Create documents that adjust layout dynamically depending on print or digital medium, maintaining consistency across platforms.
|
|
||||||
|
|
||||||
6. **Security Features**
|
|
||||||
|
|
||||||
- Apply digital signatures and encrypt sensitive documents to ensure secure and authentic document distribution.
|
|
||||||
|
|
||||||
7. **Complex Business Logic**
|
|
||||||
- Implement complex logic for feature-rich documents such as inventory systems, automatic tax calculations, or real-time pricing adjustments.
|
|
||||||
|
|
||||||
### Utilize Translation for Localization
|
|
||||||
|
|
||||||
The package offers a translation mechanism to adapt your documents for international clients:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { translate } from '@design.estate/dees-document/shared';
|
import { translate } from '@design.estate/dees-document/shared';
|
||||||
|
|
||||||
const languageCode: 'DE' | 'EN' | 'ES' = 'EN'; // Set required language code
|
// Translate document labels
|
||||||
console.log(translate(languageCode, 'invoice', 'Invoice')); // Translated output
|
translate('DE', 'invoice@@totalGross'); // "Gesamtbetrag (Brutto)"
|
||||||
|
translate('EN', 'invoice@@totalGross'); // "Total (Gross)"
|
||||||
|
translate('ES', 'invoice@@totalGross'); // "Total (Bruto)"
|
||||||
```
|
```
|
||||||
|
|
||||||
Different language settings can be applied, enabling the seamless presentation of document contents tailored to regional and linguistic preferences.
|
## Module Exports
|
||||||
|
|
||||||
### Conclusion
|
The package provides multiple entry points:
|
||||||
|
|
||||||
The `@design.estate/dees-document` package is a versatile and powerful solution for businesses seeking to automate and streamline their document generation processes using modern web technologies. By integrating seamlessly with TypeScript, it offers robust capabilities for creating complex, customizable, and interactive documents that can meet a variety of business needs.
|
```typescript
|
||||||
|
// Node.js server-side (PDF generation)
|
||||||
|
import { PdfService } from '@design.estate/dees-document';
|
||||||
|
import { PdfService } from '@design.estate/dees-document/node';
|
||||||
|
|
||||||
From custom templates to localization and advanced security features, this module provides the flexibility and power to create polished and professional documents efficiently. Whether you're generating invoices, contracts, or detailed reports, `@design.estate/dees-document` has the tools to support your enterprise workflows with ease and precision.
|
// Browser web components
|
||||||
|
import '@design.estate/dees-document/web';
|
||||||
|
|
||||||
|
// Shared utilities and types
|
||||||
|
import { translate, IDocumentSettings } from '@design.estate/dees-document/shared';
|
||||||
|
|
||||||
|
// TypeScript interfaces
|
||||||
|
import type { IDocumentSettings } from '@design.estate/dees-document/interfaces';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Document Components
|
||||||
|
|
||||||
|
The framework includes these web components:
|
||||||
|
|
||||||
|
| Component | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `<dedocument-dedocument>` | Main document container with pagination |
|
||||||
|
| `<dedocument-viewer>` | Document viewer with controls |
|
||||||
|
| `<dedocument-page>` | Single A4 page with scaling support |
|
||||||
|
| `<dedocument-pageheader>` | Company logo and branding header |
|
||||||
|
| `<dedocument-letterheader>` | Sender/recipient addresses |
|
||||||
|
| `<dedocument-pagecontent>` | Main content area |
|
||||||
|
| `<dedocument-pagefooter>` | Company info and page numbers |
|
||||||
|
| `<dedocument-contentinvoice>` | Invoice-specific content layout |
|
||||||
|
| `<dedocument-paymentcode>` | EPC QR code for SEPA payments |
|
||||||
|
|
||||||
|
## Draft vs Final Documents
|
||||||
|
|
||||||
|
Control document version display:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const invoice = {
|
||||||
|
// ... other fields
|
||||||
|
versionInfo: {
|
||||||
|
type: 'draft', // Shows watermark "DRAFT" across pages
|
||||||
|
version: '0.1.0',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// For final documents
|
||||||
|
const invoice = {
|
||||||
|
// ... other fields
|
||||||
|
versionInfo: {
|
||||||
|
type: 'final', // Clean document without watermark
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
## License and Legal Information
|
## License and Legal Information
|
||||||
|
|
||||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||||
|
|
||||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
### Trademarks
|
### Trademarks
|
||||||
|
|
||||||
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
|
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
||||||
|
|
||||||
|
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||||
|
|
||||||
### Company Information
|
### Company Information
|
||||||
|
|
||||||
Task Venture Capital GmbH
|
Task Venture Capital GmbH
|
||||||
Registered at District court Bremen HRB 35230 HB, Germany
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||||
|
|
||||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import * as tsclass from '@tsclass/tsclass';
|
import * as tsclass from '@tsclass/tsclass';
|
||||||
import * as smartfile from '@push.rocks/smartfile';
|
import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
export { tsclass, smartfile, path };
|
export const smartfs = new SmartFs(new SmartFsProviderNode());
|
||||||
|
|
||||||
|
export { tsclass, path };
|
||||||
|
|||||||
182
test/test.ts
182
test/test.ts
@@ -1,120 +1,106 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from "./plugins.js";
|
||||||
import * as paths from './paths.js';
|
import * as paths from "./paths.js";
|
||||||
import * as interfaces from '../ts_shared/interfaces/index.js';
|
import * as interfaces from "../ts_shared/interfaces/index.js";
|
||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from "@git.zone/tstest/tapbundle";
|
||||||
import * as deesDocumentServer from '../ts/index.js';
|
import * as deesDocumentServer from "../ts/index.js";
|
||||||
|
|
||||||
let testPdfServiceInstance: deesDocumentServer.PdfService;
|
let testPdfServiceInstance: deesDocumentServer.PdfService;
|
||||||
const testLetterData: plugins.tsclass.business.ILetter = {
|
const testLetterData: plugins.tsclass.finance.TInvoice = {
|
||||||
accentColor: null,
|
type: "invoice",
|
||||||
type: 'invoice',
|
invoiceType: "debitnote",
|
||||||
date: null,
|
date: null,
|
||||||
needsCoverSheet: true,
|
|
||||||
objectActions: [],
|
objectActions: [],
|
||||||
pdf: null,
|
pdf: null,
|
||||||
content: {
|
id: "XX-CLIENT-48765",
|
||||||
invoiceData: {
|
invoiceId: "XX-CLIENT-48765",
|
||||||
id: 'XX-CLIENT-48765',
|
|
||||||
reverseCharge: true,
|
reverseCharge: true,
|
||||||
dueInDays: 30,
|
dueInDays: 30,
|
||||||
currency: 'EUR',
|
currency: "EUR",
|
||||||
notes: [],
|
notes: [],
|
||||||
type: 'debitnote',
|
|
||||||
billedBy: {
|
|
||||||
address: null,
|
|
||||||
description: null,
|
|
||||||
name: 'Some Service GmbH',
|
|
||||||
type: null,
|
|
||||||
customerNumber: null,
|
|
||||||
email: null,
|
|
||||||
facebookUrl: null,
|
|
||||||
fax: null,
|
|
||||||
legalEntity: null,
|
|
||||||
sepaConnection: {
|
|
||||||
bic: 'BPOTBEB1',
|
|
||||||
iban: 'BE72000000001616',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
billedTo: null,
|
|
||||||
status: null,
|
status: null,
|
||||||
deliveryDate: new Date().getTime(),
|
deliveryDate: new Date().getTime(),
|
||||||
periodOfPerformance: null,
|
periodOfPerformance: null,
|
||||||
printResult: null,
|
printResult: null,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Website Creation',
|
name: "Website Creation",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 1200,
|
unitNetPrice: 1200,
|
||||||
unitType: 'item',
|
unitType: "item",
|
||||||
vatPercentage: 0,
|
vatPercentage: 0,
|
||||||
position: 1,
|
position: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
|
||||||
contractData: {
|
|
||||||
contractDate: Date.now(),
|
|
||||||
id: 'LL-CONTRACT-48765',
|
|
||||||
},
|
|
||||||
textData: [],
|
|
||||||
timesheetData: '',
|
|
||||||
},
|
|
||||||
from: {
|
from: {
|
||||||
name: 'PdfService Test Company',
|
name: "PdfService Test Company",
|
||||||
type: 'company',
|
type: "company",
|
||||||
description: 'doing pdf stuff',
|
status: "active",
|
||||||
|
foundedDate: { day: 1, month: 1, year: 2025 },
|
||||||
|
description: "doing pdf stuff",
|
||||||
address: {
|
address: {
|
||||||
streetName: 'Awesome Street',
|
streetName: "Awesome Street",
|
||||||
houseNumber: '5',
|
houseNumber: "5",
|
||||||
city: 'Bremen',
|
city: "Bremen",
|
||||||
country: 'Germany',
|
country: "Germany",
|
||||||
postalCode: '28359',
|
postalCode: "28359",
|
||||||
},
|
},
|
||||||
sepaConnection: {
|
sepaConnection: {
|
||||||
bic: 'BPOTBEB1',
|
bic: "BPOTBEB1",
|
||||||
iban: 'BE72000000001616',
|
iban: "BE72000000001616",
|
||||||
|
},
|
||||||
|
registrationDetails: {
|
||||||
|
vatId: "",
|
||||||
|
registrationName: "",
|
||||||
|
registrationId: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
name: 'Awesome To Company',
|
name: "Awesome To Company",
|
||||||
type: 'company',
|
type: "company",
|
||||||
description: 'a company that does stuff',
|
status: "active",
|
||||||
|
foundedDate: { day: 1, month: 1, year: 2025 },
|
||||||
|
description: "a company that does stuff",
|
||||||
address: {
|
address: {
|
||||||
streetName: 'Awesome Street',
|
streetName: "Awesome Street",
|
||||||
houseNumber: '5',
|
houseNumber: "5",
|
||||||
city: 'Bremen',
|
city: "Bremen",
|
||||||
country: 'Germany',
|
country: "Germany",
|
||||||
postalCode: '28359',
|
postalCode: "28359",
|
||||||
|
},
|
||||||
|
registrationDetails: {
|
||||||
|
vatId: "",
|
||||||
|
registrationName: "",
|
||||||
|
registrationId: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
incidenceId: null,
|
incidenceId: null,
|
||||||
language: null,
|
language: null,
|
||||||
legalContact: null,
|
legalContact: null,
|
||||||
logoUrl: null,
|
|
||||||
pdfAttachments: null,
|
pdfAttachments: null,
|
||||||
subject: 'Invoice XX-CLIENT-48765',
|
subject: "Invoice XX-CLIENT-48765",
|
||||||
versionInfo: {
|
versionInfo: {
|
||||||
type: 'final',
|
type: "final",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
tap.test('should create a document from an invoice', async () => {
|
tap.test("should create a document from an invoice", async () => {
|
||||||
testPdfServiceInstance = new deesDocumentServer.PdfService({});
|
testPdfServiceInstance = new deesDocumentServer.PdfService({});
|
||||||
await testPdfServiceInstance.start();
|
await testPdfServiceInstance.start();
|
||||||
expect(testPdfServiceInstance).toBeInstanceOf(deesDocumentServer.PdfService);
|
expect(testPdfServiceInstance).toBeInstanceOf(deesDocumentServer.PdfService);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should create an invoice', async () => {
|
tap.test("should create an invoice", async () => {
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
const saveResult = async (optionsArg: {
|
const saveResult = async (optionsArg: {
|
||||||
letterData: plugins.tsclass.business.ILetter;
|
letterData: plugins.tsclass.finance.TInvoice;
|
||||||
documentSettings: interfaces.IDocumentSettings;
|
documentSettings: interfaces.IDocumentSettings;
|
||||||
}) => {
|
}) => {
|
||||||
const pdfResult = await testPdfServiceInstance.createPdfFromLetterObject(optionsArg);
|
const pdfResult = await testPdfServiceInstance.createPdfFromLetterObject(
|
||||||
await plugins.smartfile.memory.toFs(
|
optionsArg
|
||||||
Buffer.from(pdfResult.buffer),
|
|
||||||
plugins.path.join(paths.nogitDir, `test-${counter++}.pdf`),
|
|
||||||
);
|
);
|
||||||
|
await plugins.smartfs.file(plugins.path.join(paths.nogitDir, `test-${counter++}.pdf`))
|
||||||
|
.write(Buffer.from(pdfResult.buffer));
|
||||||
};
|
};
|
||||||
await saveResult({
|
await saveResult({
|
||||||
letterData: testLetterData,
|
letterData: testLetterData,
|
||||||
@@ -124,106 +110,106 @@ tap.test('should create an invoice', async () => {
|
|||||||
letterData: {
|
letterData: {
|
||||||
...testLetterData,
|
...testLetterData,
|
||||||
versionInfo: {
|
versionInfo: {
|
||||||
type: 'draft',
|
type: "draft",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
documentSettings: {},
|
documentSettings: {},
|
||||||
});
|
});
|
||||||
(testLetterData.content.invoiceData.items = [
|
(testLetterData.items = [
|
||||||
{
|
{
|
||||||
name: 'Website Creation',
|
name: "Website Creation",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 1200,
|
unitNetPrice: 1200,
|
||||||
unitType: 'item',
|
unitType: "item",
|
||||||
vatPercentage: 0,
|
vatPercentage: 0,
|
||||||
position: 1,
|
position: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Hosting',
|
name: "Hosting",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 1200,
|
unitNetPrice: 1200,
|
||||||
unitType: 'item',
|
unitType: "item",
|
||||||
vatPercentage: 19,
|
vatPercentage: 19,
|
||||||
position: 2,
|
position: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Overnight Shipping',
|
name: "Overnight Shipping",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 1200,
|
unitNetPrice: 1200,
|
||||||
unitType: 'item',
|
unitType: "item",
|
||||||
vatPercentage: 24,
|
vatPercentage: 24,
|
||||||
position: 3,
|
position: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Website Creation',
|
name: "Website Creation",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 1200,
|
unitNetPrice: 1200,
|
||||||
unitType: 'item',
|
unitType: "item",
|
||||||
vatPercentage: 0,
|
vatPercentage: 0,
|
||||||
position: 4,
|
position: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Hosting',
|
name: "Hosting",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 1200,
|
unitNetPrice: 1200,
|
||||||
unitType: 'item',
|
unitType: "item",
|
||||||
vatPercentage: 19,
|
vatPercentage: 19,
|
||||||
position: 5,
|
position: 5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Overnight Shipping',
|
name: "Overnight Shipping",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 1200,
|
unitNetPrice: 1200,
|
||||||
unitType: 'item',
|
unitType: "item",
|
||||||
vatPercentage: 24,
|
vatPercentage: 24,
|
||||||
position: 6,
|
position: 6,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Website Creation',
|
name: "Website Creation",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 1200,
|
unitNetPrice: 1200,
|
||||||
unitType: 'item',
|
unitType: "item",
|
||||||
vatPercentage: 0,
|
vatPercentage: 0,
|
||||||
position: 7,
|
position: 7,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Hosting',
|
name: "Hosting",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 1200,
|
unitNetPrice: 1200,
|
||||||
unitType: 'item',
|
unitType: "item",
|
||||||
vatPercentage: 19,
|
vatPercentage: 19,
|
||||||
position: 8,
|
position: 8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Overnight Shipping',
|
name: "Overnight Shipping",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 1200,
|
unitNetPrice: 1200,
|
||||||
unitType: 'item',
|
unitType: "item",
|
||||||
vatPercentage: 24,
|
vatPercentage: 24,
|
||||||
position: 9,
|
position: 9,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Website Creation',
|
name: "Website Creation",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 1200,
|
unitNetPrice: 1200,
|
||||||
unitType: 'item',
|
unitType: "item",
|
||||||
vatPercentage: 0,
|
vatPercentage: 0,
|
||||||
position: 10,
|
position: 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Hosting',
|
name: "Hosting",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 1200,
|
unitNetPrice: 1200,
|
||||||
unitType: 'item',
|
unitType: "item",
|
||||||
vatPercentage: 19,
|
vatPercentage: 19,
|
||||||
position: 11,
|
position: 11,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Overnight Shipping',
|
name: "Overnight Shipping",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 1200,
|
unitNetPrice: 1200,
|
||||||
unitType: 'item',
|
unitType: "item",
|
||||||
vatPercentage: 24,
|
vatPercentage: 24,
|
||||||
position: 12,
|
position: 12,
|
||||||
},
|
},
|
||||||
@@ -234,8 +220,8 @@ tap.test('should create an invoice', async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should stop the service', async () => {
|
tap.test("should stop the service", async () => {
|
||||||
await testPdfServiceInstance.stop();
|
await testPdfServiceInstance.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.start();
|
export default tap.start();
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-document',
|
name: '@design.estate/dees-document',
|
||||||
version: '1.6.10',
|
version: '2.0.2',
|
||||||
description: 'A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.'
|
description: 'A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from "./plugins.js";
|
||||||
import * as helpers from './helpers.js';
|
import * as helpers from "./helpers.js";
|
||||||
|
|
||||||
export interface IPdfServiceConstructorOptions {}
|
export interface IPdfServiceConstructorOptions {}
|
||||||
|
|
||||||
@@ -8,7 +8,9 @@ export interface IPdfServiceConstructorOptions {}
|
|||||||
*/
|
*/
|
||||||
export class PdfService {
|
export class PdfService {
|
||||||
// STATIC
|
// STATIC
|
||||||
public static async createAndStart(optionsArg: IPdfServiceConstructorOptions) {
|
public static async createAndStart(
|
||||||
|
optionsArg: IPdfServiceConstructorOptions
|
||||||
|
) {
|
||||||
const pdfService = new PdfService(optionsArg);
|
const pdfService = new PdfService(optionsArg);
|
||||||
await pdfService.start();
|
await pdfService.start();
|
||||||
return pdfService;
|
return pdfService;
|
||||||
@@ -42,17 +44,23 @@ export class PdfService {
|
|||||||
* creates an letter
|
* creates an letter
|
||||||
*/
|
*/
|
||||||
public async createPdfFromLetterObject(optionsArg: {
|
public async createPdfFromLetterObject(optionsArg: {
|
||||||
letterData: plugins.tsclass.business.ILetter;
|
letterData: plugins.tsclass.business.TLetter;
|
||||||
documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
||||||
}) {
|
}): Promise<plugins.smartpdf.IPdf> {
|
||||||
const html = `
|
const html = `
|
||||||
<script type="module">
|
<script type="module">
|
||||||
${await helpers.getBundleAsString()}
|
${await helpers.getBundleAsString()}
|
||||||
</script>
|
</script>
|
||||||
<dedocument-dedocument printMode documentSettings="${plugins.smartjson.stringifyBase64(optionsArg.documentSettings)}" letterData="${plugins.smartjson.stringifyBase64(optionsArg.letterData)}"></dedocument-dedocument>
|
<dedocument-dedocument printMode documentSettings="${plugins.smartjson.stringifyBase64(
|
||||||
|
optionsArg.documentSettings
|
||||||
|
)}" letterData="${plugins.smartjson.stringifyBase64(
|
||||||
|
optionsArg.letterData
|
||||||
|
)}"></dedocument-dedocument>
|
||||||
`;
|
`;
|
||||||
// console.log(html);
|
// console.log(html);
|
||||||
const pdfResult = await this.smartpdfInstance.getA4PdfResultForHtmlString(html);
|
const pdfResult = await this.smartpdfInstance.getA4PdfResultForHtmlString(
|
||||||
|
html
|
||||||
|
);
|
||||||
return pdfResult;
|
return pdfResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ import * as plugins from './plugins.js';
|
|||||||
import * as paths from './paths.js';
|
import * as paths from './paths.js';
|
||||||
|
|
||||||
export const getBundleAsString = async () => {
|
export const getBundleAsString = async () => {
|
||||||
return plugins.smartfile.fs.toStringSync(paths.bundleFile);
|
return plugins.smartfs.file(paths.bundleFile).encoding('utf8').read();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ export const packageDir = plugins.path.join(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const nogitDir = plugins.path.join(packageDir, '.nogit/');
|
export const nogitDir = plugins.path.join(packageDir, '.nogit/');
|
||||||
plugins.smartfile.fs.ensureDirSync(nogitDir);
|
if (!(await plugins.smartfs.directory(nogitDir).exists())) {
|
||||||
|
await plugins.smartfs.directory(nogitDir).create();
|
||||||
|
}
|
||||||
|
|
||||||
export const bundleDir = plugins.path.join(packageDir, './dist_bundle');
|
export const bundleDir = plugins.path.join(packageDir, './dist_bundle');
|
||||||
export const bundleFile = plugins.path.join(bundleDir, './bundle.js');
|
export const bundleFile = plugins.path.join(bundleDir, './bundle.js');
|
||||||
|
|||||||
@@ -10,10 +10,13 @@ export { shared };
|
|||||||
|
|
||||||
// @push.rocks/scope
|
// @push.rocks/scope
|
||||||
import * as smartfile from '@push.rocks/smartfile';
|
import * as smartfile from '@push.rocks/smartfile';
|
||||||
|
import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs';
|
||||||
import * as smartjson from '@push.rocks/smartjson';
|
import * as smartjson from '@push.rocks/smartjson';
|
||||||
import * as smartpath from '@push.rocks/smartpath';
|
import * as smartpath from '@push.rocks/smartpath';
|
||||||
import * as smartpdf from '@push.rocks/smartpdf';
|
import * as smartpdf from '@push.rocks/smartpdf';
|
||||||
|
|
||||||
|
export const smartfs = new SmartFs(new SmartFsProviderNode());
|
||||||
|
|
||||||
export { smartfile, smartpath, smartjson, smartpdf };
|
export { smartfile, smartpath, smartjson, smartpdf };
|
||||||
|
|
||||||
// @tsclass scope
|
// @tsclass scope
|
||||||
|
|||||||
@@ -1,219 +1,248 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from "./plugins.js";
|
||||||
import * as interfaces from './interfaces/index.js';
|
import * as interfaces from "./interfaces/index.js";
|
||||||
|
|
||||||
const fromContact: plugins.tsclass.business.IContact = {
|
const fromContact: plugins.tsclass.business.TContact = {
|
||||||
name: 'Awesome From Company',
|
name: "Awesome From Company",
|
||||||
type: 'company',
|
type: "company",
|
||||||
description: 'a company that does stuff',
|
status: "active",
|
||||||
|
foundedDate: { day: 1, month: 1, year: 2025 },
|
||||||
|
description: "a company that does stuff",
|
||||||
address: {
|
address: {
|
||||||
streetName: 'Awesome Street',
|
streetName: "Awesome Street",
|
||||||
houseNumber: '5',
|
houseNumber: "5",
|
||||||
city: 'Bremen',
|
city: "Bremen",
|
||||||
country: 'Germany',
|
country: "Germany",
|
||||||
postalCode: '28359',
|
postalCode: "28359",
|
||||||
},
|
},
|
||||||
vatId: 'DE12345678',
|
|
||||||
sepaConnection: {
|
sepaConnection: {
|
||||||
bic: 'BPOTBEB1',
|
bic: "BPOTBEB1",
|
||||||
iban: 'BE01234567891616'
|
iban: "BE01234567891616",
|
||||||
|
},
|
||||||
|
email: "hello@awesome.company",
|
||||||
|
phone: "+49 421 1234567",
|
||||||
|
fax: "+49 421 1234568",
|
||||||
|
registrationDetails: {
|
||||||
|
registrationId: "HRB 35230 HB",
|
||||||
|
registrationName: "Amtsgericht Bremen",
|
||||||
|
vatId: "DE12345678",
|
||||||
},
|
},
|
||||||
email: 'hello@awesome.company',
|
|
||||||
phone: '+49 421 1234567',
|
|
||||||
fax: '+49 421 1234568',
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toContact: plugins.tsclass.business.IContact = {
|
const toContact: plugins.tsclass.business.TContact = {
|
||||||
name: 'Awesome To GmbH',
|
name: "Awesome To GmbH",
|
||||||
type: 'company',
|
type: "company",
|
||||||
customerNumber: 'LL-CLIENT-123',
|
status: "active",
|
||||||
description: 'a company that does stuff',
|
foundedDate: { day: 1, month: 1, year: 2025 },
|
||||||
|
customerNumber: "LL-CLIENT-123",
|
||||||
|
description: "a company that does stuff",
|
||||||
address: {
|
address: {
|
||||||
streetName: 'Awesome Street',
|
streetName: "Awesome Street",
|
||||||
houseNumber: '5',
|
houseNumber: "5",
|
||||||
city: 'Bremen',
|
city: "Bremen",
|
||||||
country: 'Germany',
|
country: "Germany",
|
||||||
postalCode: '28359'
|
postalCode: "28359",
|
||||||
},
|
},
|
||||||
vatId: 'BE12345678',
|
registrationDetails: {
|
||||||
}
|
registrationId: "HRB 35230 HB",
|
||||||
|
registrationName: "Amtsgericht Bremen",
|
||||||
|
vatId: "DE12345678",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const demoLetter: plugins.tsclass.business.ILetter = {
|
export const demoLetter: plugins.tsclass.finance.TInvoice = {
|
||||||
|
type: "accounting-doc",
|
||||||
|
accountingDocType: "invoice",
|
||||||
|
accountingDocId: "LL-INV-48765",
|
||||||
|
accountingDocStatus: "draft",
|
||||||
|
id: "LL-INV-48765",
|
||||||
versionInfo: {
|
versionInfo: {
|
||||||
type: 'draft',
|
version: "1.0.0",
|
||||||
version: '1.0.0',
|
type: "draft",
|
||||||
},
|
},
|
||||||
accentColor: null,
|
language: "de",
|
||||||
content: {
|
date: Date.now(),
|
||||||
textData: null,
|
incidenceId: "LL-INV-48765",
|
||||||
timesheetData: null,
|
subject: "LL-INV-48765",
|
||||||
contractData: {
|
|
||||||
contractDate: Date.now(),
|
|
||||||
id: 'someid'
|
|
||||||
},
|
|
||||||
invoiceData: {
|
|
||||||
id: 'LL-INV-48765',
|
|
||||||
reverseCharge: true,
|
reverseCharge: true,
|
||||||
dueInDays: 30,
|
dueInDays: 30,
|
||||||
billedBy: fromContact,
|
from: fromContact,
|
||||||
billedTo: toContact,
|
to: toContact,
|
||||||
status: null,
|
status: null,
|
||||||
deliveryDate: new Date().getTime(),
|
deliveryDate: new Date().getTime(),
|
||||||
periodOfPerformance: null,
|
periodOfPerformance: {
|
||||||
|
from: +new Date().setDate(new Date().getDate() - 7),
|
||||||
|
to: +new Date(),
|
||||||
|
},
|
||||||
printResult: null,
|
printResult: null,
|
||||||
currency: 'EUR',
|
currency: "EUR",
|
||||||
notes: [],
|
notes: [],
|
||||||
type: 'debitnote',
|
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'Item with 19% VAT',
|
name: "Item with 19% VAT",
|
||||||
unitQuantity: 2,
|
unitQuantity: 2,
|
||||||
unitNetPrice: 100,
|
unitNetPrice: 100,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 19,
|
vatPercentage: 19,
|
||||||
position: 0,
|
position: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 7% VAT',
|
name: "Item with 7% VAT",
|
||||||
unitQuantity: 4,
|
unitQuantity: 4,
|
||||||
unitNetPrice: 100,
|
unitNetPrice: 100,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 7,
|
vatPercentage: 7,
|
||||||
position: 1,
|
position: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 7% VAT',
|
name: "Item with 7% VAT",
|
||||||
unitQuantity: 3,
|
unitQuantity: 3,
|
||||||
unitNetPrice: 230,
|
unitNetPrice: 230,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 7,
|
vatPercentage: 7,
|
||||||
position: 2,
|
position: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 21% VAT',
|
name: "Item with 21% VAT",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 230,
|
unitNetPrice: 230,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 21,
|
vatPercentage: 21,
|
||||||
position: 3,
|
position: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 0% VAT',
|
name: "Item with 0% VAT",
|
||||||
unitQuantity: 6,
|
unitQuantity: 6,
|
||||||
unitNetPrice: 230,
|
unitNetPrice: 230,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 0,
|
vatPercentage: 0,
|
||||||
position: 4,
|
position: 4,
|
||||||
},{
|
},
|
||||||
name: 'Item with 19% VAT',
|
{
|
||||||
|
name: "Item with 19% VAT",
|
||||||
unitQuantity: 8,
|
unitQuantity: 8,
|
||||||
unitNetPrice: 100,
|
unitNetPrice: 100,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 19,
|
vatPercentage: 19,
|
||||||
position: 5,
|
position: 5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 7% VAT',
|
name: "Item with 7% VAT",
|
||||||
unitQuantity: 9,
|
unitQuantity: 9,
|
||||||
unitNetPrice: 100,
|
unitNetPrice: 100,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 7,
|
vatPercentage: 7,
|
||||||
position: 6,
|
position: 6,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 7% VAT',
|
name: "Item with 7% VAT",
|
||||||
unitQuantity: 4,
|
unitQuantity: 4,
|
||||||
unitNetPrice: 230,
|
unitNetPrice: 230,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 7,
|
vatPercentage: 7,
|
||||||
position: 8,
|
position: 8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 21% VAT',
|
name: "Item with 21% VAT",
|
||||||
unitQuantity: 3,
|
unitQuantity: 3,
|
||||||
unitNetPrice: 230,
|
unitNetPrice: 230,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 21,
|
vatPercentage: 21,
|
||||||
position: 9,
|
position: 9,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 0% VAT',
|
name: "Item with 0% VAT",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 230,
|
unitNetPrice: 230,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 0,
|
vatPercentage: 0,
|
||||||
position: 10,
|
position: 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 0% VAT',
|
name: "Item with 0% VAT",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 230,
|
unitNetPrice: 230,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 0,
|
vatPercentage: 0,
|
||||||
position: 10,
|
position: 11,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 0% VAT',
|
name: "Item with 0% VAT",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 230,
|
unitNetPrice: 230,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 0,
|
vatPercentage: 0,
|
||||||
position: 10,
|
position: 12,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 0% VAT',
|
name: "Item with 0% VAT",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 230,
|
unitNetPrice: 230,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 0,
|
vatPercentage: 0,
|
||||||
position: 10,
|
position: 13,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 0% VAT',
|
name: "Item with 0% VAT",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 230,
|
unitNetPrice: 230,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 0,
|
vatPercentage: 0,
|
||||||
position: 10,
|
position: 14,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 0% VAT',
|
name: "Item with 0% VAT",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 230,
|
unitNetPrice: 230,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 0,
|
vatPercentage: 0,
|
||||||
position: 10,
|
position: 15,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item with 0% VAT',
|
name: "Item with 0% VAT",
|
||||||
unitQuantity: 1,
|
unitQuantity: 1,
|
||||||
unitNetPrice: 230,
|
unitNetPrice: 230,
|
||||||
unitType: 'hours',
|
unitType: "hours",
|
||||||
vatPercentage: 0,
|
vatPercentage: 0,
|
||||||
position: 10,
|
position: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Item with 0% VAT",
|
||||||
|
unitQuantity: 1,
|
||||||
|
unitNetPrice: 230,
|
||||||
|
unitType: "hours",
|
||||||
|
vatPercentage: 0,
|
||||||
|
position: 17,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Item with 0% VAT",
|
||||||
|
unitQuantity: 1,
|
||||||
|
unitNetPrice: 230,
|
||||||
|
unitType: "hours",
|
||||||
|
vatPercentage: 0,
|
||||||
|
position: 18,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Item with 0% VAT",
|
||||||
|
unitQuantity: 1,
|
||||||
|
unitNetPrice: 230,
|
||||||
|
unitType: "hours",
|
||||||
|
vatPercentage: 0,
|
||||||
|
position: 19,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Item with 0% VAT",
|
||||||
|
unitQuantity: 1,
|
||||||
|
unitNetPrice: 230,
|
||||||
|
unitType: "hours",
|
||||||
|
vatPercentage: 0,
|
||||||
|
position: 20,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
},
|
|
||||||
|
|
||||||
date: Date.now(),
|
|
||||||
type: 'invoice',
|
|
||||||
needsCoverSheet: false,
|
|
||||||
objectActions: [],
|
|
||||||
pdf: null,
|
|
||||||
from: fromContact,
|
|
||||||
to: toContact,
|
|
||||||
incidenceId: null,
|
|
||||||
language: null,
|
|
||||||
legalContact: null,
|
|
||||||
logoUrl: null,
|
|
||||||
pdfAttachments: null,
|
|
||||||
subject: 'Invoice: LL-INV-48765',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const demoDocumentSettings: interfaces.IDocumentSettings = {
|
export const demoDocumentSettings: interfaces.IDocumentSettings = {
|
||||||
enableTopDraftText: true,
|
enableTopDraftText: true,
|
||||||
enableDefaultHeader: true,
|
enableDefaultHeader: true,
|
||||||
enableDefaultFooter: true,
|
enableDefaultFooter: true,
|
||||||
languageCode: 'DE',
|
languageCode: "DE",
|
||||||
};
|
};
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
export const a4Height = 1122;
|
const DPI = 96 / 2.54; // <PX> / <INCH>
|
||||||
export const a4Width = 794;
|
export const A4_HEIGHT = cmToPx(29.7); // DPI * 29.7cm
|
||||||
export const rightMargin = 70;
|
export const A4_WIDTH = cmToPx(21); // DPI * 21cm
|
||||||
export const leftMargin = 90;
|
|
||||||
|
|
||||||
import * as interfaces from './interfaces/index.js';
|
export function cmToPx(value: number): number {
|
||||||
|
return DPI * value;
|
||||||
|
}
|
||||||
|
|
||||||
|
import * as interfaces from "./interfaces/index.js";
|
||||||
export { interfaces };
|
export { interfaces };
|
||||||
|
|
||||||
import * as translation from './translation.js';
|
import * as translation from "./translation.js";
|
||||||
export { translation };
|
export { translation };
|
||||||
|
|
||||||
export * from './demoletter.js';
|
export * from "./demoletter.js";
|
||||||
|
|||||||
@@ -1,9 +1,23 @@
|
|||||||
import * as translation from '../translation.js';
|
import * as translation from "../translation.js";
|
||||||
|
|
||||||
|
export interface IDocumentTheme {
|
||||||
|
colorPrimaryForeground?: string;
|
||||||
|
colorPrimaryBackground?: string;
|
||||||
|
colorAccentForeground?: string;
|
||||||
|
colorAccentBackground?: string;
|
||||||
|
fontFamily?: string;
|
||||||
|
pageBackground?: string;
|
||||||
|
coverPageBackground?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IDocumentSettings {
|
export interface IDocumentSettings {
|
||||||
enableTopDraftText?: boolean;
|
enableTopDraftText?: boolean;
|
||||||
enableDefaultHeader?: boolean;
|
enableDefaultHeader?: boolean;
|
||||||
enableDefaultFooter?: boolean;
|
enableDefaultFooter?: boolean;
|
||||||
languageCode?: translation.TLanguageCode;
|
enableFoldMarks?: boolean;
|
||||||
|
enableInvoiceContractRefSection?: boolean;
|
||||||
|
languageCode?: translation.LanguageCode;
|
||||||
|
dateStyle?: Intl.DateTimeFormatOptions["dateStyle"];
|
||||||
vatGroupPositions?: boolean;
|
vatGroupPositions?: boolean;
|
||||||
|
theme?: IDocumentTheme;
|
||||||
}
|
}
|
||||||
@@ -1,143 +1,200 @@
|
|||||||
import * as interfaces from './interfaces/index.js';
|
|
||||||
|
|
||||||
// Define English translations without enforcing TTranslationImplementation yet
|
// Define English translations without enforcing TTranslationImplementation yet
|
||||||
export const EN_translations = {
|
export const EN_translations = {
|
||||||
address: 'Address',
|
address: "Address",
|
||||||
bankConnection: 'Bank Connection',
|
"bank.accountHolder": "beneficiary",
|
||||||
contactInfo: 'Contact Info',
|
"bank.bic": "bic",
|
||||||
description: 'Description',
|
"bank.iban": "iban",
|
||||||
invoice: 'Invoice',
|
"bank.institution": "institution",
|
||||||
itemPos: 'Item Pos.',
|
"bankConnection@@title": "Bank Connection",
|
||||||
quantity: 'Quantity',
|
"contact@@title": "Contact Info",
|
||||||
registrationInfo: 'Registration Info',
|
"customer.number": "Your Customer ID",
|
||||||
reverseVatNote: 'VAT arises on a reverse charge basis and is payable by the customer.',
|
description: "Description",
|
||||||
totalNetPrice: 'Total Net Price',
|
"empty.logo": "no logo provided",
|
||||||
unitNetPrice: 'Unit Net Price',
|
"empty.number.customer": "not registered",
|
||||||
unitType: 'Unit Type',
|
empty: "not provided",
|
||||||
yourCustomerId: 'Your Customer ID:',
|
fax: "Fax",
|
||||||
yourVatId: 'Your vat id on file:',
|
introStatement: "We hereby invoice the following products and services",
|
||||||
continuesOnPage: 'Continues on page',
|
"invoice.number": "Invoice number",
|
||||||
finalPageStatement: 'This is the final page of this document.',
|
invoice: "Invoice",
|
||||||
page: 'Page',
|
"item.position": "Pos.",
|
||||||
vatShort: 'VAT',
|
mail: "Mail",
|
||||||
} as const;
|
"overlay@@draft": "Draft",
|
||||||
|
"page.continueNext": "Continues on page",
|
||||||
|
"page.final": "This is the final page of this document.",
|
||||||
|
page: "Page",
|
||||||
|
pageOf: "of",
|
||||||
|
"payment.qr": "Pay via QR code",
|
||||||
|
"payment.qr.description": "Scan the QR code with you banking app",
|
||||||
|
"payment.terms": "Payment Terms",
|
||||||
|
"payment.terms.direct": "Without deduction until",
|
||||||
|
"periodOfPerformance.day": "Delivery Date",
|
||||||
|
"periodOfPerformance.range": "Delivery Period",
|
||||||
|
phone: "Phone",
|
||||||
|
"price.total.net": "Total Net Price",
|
||||||
|
"price.unit.net": "Unit Net Price",
|
||||||
|
price: "Price",
|
||||||
|
quantity: "Quantity",
|
||||||
|
referencedContract: "Referenced contract",
|
||||||
|
"referencedContract.text":
|
||||||
|
"This invoice is adhering to agreements made by contract between the parties on",
|
||||||
|
"registration.label": "Registration Info",
|
||||||
|
subject: "Subject",
|
||||||
|
sum: "Sum",
|
||||||
|
totalGross: "Total gross",
|
||||||
|
"unit.type": "Unit Type",
|
||||||
|
"vat.position": "on item positions",
|
||||||
|
"vat.reverseCharge.note":
|
||||||
|
"VAT arises on a reverse charge basis and is payable by the customer.",
|
||||||
|
"vat.short": "VAT",
|
||||||
|
"vat.yourId": "Your vat id on file",
|
||||||
|
vat: "Valued Added Tax",
|
||||||
|
};
|
||||||
|
|
||||||
// Infer keys of EN_translations
|
// Infer keys of EN_translations
|
||||||
export type TTranslationKey = keyof typeof EN_translations;
|
|
||||||
|
/**
|
||||||
|
* For example:
|
||||||
|
* - price
|
||||||
|
*/
|
||||||
|
type RawTranslationKeys = keyof typeof EN_translations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For example:
|
||||||
|
* - price.item
|
||||||
|
* - price.sum
|
||||||
|
* - price.unit
|
||||||
|
* - vat.yourId
|
||||||
|
*/
|
||||||
|
type NestedTranslationKeys =
|
||||||
|
| RawTranslationKeys
|
||||||
|
| `${RawTranslationKeys}.${string}`
|
||||||
|
| `${RawTranslationKeys}.${string}.${string}`
|
||||||
|
| `${RawTranslationKeys}.${string}.${string}.${string}`
|
||||||
|
| `${RawTranslationKeys}.${string}.${string}.${string}.${string}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For example:
|
||||||
|
* - contact@@mail
|
||||||
|
* - vat = 'VAT'
|
||||||
|
* - vat.yourId = 'your vat id'
|
||||||
|
* - footer@@vat.yourId = 'your vat id'
|
||||||
|
* - header@@vat.yourId = 'THIS IS YOUR VAT'
|
||||||
|
*/
|
||||||
|
type LocationBasedTranslationKeys = `${string}@@${NestedTranslationKeys}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mix of everything
|
||||||
|
*/
|
||||||
|
export type TranslationKey =
|
||||||
|
| NestedTranslationKeys
|
||||||
|
| LocationBasedTranslationKeys;
|
||||||
|
|
||||||
// Define the type for all translations based on EN_translations keys
|
// Define the type for all translations based on EN_translations keys
|
||||||
export type TTranslationImplementation = {
|
export type Dictionary = {
|
||||||
[key in TTranslationKey]: string;
|
[key in TranslationKey]: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define German translations
|
// Define German translations
|
||||||
export const DE_translations: TTranslationImplementation = {
|
export const DE_translations: Dictionary = {
|
||||||
address: 'Adresse',
|
address: "Adresse",
|
||||||
bankConnection: 'Bankverbindung',
|
"bank.accountHolder": "Kontoinhaber",
|
||||||
contactInfo: 'Kontaktinformationen',
|
"bank.bic": "BIC",
|
||||||
description: 'Beschreibung',
|
"bank.iban": "IBAN",
|
||||||
invoice: 'Rechnung',
|
"bank.institution": "Bankinstitut",
|
||||||
itemPos: 'Pos.',
|
"bankConnection@@title": "Bankverbindung",
|
||||||
quantity: 'Anzahl',
|
"contact@@title": "Kontaktinformationen",
|
||||||
registrationInfo: 'HRA/HRB Info',
|
"customer.number": "Ihre Kundennummer",
|
||||||
reverseVatNote:
|
description: "Beschreibung",
|
||||||
'Umkehr der Umsatzsteuerpflicht: Der Rechnungsempfänger ist für die korrekte Abrechnung der Umsatzsteuer zuständig.',
|
"empty.logo": "Kein Logo gesetzt",
|
||||||
totalNetPrice: 'Summe netto',
|
"empty.number.customer": "nicht registriert",
|
||||||
unitNetPrice: 'Einheit netto',
|
empty: "nicht angegeben",
|
||||||
unitType: 'Einheit',
|
fax: "Fax",
|
||||||
yourCustomerId: 'Ihre Kundennummer:',
|
introStatement:
|
||||||
yourVatId: 'Ihre Umsatzsteuer-ID:',
|
"Wir stellen Ihnen hiermit folgende Produkte und Dienstleistungen in Rechnung",
|
||||||
continuesOnPage: 'Fortsetzung auf Seite',
|
"invoice.number": "Rechnungsnr.",
|
||||||
finalPageStatement: 'Dies ist die letzte Seite dieses Dokuments.',
|
invoice: "Rechnung",
|
||||||
page: 'Seite',
|
"item.position": "Pos.",
|
||||||
vatShort: 'USt',
|
mail: "E-Mail",
|
||||||
};
|
"overlay@@draft": "Entwurf",
|
||||||
|
"page.continueNext": "Fortsetzung auf Seite",
|
||||||
// Define Spanish translations
|
"page.final": "Dies ist die letzte Seite dieses Dokuments.",
|
||||||
export const ES_translations: TTranslationImplementation = {
|
page: "Seite",
|
||||||
address: 'Dirección',
|
pageOf: "von",
|
||||||
bankConnection: 'Conexión bancaria',
|
"payment.qr": "Überweisen per QR-Code",
|
||||||
contactInfo: 'Información de contacto',
|
"payment.qr.description":
|
||||||
description: 'Descripción',
|
"Den QR-Code einfach mit der Banking-App einscannen",
|
||||||
invoice: 'Factura',
|
"payment.terms": "Zahlungsbedingungen",
|
||||||
itemPos: 'Pos.',
|
"payment.terms.direct": "Ohne Abzug bis zum",
|
||||||
quantity: 'Cantidad',
|
"periodOfPerformance.day": "Lieferdatum",
|
||||||
registrationInfo: 'Información de registro',
|
"periodOfPerformance.range": "Lieferzeitraum",
|
||||||
reverseVatNote: 'El IVA se aplica por inversión del sujeto pasivo y debe ser pagado por el cliente.',
|
phone: "Telefon",
|
||||||
totalNetPrice: 'Precio total neto',
|
"price.total.net": "Gesamtpreis",
|
||||||
unitNetPrice: 'Precio unitario neto',
|
"price.unit.net": "Stückpreis",
|
||||||
unitType: 'Tipo de unidad',
|
price: "Preis",
|
||||||
yourCustomerId: 'Su número de cliente:',
|
quantity: "Menge",
|
||||||
yourVatId: 'Su ID de IVA:',
|
referencedContract: "Referenzierter Vertrag",
|
||||||
continuesOnPage: 'Continúa en la página',
|
"referencedContract.text":
|
||||||
finalPageStatement: 'Esta es la última página de este documento.',
|
"Diese Rechnung bezieht sich auf die getroffenen Vertragsvereinbarungen vom",
|
||||||
page: 'Página',
|
"registration.label": "Registrierungsinfo",
|
||||||
vatShort: 'IVA',
|
subject: "Betreff",
|
||||||
};
|
sum: "Summe",
|
||||||
|
totalGross: "Gesamtbetrag brutto",
|
||||||
// Define French translations
|
"unit.type": "Einheit",
|
||||||
export const FR_translations: TTranslationImplementation = {
|
"vat.position": "auf Positionen",
|
||||||
address: 'Adresse',
|
"vat.reverseCharge.note":
|
||||||
bankConnection: 'Coordonnées bancaires',
|
"Die Umsatzsteuer entsteht im Reverse-Charge-Verfahren und ist vom Kunden zu zahlen.",
|
||||||
contactInfo: 'Informations de contact',
|
"vat.short": "MwSt.",
|
||||||
description: 'Description',
|
"vat.yourId": "Ihre hinterlegte USt-Id",
|
||||||
invoice: 'Facture',
|
vat: "Umsatzsteuer",
|
||||||
itemPos: 'Position',
|
|
||||||
quantity: 'Quantité',
|
|
||||||
registrationInfo: "Informations d'enregistrement",
|
|
||||||
reverseVatNote:
|
|
||||||
"La TVA s'applique selon le mécanisme d'autoliquidation et est à payer par le client.",
|
|
||||||
totalNetPrice: 'Prix net total',
|
|
||||||
unitNetPrice: 'Prix unitaire net',
|
|
||||||
unitType: "Type d'unité",
|
|
||||||
yourCustomerId: 'Votre numéro de client :',
|
|
||||||
yourVatId: 'Votre numéro de TVA :',
|
|
||||||
continuesOnPage: 'Continue sur la page',
|
|
||||||
finalPageStatement: 'Ceci est la dernière page de ce document.',
|
|
||||||
page: 'Page',
|
|
||||||
vatShort: 'TVA',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Define Italian translations
|
|
||||||
export const IT_translations: TTranslationImplementation = {
|
|
||||||
address: 'Indirizzo',
|
|
||||||
bankConnection: 'Coordinate bancarie',
|
|
||||||
contactInfo: 'Informazioni di contatto',
|
|
||||||
description: 'Descrizione',
|
|
||||||
invoice: 'Fattura',
|
|
||||||
itemPos: 'Pos.',
|
|
||||||
quantity: 'Quantità',
|
|
||||||
registrationInfo: 'Informazioni di registrazione',
|
|
||||||
reverseVatNote: "L'IVA è applicata con inversione contabile ed è a carico del cliente.",
|
|
||||||
totalNetPrice: 'Prezzo netto totale',
|
|
||||||
unitNetPrice: 'Prezzo netto unitario',
|
|
||||||
unitType: 'Tipo di unità',
|
|
||||||
yourCustomerId: 'Il tuo numero cliente:',
|
|
||||||
yourVatId: 'Il tuo numero di partita IVA:',
|
|
||||||
continuesOnPage: 'Continua alla pagina',
|
|
||||||
finalPageStatement: 'Questa è l\'ultima pagina di questo documento.',
|
|
||||||
page: 'Pagina',
|
|
||||||
vatShort: 'IVA',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Language Code Map
|
// Language Code Map
|
||||||
export const languageCodeMap: Record<string, TTranslationImplementation> = {
|
export const languageCodeMap: Record<string, Dictionary> = {
|
||||||
EN: EN_translations,
|
EN: EN_translations,
|
||||||
DE: DE_translations,
|
DE: DE_translations,
|
||||||
ES: ES_translations,
|
|
||||||
FR: FR_translations,
|
|
||||||
IT: IT_translations,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Language Code Type
|
// Language Code Type
|
||||||
export type TLanguageCode = keyof typeof languageCodeMap;
|
export type LanguageCode = keyof typeof languageCodeMap;
|
||||||
|
|
||||||
|
function* getTranslationKeyHierarchy(
|
||||||
|
key: TranslationKey
|
||||||
|
): Generator<TranslationKey, TranslationKey> {
|
||||||
|
yield key;
|
||||||
|
|
||||||
|
const areaSplit = key.split("@@") as [TranslationKey, TranslationKey];
|
||||||
|
let rest = areaSplit[1];
|
||||||
|
|
||||||
|
if (rest) {
|
||||||
|
yield rest;
|
||||||
|
} else {
|
||||||
|
rest = areaSplit[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rest.includes(".")) return;
|
||||||
|
|
||||||
|
const parts = rest.split(".");
|
||||||
|
for (let i = parts.length - 1; i > 0; i--) {
|
||||||
|
yield parts.slice(0, i).join(".") as TranslationKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Translate Function
|
// Translate Function
|
||||||
export const translate = (
|
export const translate = (
|
||||||
languageCode: TLanguageCode,
|
languageCode: LanguageCode,
|
||||||
key: TTranslationKey,
|
key: TranslationKey
|
||||||
defaultValue: string
|
|
||||||
): string => {
|
): string => {
|
||||||
const translations = languageCodeMap[languageCode] || EN_translations;
|
const dictionary = languageCodeMap[languageCode] || EN_translations;
|
||||||
return translations[key] || defaultValue;
|
const lookupHierarchy = getTranslationKeyHierarchy(key);
|
||||||
|
|
||||||
|
let found: string;
|
||||||
|
|
||||||
|
for (let keyOption of lookupHierarchy) {
|
||||||
|
found = dictionary[keyOption] || EN_translations[keyOption];
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found;
|
||||||
};
|
};
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-document',
|
name: '@design.estate/dees-document',
|
||||||
version: '1.6.10',
|
version: '2.0.2',
|
||||||
description: 'A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.'
|
description: 'A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,22 +9,21 @@ import {
|
|||||||
customElement,
|
customElement,
|
||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
css,
|
css,
|
||||||
cssManager,
|
|
||||||
unsafeCSS,
|
|
||||||
render,
|
render,
|
||||||
domtools,
|
domtools,
|
||||||
} from '@design.estate/dees-element';
|
} from "@design.estate/dees-element";
|
||||||
import * as plugins from '../plugins.js';
|
import * as plugins from "../plugins.js";
|
||||||
|
|
||||||
import { dedocumentSharedStyle } from '../style.js';
|
import { dedocumentSharedStyle } from "../style.js";
|
||||||
|
import type { TranslationKey } from "ts_shared/translation.js";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'dedocument-contentinvoice': DeContentInvoice;
|
"dedocument-contentinvoice": DeContentInvoice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement('dedocument-contentinvoice')
|
@customElement("dedocument-contentinvoice")
|
||||||
export class DeContentInvoice extends DeesElement {
|
export class DeContentInvoice extends DeesElement {
|
||||||
public static demo = () => html`
|
public static demo = () => html`
|
||||||
<style>
|
<style>
|
||||||
@@ -42,13 +41,13 @@ export class DeContentInvoice extends DeesElement {
|
|||||||
type: Object,
|
type: Object,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
public letterData: plugins.tsclass.business.ILetter;
|
accessor letterData: plugins.tsclass.finance.TInvoice;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Object,
|
type: Object,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
public documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -59,169 +58,33 @@ export class DeContentInvoice extends DeesElement {
|
|||||||
domtools.elementBasic.staticStyles,
|
domtools.elementBasic.staticStyles,
|
||||||
dedocumentSharedStyle,
|
dedocumentSharedStyle,
|
||||||
css`
|
css`
|
||||||
:host {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trimmedContent {
|
.trimmedContent {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repeatedContent {
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
|
|
||||||
public render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<div class="trimmedContent"></div>
|
|
||||||
<div class="repeatedContent"></div>
|
|
||||||
<div class="currentContent"></div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTotalNet = (): number => {
|
|
||||||
let totalNet = 0;
|
|
||||||
|
|
||||||
if (!this.letterData) {
|
|
||||||
return totalNet;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const item of this.letterData.content.invoiceData.items) {
|
|
||||||
totalNet += item.unitNetPrice * item.unitQuantity;
|
|
||||||
}
|
|
||||||
return totalNet;
|
|
||||||
};
|
|
||||||
|
|
||||||
public getTotalGross = (): number => {
|
|
||||||
let totalVat = 0;
|
|
||||||
|
|
||||||
if (!this.letterData) {
|
|
||||||
return totalVat;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const taxgroup of this.getVatGroups()) {
|
|
||||||
totalVat += taxgroup.vatAmountSum;
|
|
||||||
}
|
|
||||||
return this.getTotalNet() + totalVat;
|
|
||||||
};
|
|
||||||
|
|
||||||
public getVatGroups = () => {
|
|
||||||
const vatGroups: {
|
|
||||||
vatPercentage: number;
|
|
||||||
items: plugins.tsclass.finance.IInvoice['items'];
|
|
||||||
vatAmountSum: number;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
if (!this.letterData) {
|
|
||||||
return vatGroups;
|
|
||||||
}
|
|
||||||
|
|
||||||
const taxAmounts: number[] = [];
|
|
||||||
for (const item of this.letterData.content.invoiceData.items) {
|
|
||||||
taxAmounts.includes(item.vatPercentage) ? null : taxAmounts.push(item.vatPercentage);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const taxAmount of taxAmounts) {
|
|
||||||
const matchingItems = this.letterData.content.invoiceData.items.filter(
|
|
||||||
(itemArg) => itemArg.vatPercentage === taxAmount
|
|
||||||
);
|
|
||||||
let sum = 0;
|
|
||||||
for (const matchingItem of matchingItems) {
|
|
||||||
sum += matchingItem.unitNetPrice * matchingItem.unitQuantity * (taxAmount / 100);
|
|
||||||
}
|
|
||||||
vatGroups.push({
|
|
||||||
items: matchingItems,
|
|
||||||
vatPercentage: taxAmount,
|
|
||||||
vatAmountSum: Math.round(sum * 100) / 100,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return vatGroups;
|
|
||||||
};
|
|
||||||
|
|
||||||
public async getContentNodes() {
|
|
||||||
await this.elementDomReady;
|
|
||||||
return {
|
|
||||||
currentContent: this.shadowRoot.querySelector('.currentContent') as HTMLElement,
|
|
||||||
trimmedContent: this.shadowRoot.querySelector('.trimmedContent') as HTMLElement,
|
|
||||||
repeatedContent: this.shadowRoot.querySelector('.repeatedContent') as HTMLElement,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getContentLength() {
|
|
||||||
await this.elementDomReady;
|
|
||||||
return (await this.getContentNodes()).currentContent.children.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async trimEndByOne() {
|
|
||||||
await this.elementDomReady;
|
|
||||||
this.shadowRoot
|
|
||||||
.querySelector('.trimmedContent')
|
|
||||||
.append(
|
|
||||||
(await this.getContentNodes()).currentContent.children.item(
|
|
||||||
(await this.getContentNodes()).currentContent.children.length - 1
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async trimStartToOffset(contentOffsetArg: number) {
|
|
||||||
await this.elementDomReady;
|
|
||||||
const beginningLength = (await this.getContentNodes()).currentContent.children.length;
|
|
||||||
while (
|
|
||||||
(await this.getContentNodes()).currentContent.children.length !==
|
|
||||||
beginningLength - contentOffsetArg
|
|
||||||
) {
|
|
||||||
(await this.getContentNodes()).trimmedContent.append(
|
|
||||||
(await this.getContentNodes()).currentContent.children.item(0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(await this.getContentNodes()).currentContent.children
|
|
||||||
.item(0)
|
|
||||||
.classList.contains('needsDataHeader')
|
|
||||||
) {
|
|
||||||
const trimmedContent = (await this.getContentNodes()).trimmedContent;
|
|
||||||
let startPoint = trimmedContent.children.length;
|
|
||||||
while (startPoint > 0) {
|
|
||||||
const element = trimmedContent.children.item(startPoint - 1);
|
|
||||||
if (element.classList.contains('dataHeader')) {
|
|
||||||
(await this.getContentNodes()).repeatedContent.append(element);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
startPoint--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
|
|
||||||
super.firstUpdated(_changedProperties);
|
|
||||||
this.attachInvoiceDom();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async attachInvoiceDom() {
|
|
||||||
const contentNodes = await this.getContentNodes();
|
|
||||||
render(
|
|
||||||
html`
|
|
||||||
<style>
|
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 40px auto 60px 60px 84px 84px 46px;
|
grid-template-columns: 40px auto 50px 50px 100px 50px 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topLine {
|
.topLine {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
background: #e7e7e7;
|
background: #e7e7e7;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lineItem {
|
.lineItem {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-right: 1px dashed #ccc;
|
border-right: 1px dashed #ccc;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lineItem:last-child {
|
.lineItem:last-child {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.value.rightAlign,
|
||||||
.lineItem.rightAlign {
|
.lineItem.rightAlign {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
@@ -247,7 +110,7 @@ export class DeContentInvoice extends DeesElement {
|
|||||||
.sums .sumline {
|
.sums .sumline {
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 90px;
|
grid-template-columns: auto 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sums .sumline .label {
|
.sums .sumline .label {
|
||||||
@@ -259,6 +122,9 @@ export class DeContentInvoice extends DeesElement {
|
|||||||
|
|
||||||
.sums .sumline .value {
|
.sums .sumline .value {
|
||||||
padding: 2px 5px;
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sums .sumline .value--total {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,193 +141,349 @@ export class DeContentInvoice extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.infoBox {
|
.infoBox {
|
||||||
border-radius: 7px;
|
margin-top: 22px;
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-left: 3px solid #c5e1a5;
|
|
||||||
padding: 8px;
|
|
||||||
margin-top: 16px;
|
|
||||||
line-height: 1.4em;
|
line-height: 1.4em;
|
||||||
font-size: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoBox .label {
|
.infoBox .label {
|
||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
.paymentCode {
|
public render(): TemplateResult {
|
||||||
text-align: center;
|
return html`
|
||||||
border-left: 2px solid #666;
|
<div class="trimmedContent"></div>
|
||||||
|
<div class="repeatedContent"></div>
|
||||||
|
<div class="currentContent"></div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
<div>We hereby invoice products and services provided to you by Lossless GmbH:</div>
|
protected formatPrice(
|
||||||
|
value: number,
|
||||||
|
currency = "EUR",
|
||||||
|
lang = "de-DE"
|
||||||
|
): string {
|
||||||
|
return new Intl.NumberFormat(lang, {
|
||||||
|
style: "currency",
|
||||||
|
currency,
|
||||||
|
}).format(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTotalNet = (): number => {
|
||||||
|
let totalNet = 0;
|
||||||
|
|
||||||
|
if (!this.letterData) {
|
||||||
|
return totalNet;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of this.letterData.items) {
|
||||||
|
totalNet += item.unitNetPrice * item.unitQuantity;
|
||||||
|
}
|
||||||
|
return totalNet;
|
||||||
|
};
|
||||||
|
|
||||||
|
public getTotalGross = (): number => {
|
||||||
|
let totalVat = 0;
|
||||||
|
|
||||||
|
if (!this.letterData) {
|
||||||
|
return totalVat;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const taxgroup of this.getVatGroups()) {
|
||||||
|
totalVat += taxgroup.vatAmountSum;
|
||||||
|
}
|
||||||
|
return this.getTotalNet() + totalVat;
|
||||||
|
};
|
||||||
|
|
||||||
|
public getVatGroups = () => {
|
||||||
|
const vatGroups: {
|
||||||
|
vatPercentage: number;
|
||||||
|
items: plugins.tsclass.finance.TInvoice["items"];
|
||||||
|
vatAmountSum: number;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
if (!this.letterData) {
|
||||||
|
return vatGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
const taxAmounts: number[] = [];
|
||||||
|
for (const item of this.letterData.items) {
|
||||||
|
taxAmounts.includes(item.vatPercentage)
|
||||||
|
? null
|
||||||
|
: taxAmounts.push(item.vatPercentage);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const taxAmount of taxAmounts) {
|
||||||
|
const matchingItems = this.letterData.items.filter(
|
||||||
|
(itemArg) => itemArg.vatPercentage === taxAmount
|
||||||
|
);
|
||||||
|
let sum = 0;
|
||||||
|
for (const matchingItem of matchingItems) {
|
||||||
|
sum +=
|
||||||
|
matchingItem.unitNetPrice *
|
||||||
|
matchingItem.unitQuantity *
|
||||||
|
(taxAmount / 100);
|
||||||
|
}
|
||||||
|
vatGroups.push({
|
||||||
|
items: matchingItems,
|
||||||
|
vatPercentage: taxAmount,
|
||||||
|
vatAmountSum: Math.round(sum * 100) / 100,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return vatGroups.sort((a, b) => b.vatPercentage - a.vatPercentage);
|
||||||
|
};
|
||||||
|
|
||||||
|
public async getContentNodes() {
|
||||||
|
await this.elementDomReady;
|
||||||
|
return {
|
||||||
|
currentContent: this.shadowRoot.querySelector(
|
||||||
|
".currentContent"
|
||||||
|
) as HTMLElement,
|
||||||
|
trimmedContent: this.shadowRoot.querySelector(
|
||||||
|
".trimmedContent"
|
||||||
|
) as HTMLElement,
|
||||||
|
repeatedContent: this.shadowRoot.querySelector(
|
||||||
|
".repeatedContent"
|
||||||
|
) as HTMLElement,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getContentLength() {
|
||||||
|
await this.elementDomReady;
|
||||||
|
return (await this.getContentNodes()).currentContent.children.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async trimEndByOne() {
|
||||||
|
await this.elementDomReady;
|
||||||
|
this.shadowRoot
|
||||||
|
.querySelector(".trimmedContent")
|
||||||
|
.append(
|
||||||
|
(await this.getContentNodes()).currentContent.children.item(
|
||||||
|
(await this.getContentNodes()).currentContent.children.length - 1
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async trimStartToOffset(contentOffsetArg: number) {
|
||||||
|
await this.elementDomReady;
|
||||||
|
const beginningLength = (await this.getContentNodes()).currentContent
|
||||||
|
.children.length;
|
||||||
|
while (
|
||||||
|
(await this.getContentNodes()).currentContent.children.length !==
|
||||||
|
beginningLength - contentOffsetArg
|
||||||
|
) {
|
||||||
|
(await this.getContentNodes()).trimmedContent.append(
|
||||||
|
(await this.getContentNodes()).currentContent.children.item(0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(await this.getContentNodes()).currentContent.children
|
||||||
|
.item(0)
|
||||||
|
.classList.contains("needsDataHeader")
|
||||||
|
) {
|
||||||
|
const trimmedContent = (await this.getContentNodes()).trimmedContent;
|
||||||
|
let startPoint = trimmedContent.children.length;
|
||||||
|
while (startPoint > 0) {
|
||||||
|
const element = trimmedContent.children.item(startPoint - 1);
|
||||||
|
if (element.classList.contains("dataHeader")) {
|
||||||
|
(await this.getContentNodes()).repeatedContent.append(element);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
startPoint--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public translateKey(key: TranslationKey): string {
|
||||||
|
return plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async firstUpdated(
|
||||||
|
_changedProperties: Map<string | number | symbol, unknown>
|
||||||
|
) {
|
||||||
|
super.firstUpdated(_changedProperties);
|
||||||
|
this.attachInvoiceDom();
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderPaymentTerms(): TemplateResult {
|
||||||
|
return html`<div class="infoBox">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div class="label">
|
||||||
|
${this.translateKey("invoice@@payment.terms")}
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
${this.translateKey("invoice@@payment.terms.direct")}
|
||||||
|
${new Intl.DateTimeFormat(this.documentSettings.languageCode, {
|
||||||
|
dateStyle: this.documentSettings.dateStyle,
|
||||||
|
}).format(
|
||||||
|
new Date(this.letterData.date).setDate(
|
||||||
|
new Date(this.letterData.date).getDate() +
|
||||||
|
this.letterData?.dueInDays
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderPaymentInfo(): TemplateResult {
|
||||||
|
const bic = this.letterData?.from.sepaConnection.bic;
|
||||||
|
const name = this.letterData?.from.name;
|
||||||
|
const iban = this.letterData?.from.sepaConnection.iban;
|
||||||
|
const currency = this.letterData?.currency;
|
||||||
|
const totalGross = this.getTotalGross();
|
||||||
|
const reference = this.letterData?.id;
|
||||||
|
|
||||||
|
return html`<div class="infoBox">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div class="label">${this.translateKey("invoice@@payment.qr")}</div>
|
||||||
|
<span> ${this.translateKey("invoice@@payment.qr.description")} </span>
|
||||||
|
</div>
|
||||||
|
<dedocument-paymentcode
|
||||||
|
bic="${bic}"
|
||||||
|
name="${name}"
|
||||||
|
iban="${iban}"
|
||||||
|
currency="${currency}"
|
||||||
|
totalGross="${totalGross}"
|
||||||
|
reference="${reference}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderReferencedContract(): TemplateResult {
|
||||||
|
return null;
|
||||||
|
// return this.documentSettings.enableInvoiceContractRefSection &&
|
||||||
|
// this.invoiceData?.content?.contractData?.contractDate
|
||||||
|
// ? html`
|
||||||
|
// <div class="infoBox">
|
||||||
|
// <div class="label">
|
||||||
|
// ${this.translateKey("invoice@@referencedContract")}
|
||||||
|
// </div>
|
||||||
|
// ${this.translateKey("invoice@@referencedContract.text")}
|
||||||
|
// ${new Intl.DateTimeFormat(this.documentSettings.languageCode, {
|
||||||
|
// dateStyle: this.documentSettings.dateStyle,
|
||||||
|
// }).format(
|
||||||
|
// new Date(this.invoiceData?.content.contractData.contractDate)
|
||||||
|
// )}.
|
||||||
|
// </div>
|
||||||
|
// `
|
||||||
|
// : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async attachInvoiceDom() {
|
||||||
|
const contentNodes = await this.getContentNodes();
|
||||||
|
render(
|
||||||
|
html`
|
||||||
|
<div>${this.translateKey("invoice@@introStatement")}</div>
|
||||||
<div class="grid topLine dataHeader">
|
<div class="grid topLine dataHeader">
|
||||||
<div class="lineItem rightAlign">
|
<div class="lineItem rightAlign">
|
||||||
${plugins.shared.translation.translate(
|
${this.translateKey("invoice@@item.position")}
|
||||||
this.documentSettings.languageCode,
|
|
||||||
'itemPos',
|
|
||||||
'Item Pos.'
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="lineItem">
|
<div class="lineItem">
|
||||||
${plugins.shared.translation.translate(
|
${this.translateKey("invoice@@description")}
|
||||||
this.documentSettings.languageCode,
|
|
||||||
'description',
|
|
||||||
'Description'
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="lineItem rightAlign">
|
<div class="lineItem rightAlign">
|
||||||
${plugins.shared.translation.translate(
|
${this.translateKey("invoice@@quantity")}
|
||||||
this.documentSettings.languageCode,
|
|
||||||
'quantity',
|
|
||||||
'Quantity'
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="lineItem">
|
<div class="lineItem">${this.translateKey("invoice@@unit.type")}</div>
|
||||||
${plugins.shared.translation.translate(
|
<div class="lineItem rightAlign">
|
||||||
this.documentSettings.languageCode,
|
${this.translateKey("invoice@@price.unit.net")}
|
||||||
'unitType',
|
|
||||||
'Unit Type'
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="lineItem rightAlign">
|
<div class="lineItem rightAlign">
|
||||||
${plugins.shared.translation.translate(
|
${this.translateKey("invoice@@vat.short")}
|
||||||
this.documentSettings.languageCode,
|
|
||||||
'unitNetPrice',
|
|
||||||
'Unit Net Price'
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="lineItem rightAlign">
|
<div class="lineItem rightAlign">
|
||||||
${plugins.shared.translation.translate(
|
${this.translateKey("invoice@@price.total.net")}
|
||||||
this.documentSettings.languageCode,
|
|
||||||
'totalNetPrice',
|
|
||||||
'Total Net Price'
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="lineItem rightAlign">
|
|
||||||
${plugins.shared.translation.translate(
|
|
||||||
this.documentSettings.languageCode,
|
|
||||||
'vatShort',
|
|
||||||
'VAT'
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${(() => {
|
${this.letterData?.items?.map(
|
||||||
let counter = 1;
|
(invoiceItem, index) => html`
|
||||||
return this.letterData?.content.invoiceData?.items?.map((invoiceItem) => {
|
<div class="grid needsDataHeader">
|
||||||
const isHighlighted = false; // TODO: implement rest of highlight logic
|
<div class="lineItem rightAlign">${index + 1}</div>
|
||||||
return html`
|
|
||||||
<div class="grid invoiceLine needsDataHeader ${isHighlighted ? 'highlighted' : ''}">
|
|
||||||
<div class="lineItem rightAlign">${counter++}</div>
|
|
||||||
<div class="lineItem">${invoiceItem.name}</div>
|
<div class="lineItem">${invoiceItem.name}</div>
|
||||||
<div class="lineItem rightAlign">${invoiceItem.unitQuantity}</div>
|
<div class="lineItem rightAlign">${invoiceItem.unitQuantity}</div>
|
||||||
<div class="lineItem">${invoiceItem.unitType}</div>
|
<div class="lineItem">${invoiceItem.unitType}</div>
|
||||||
<div class="lineItem rightAlign">
|
<div class="lineItem rightAlign">
|
||||||
${invoiceItem.unitNetPrice} ${this.letterData?.content.invoiceData.currency}
|
${this.formatPrice(invoiceItem.unitNetPrice)}
|
||||||
</div>
|
</div>
|
||||||
<div class="lineItem rightAlign">
|
<div class="lineItem rightAlign">
|
||||||
${invoiceItem.unitQuantity * invoiceItem.unitNetPrice}
|
${invoiceItem.vatPercentage}%
|
||||||
${this.letterData?.content.invoiceData.currency}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="lineItem rightAlign">${invoiceItem.vatPercentage}%</div>
|
<div class="lineItem rightAlign">
|
||||||
|
${this.formatPrice(
|
||||||
|
invoiceItem.unitQuantity * invoiceItem.unitNetPrice
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
`;
|
</div>
|
||||||
});
|
`
|
||||||
})()}
|
)}
|
||||||
<div class="sums">
|
<div class="sums">
|
||||||
<div class="sumline">
|
<div class="sumline">
|
||||||
<div class="label">Total net</div>
|
<div class="label">
|
||||||
<div class="value">${this.getTotalNet()} EUR</div>
|
${this.translateKey("invoice@@sum.total.net")}
|
||||||
|
</div>
|
||||||
|
<div class="value value--total rightAlign">
|
||||||
|
${this.formatPrice(this.getTotalNet())}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${this.getVatGroups().map((vatGroupArg) => {
|
${this.getVatGroups().map((vatGroupArg) => {
|
||||||
let itemNumbers = '';
|
let itemNumbers = vatGroupArg.items
|
||||||
let first = true;
|
.map((item) => this.letterData.items.indexOf(item) + 1)
|
||||||
for (const item of vatGroupArg.items) {
|
.join(", ");
|
||||||
const itemIndex = this.letterData.content.invoiceData.items.indexOf(item);
|
|
||||||
itemNumbers += `${first ? '' : ', '}${itemIndex + 1}`;
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
return html`
|
return html`
|
||||||
<div class="sumline">
|
<div class="sumline">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
Vat ${vatGroupArg.vatPercentage}%
|
${this.translateKey("vat.short")}
|
||||||
|
${vatGroupArg.vatPercentage}%
|
||||||
${this.documentSettings.vatGroupPositions
|
${this.documentSettings.vatGroupPositions
|
||||||
? html`
|
? html`
|
||||||
<br /><span style="font-weight: normal"
|
<br /><span style="font-weight: normal"
|
||||||
>(on item positions: ${itemNumbers})</span
|
>(${this.translateKey("invoice@@vat.position")}:
|
||||||
|
${itemNumbers})</span
|
||||||
>
|
>
|
||||||
`
|
`
|
||||||
: html``}
|
: html``}
|
||||||
</div>
|
</div>
|
||||||
<div class="value">${vatGroupArg.vatAmountSum} EUR</div>
|
<div class="value rightAlign">
|
||||||
|
${this.formatPrice(vatGroupArg.vatAmountSum)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
<div class="sumline">
|
<div class="sumline">
|
||||||
<div class="label">Total gross</div>
|
<div class="label">${this.translateKey("invoice@@totalGross")}</div>
|
||||||
<div class="value">${this.getTotalGross()} EUR</div>
|
<div class="value value--total rightAlign">
|
||||||
|
${this.formatPrice(this.getTotalGross())}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
${this.letterData?.content.invoiceData.reverseCharge
|
|
||||||
? html`
|
${this.letterData?.reverseCharge
|
||||||
<div class="taxNote">
|
? html`<div class="taxNote">
|
||||||
${plugins.shared.translation.translate(
|
${this.translateKey("invoice@@vat.reverseCharge.note")}
|
||||||
this.documentSettings.languageCode,
|
</div>`
|
||||||
'reverseVatNote',
|
|
||||||
'VAT arises on a reverse charge basis and is payable by the customer.'
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ``}
|
: ``}
|
||||||
<div class="infoBox">
|
|
||||||
<div class="label">Payment Terms:</div>
|
<!-- REFERENCED CONTRACT -->
|
||||||
Payment is due within 30 days starting from the reception of this invoice. Please use the
|
${this.renderReferencedContract()}
|
||||||
following SEPA details:
|
|
||||||
<br /><br />
|
<!-- PAYMENT TERMS -->
|
||||||
Beneficiary: ${this.letterData?.from.name}<br />
|
${this.renderPaymentTerms()}
|
||||||
IBAN: ${this.letterData?.from?.sepaConnection?.iban}<br />
|
|
||||||
BIC: ${this.letterData?.from?.sepaConnection?.bic}<br />
|
<!-- PAYMENT INFO -->
|
||||||
Description: ${this.letterData?.content.invoiceData?.id}<br />
|
${this.renderPaymentInfo()}
|
||||||
Amount: ${this.getTotalGross()} ${this.letterData?.content.invoiceData.currency}
|
|
||||||
</div>
|
|
||||||
${this.letterData?.content?.contractData?.contractDate
|
|
||||||
? html`
|
|
||||||
<div class="infoBox">
|
|
||||||
<div class="label">Referenced contract:</div>
|
|
||||||
This invoice is adhering to agreements made by contract between the parties on
|
|
||||||
${plugins.smarttime.ExtendedDate.fromMillis(
|
|
||||||
this.letterData?.content.contractData.contractDate
|
|
||||||
).format('MMMM D, YYYY')}.
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: html``}
|
|
||||||
<div class="infoBox paymentCode">
|
|
||||||
<div class="label">Sepa Payment Code:</div>
|
|
||||||
</div>
|
|
||||||
`,
|
`,
|
||||||
contentNodes.currentContent
|
contentNodes.currentContent
|
||||||
);
|
);
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
plugins.qrcode.toCanvas(
|
|
||||||
canvas,
|
|
||||||
`BCD
|
|
||||||
001
|
|
||||||
1
|
|
||||||
SCT
|
|
||||||
${this.letterData.content.invoiceData.billedBy.sepaConnection.bic}
|
|
||||||
${this.letterData.content.invoiceData.billedBy.name}
|
|
||||||
${this.letterData.content.invoiceData.billedBy.sepaConnection.iban}
|
|
||||||
EUR${this.getTotalGross()}
|
|
||||||
CHAR
|
|
||||||
${this.letterData.content.invoiceData.id}
|
|
||||||
${this.letterData.content.invoiceData.id}
|
|
||||||
EPC QR Code`,
|
|
||||||
(error) => {
|
|
||||||
if (error) console.error(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
contentNodes.currentContent.querySelector('.paymentCode').append(canvas);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from "../plugins.js";
|
||||||
|
|
||||||
import { html } from '@design.estate/dees-element';
|
import { html } from "@design.estate/dees-element";
|
||||||
|
|
||||||
export const demoFunc = () => html`
|
export const demoFunc = () => html`
|
||||||
<dedocument-dedocument .format="${'a4'}" .letterData=${plugins.shared.demoLetter}></dedocument-dedocument>
|
<dedocument-dedocument
|
||||||
|
.format="${"a4"}"
|
||||||
|
.letterData=${plugins.shared.demoLetter}
|
||||||
|
></dedocument-dedocument>
|
||||||
`;
|
`;
|
||||||
@@ -5,34 +5,34 @@ import {
|
|||||||
customElement,
|
customElement,
|
||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
css,
|
css,
|
||||||
state,
|
|
||||||
cssManager,
|
|
||||||
unsafeCSS,
|
|
||||||
domtools,
|
domtools,
|
||||||
} from '@design.estate/dees-element';
|
} from "@design.estate/dees-element";
|
||||||
import * as plugins from '../plugins.js';
|
import * as plugins from "../plugins.js";
|
||||||
|
|
||||||
export const defaultDocumentSettings: plugins.shared.interfaces.IDocumentSettings = {
|
export const defaultDocumentSettings: plugins.shared.interfaces.IDocumentSettings =
|
||||||
|
{
|
||||||
enableTopDraftText: true,
|
enableTopDraftText: true,
|
||||||
enableDefaultHeader: true,
|
enableDefaultHeader: true,
|
||||||
enableDefaultFooter: true,
|
enableDefaultFooter: true,
|
||||||
languageCode: 'EN',
|
enableFoldMarks: true,
|
||||||
|
enableInvoiceContractRefSection: true,
|
||||||
|
languageCode: "EN",
|
||||||
vatGroupPositions: true,
|
vatGroupPositions: true,
|
||||||
};
|
dateStyle: "short",
|
||||||
|
};
|
||||||
|
|
||||||
|
import { DePage } from "./page.js";
|
||||||
|
import { DeContentInvoice } from "./contentinvoice.js";
|
||||||
|
|
||||||
import { DePage } from './page.js';
|
import { demoFunc } from "./document.demo.js";
|
||||||
import { DeContentInvoice } from './contentinvoice.js';
|
|
||||||
|
|
||||||
import { demoFunc } from './document.demo.js';
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'dedocument-dedocument': DeDocument;
|
"dedocument-dedocument": DeDocument;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement('dedocument-dedocument')
|
@customElement("dedocument-dedocument")
|
||||||
export class DeDocument extends DeesElement {
|
export class DeDocument extends DeesElement {
|
||||||
public static demo = demoFunc;
|
public static demo = demoFunc;
|
||||||
|
|
||||||
@@ -40,51 +40,52 @@ export class DeDocument extends DeesElement {
|
|||||||
type: String,
|
type: String,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
public format: 'a4' = 'a4';
|
accessor format: "a4" = "a4";
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number,
|
type: Number,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
public viewWidth: number = null;
|
accessor viewWidth: number = null;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number,
|
type: Number,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
public viewHeight: number = null;
|
accessor viewHeight: number = null;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
printMode = false;
|
accessor printMode = false;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Object,
|
type: Object,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
converter: (valueArg) => {
|
converter: (valueArg) => {
|
||||||
if (typeof valueArg === 'string') {
|
if (typeof valueArg === "string") {
|
||||||
return plugins.smartjson.parseBase64(valueArg)
|
return plugins.smartjson.parseBase64(valueArg);
|
||||||
} else {
|
} else {
|
||||||
return valueArg;
|
return valueArg;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
public letterData: plugins.tsclass.business.ILetter;
|
accessor letterData: plugins.tsclass.business.TLetter;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Object,
|
type: Object,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
converter: (valueArg) => {
|
converter: (valueArg) => {
|
||||||
if (typeof valueArg === 'string') {
|
if (typeof valueArg === "string") {
|
||||||
return plugins.smartjson.parseBase64(valueArg)
|
return plugins.smartjson.parseBase64(valueArg);
|
||||||
} else {
|
} else {
|
||||||
return valueArg;
|
return valueArg;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
public documentSettings: plugins.shared.interfaces.IDocumentSettings = defaultDocumentSettings;
|
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings =
|
||||||
|
defaultDocumentSettings;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -96,10 +97,6 @@ export class DeDocument extends DeesElement {
|
|||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
color: #333;
|
|
||||||
padding: 0px;
|
|
||||||
position: relative;
|
|
||||||
font-family: 'Dees Sans', sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.betweenPagesSpacer {
|
.betweenPagesSpacer {
|
||||||
@@ -109,41 +106,17 @@ export class DeDocument extends DeesElement {
|
|||||||
];
|
];
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html` <div class="documentContainer"></div> `;
|
||||||
<div class="documentContainer"></div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
|
public async firstUpdated(
|
||||||
|
_changedProperties: Map<string | number | symbol, unknown>
|
||||||
|
) {
|
||||||
domtools.plugins.smartdelay.delayFor(0).then(async () => {
|
domtools.plugins.smartdelay.delayFor(0).then(async () => {
|
||||||
this.documentSettings = {
|
this.documentSettings = {
|
||||||
...defaultDocumentSettings,
|
...defaultDocumentSettings,
|
||||||
...this.documentSettings,
|
...this.documentSettings,
|
||||||
}
|
};
|
||||||
|
|
||||||
while (false) {
|
|
||||||
await domtools.plugins.smartdelay.delayFor(1000);
|
|
||||||
this.letterData = {
|
|
||||||
...this.letterData,
|
|
||||||
content: {
|
|
||||||
...this.letterData.content,
|
|
||||||
invoiceData: {
|
|
||||||
...this.letterData.content.invoiceData,
|
|
||||||
items: [
|
|
||||||
...this.letterData.content.invoiceData.items,
|
|
||||||
{
|
|
||||||
name: 'Test Item',
|
|
||||||
unitQuantity: 1,
|
|
||||||
unitNetPrice: 100,
|
|
||||||
unitType: 'hours',
|
|
||||||
vatPercentage: 19,
|
|
||||||
position: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
@@ -156,16 +129,15 @@ export class DeDocument extends DeesElement {
|
|||||||
resizeObserver.observe(this);
|
resizeObserver.observe(this);
|
||||||
this.registerGarbageFunction(() => {
|
this.registerGarbageFunction(() => {
|
||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public latestDocumentSettings: plugins.shared.interfaces.IDocumentSettings = null;
|
public latestDocumentSettings: plugins.shared.interfaces.IDocumentSettings =
|
||||||
public latestRenderedLetterData: plugins.tsclass.business.ILetter = null;
|
null;
|
||||||
|
public latestRenderedLetterData: plugins.tsclass.business.TLetter = null;
|
||||||
public cleanupStore: any[] = [];
|
public cleanupStore: any[] = [];
|
||||||
|
|
||||||
|
|
||||||
public async renderDocument() {
|
public async renderDocument() {
|
||||||
|
|
||||||
this.latestDocumentSettings = this.documentSettings;
|
this.latestDocumentSettings = this.documentSettings;
|
||||||
this.latestRenderedLetterData = this.letterData;
|
this.latestRenderedLetterData = this.letterData;
|
||||||
|
|
||||||
@@ -173,7 +145,7 @@ export class DeDocument extends DeesElement {
|
|||||||
const cleanUpStoreNextRender = [];
|
const cleanUpStoreNextRender = [];
|
||||||
|
|
||||||
const domtools = await this.domtoolsPromise;
|
const domtools = await this.domtoolsPromise;
|
||||||
const documentBuildContainer = document.createElement('div');
|
const documentBuildContainer = document.createElement("div");
|
||||||
cleanUpStoreCurrentRender.push(documentBuildContainer);
|
cleanUpStoreCurrentRender.push(documentBuildContainer);
|
||||||
document.body.appendChild(documentBuildContainer);
|
document.body.appendChild(documentBuildContainer);
|
||||||
|
|
||||||
@@ -184,7 +156,8 @@ export class DeDocument extends DeesElement {
|
|||||||
// lets append the content
|
// lets append the content
|
||||||
const content: DeContentInvoice = new DeContentInvoice();
|
const content: DeContentInvoice = new DeContentInvoice();
|
||||||
cleanUpStoreCurrentRender.push(content);
|
cleanUpStoreCurrentRender.push(content);
|
||||||
content.letterData = this.letterData;
|
content.letterData = this
|
||||||
|
.letterData as unknown as plugins.tsclass.finance.TInvoice;
|
||||||
content.documentSettings = this.documentSettings;
|
content.documentSettings = this.documentSettings;
|
||||||
document.body.appendChild(content);
|
document.body.appendChild(content);
|
||||||
|
|
||||||
@@ -228,13 +201,14 @@ export class DeDocument extends DeesElement {
|
|||||||
for (const cleanUp of this.cleanupStore) {
|
for (const cleanUp of this.cleanupStore) {
|
||||||
cleanUp.remove();
|
cleanUp.remove();
|
||||||
}
|
}
|
||||||
this.cleanupStore = cleanUpStoreNextRender
|
this.cleanupStore = cleanUpStoreNextRender;
|
||||||
|
|
||||||
cleanUpStoreCurrentRender.forEach((cleanUp) => {
|
cleanUpStoreCurrentRender.forEach((cleanUp) => {
|
||||||
cleanUp.remove();
|
cleanUp.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
const documentContainer = this.shadowRoot.querySelector('.documentContainer');
|
const documentContainer =
|
||||||
|
this.shadowRoot.querySelector(".documentContainer");
|
||||||
if (documentContainer) {
|
if (documentContainer) {
|
||||||
const children = Array.from(documentContainer.children);
|
const children = Array.from(documentContainer.children);
|
||||||
children.forEach((child) => {
|
children.forEach((child) => {
|
||||||
@@ -247,24 +221,36 @@ export class DeDocument extends DeesElement {
|
|||||||
documentContainer.append(page);
|
documentContainer.append(page);
|
||||||
// betweenPagesSpacer
|
// betweenPagesSpacer
|
||||||
if (!this.printMode) {
|
if (!this.printMode) {
|
||||||
const betweenPagesSpacerDiv = document.createElement('div');
|
const betweenPagesSpacerDiv = document.createElement("div");
|
||||||
betweenPagesSpacerDiv.classList.add('betweenPagesSpacer');
|
betweenPagesSpacerDiv.classList.add("betweenPagesSpacer");
|
||||||
documentContainer.appendChild(betweenPagesSpacerDiv);
|
documentContainer.appendChild(betweenPagesSpacerDiv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.adjustDePageScaling();
|
this.adjustDePageScaling();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updated(changedProperties: Map<string | number | symbol, unknown>): Promise<void> {
|
async updated(
|
||||||
|
changedProperties: Map<string | number | symbol, unknown>
|
||||||
|
): Promise<void> {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
const domtools = await this.domtoolsPromise;
|
const domtools = await this.domtoolsPromise;
|
||||||
let renderedDocIsUpToDate = domtools.convenience.smartjson.deepEqualObjects(this.letterData, this.latestRenderedLetterData)
|
let renderedDocIsUpToDate =
|
||||||
&& domtools.convenience.smartjson.deepEqualObjects(this.documentSettings, this.latestDocumentSettings);
|
domtools.convenience.smartjson.deepEqualObjects(
|
||||||
|
this.letterData,
|
||||||
|
this.latestRenderedLetterData
|
||||||
|
) &&
|
||||||
|
domtools.convenience.smartjson.deepEqualObjects(
|
||||||
|
this.documentSettings,
|
||||||
|
this.latestDocumentSettings
|
||||||
|
);
|
||||||
if (!renderedDocIsUpToDate) {
|
if (!renderedDocIsUpToDate) {
|
||||||
this.renderDocument();
|
this.renderDocument();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedProperties.has('viewHeight') || changedProperties.has('viewWidth')) {
|
if (
|
||||||
|
changedProperties.has("viewHeight") ||
|
||||||
|
changedProperties.has("viewWidth")
|
||||||
|
) {
|
||||||
this.adjustDePageScaling();
|
this.adjustDePageScaling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,7 +261,7 @@ export class DeDocument extends DeesElement {
|
|||||||
}
|
}
|
||||||
this.viewWidth = this.clientWidth;
|
this.viewWidth = this.clientWidth;
|
||||||
// Find all DePage instances within this DeDocument
|
// Find all DePage instances within this DeDocument
|
||||||
const pages = this.shadowRoot.querySelectorAll('dedocument-page');
|
const pages = this.shadowRoot.querySelectorAll("dedocument-page");
|
||||||
|
|
||||||
// Update each DePage instance's viewHeight and viewWidth
|
// Update each DePage instance's viewHeight and viewWidth
|
||||||
pages.forEach((page: DePage) => {
|
pages.forEach((page: DePage) => {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
export * from './contentinvoice.js';
|
export * from "./contentinvoice.js";
|
||||||
export * from './document.js';
|
export * from "./document.js";
|
||||||
export * from './letterheader.js';
|
export * from "./letterheader.js";
|
||||||
export * from './page.js';
|
export * from "./page.js";
|
||||||
export * from './pagecontainer.js';
|
export * from "./pagecontainer.js";
|
||||||
export * from './pagecontent.js';
|
export * from "./pagecontent.js";
|
||||||
export * from './pagefooter.js';
|
export * from "./pagefooter.js";
|
||||||
export * from './pageheader.js';
|
export * from "./pageheader.js";
|
||||||
export * from './viewer.js';
|
export * from "./viewer.js";
|
||||||
|
export * from "./paymentcode.js";
|
||||||
|
|||||||
@@ -5,43 +5,51 @@ import {
|
|||||||
customElement,
|
customElement,
|
||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
css,
|
css,
|
||||||
cssManager,
|
|
||||||
unsafeCSS,
|
unsafeCSS,
|
||||||
domtools,
|
domtools,
|
||||||
} from '@design.estate/dees-element';
|
} from "@design.estate/dees-element";
|
||||||
|
|
||||||
import * as plugins from '../plugins.js';
|
import * as plugins from "../plugins.js";
|
||||||
import { dedocumentSharedStyle } from '../style.js';
|
import { dedocumentSharedStyle } from "../style.js";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'dedocument-letterheader': DeLetterHeader;
|
"dedocument-letterheader": DeLetterHeader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement('dedocument-letterheader')
|
@customElement("dedocument-letterheader")
|
||||||
export class DeLetterHeader extends DeesElement {
|
export class DeLetterHeader extends DeesElement {
|
||||||
public static demo = () => html`
|
public static demo = () => html`
|
||||||
<dedocument-letterheader .format="${'a4'}" .letterData=${plugins.shared.demoLetter}></dedocument-letterheader>
|
<dedocument-letterheader
|
||||||
|
.format="${"a4"}"
|
||||||
|
.letterData=${plugins.shared.demoLetter}
|
||||||
|
></dedocument-letterheader>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Object,
|
type: Object,
|
||||||
reflect: true
|
reflect: true,
|
||||||
})
|
})
|
||||||
public letterData: plugins.tsclass.business.ILetter;
|
accessor letterData: plugins.tsclass.finance.TInvoice;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number,
|
type: Number,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
public pageNumber: number = 1;
|
accessor pageNumber: number = 1;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number,
|
type: Number,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
public pageTotalNumber: number = 1;
|
accessor pageTotalNumber: number = 1;
|
||||||
|
|
||||||
|
@property({
|
||||||
|
type: Object,
|
||||||
|
reflect: true,
|
||||||
|
})
|
||||||
|
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -52,19 +60,29 @@ export class DeLetterHeader extends DeesElement {
|
|||||||
domtools.elementBasic.staticStyles,
|
domtools.elementBasic.staticStyles,
|
||||||
dedocumentSharedStyle,
|
dedocumentSharedStyle,
|
||||||
css`
|
css`
|
||||||
:host {
|
.address {
|
||||||
color: #333;
|
position: absolute;
|
||||||
|
top: calc(var(--DPI-FACTOR) * 4.5);
|
||||||
|
left: var(--LEFT-MARGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(var(--DPI-FACTOR) * 4.5);
|
||||||
|
right: var(--RIGHT-MARGIN);
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recepientInfo {
|
.recepientInfo {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
top: 200px;
|
top: calc(var(--DPI-FACTOR) * 5.5);
|
||||||
right: ${unsafeCSS(plugins.shared.rightMargin + 'px')};
|
right: var(--RIGHT-MARGIN);
|
||||||
width: 200px;
|
width: 200px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recepientInfo .label {
|
.recepientInfo .label {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
@@ -72,19 +90,6 @@ export class DeLetterHeader extends DeesElement {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date {
|
|
||||||
position: absolute;
|
|
||||||
top: 180px;
|
|
||||||
right: ${unsafeCSS(plugins.shared.rightMargin + 'px')};
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.address {
|
|
||||||
position: absolute;
|
|
||||||
top: 180px;
|
|
||||||
left: ${unsafeCSS(plugins.shared.leftMargin + 'px')};
|
|
||||||
}
|
|
||||||
|
|
||||||
.address .from {
|
.address .from {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
@@ -95,31 +100,93 @@ export class DeLetterHeader extends DeesElement {
|
|||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private renderDeliveryDate(from: Date, to: Date): TemplateResult {
|
||||||
|
if (this.letterData.accountingDocType !== "invoice") return null;
|
||||||
|
const dateFormat = new Intl.DateTimeFormat(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
{ dateStyle: this.documentSettings.dateStyle }
|
||||||
|
);
|
||||||
|
|
||||||
|
let formattedFrom = from ? dateFormat.format(from) : null;
|
||||||
|
let formattedTo = to ? dateFormat.format(to) : null;
|
||||||
|
|
||||||
|
const isSameDay = formattedFrom === formattedTo;
|
||||||
|
|
||||||
|
if (isSameDay) {
|
||||||
|
return html`<div class="label">
|
||||||
|
${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"letterhead@@periodOfPerformance.day"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span> ${formattedFrom} </span>`;
|
||||||
|
} else {
|
||||||
|
return html`<div class="label">
|
||||||
|
${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"letterhead@@periodOfPerformance.range"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span> ${formattedFrom} - ${formattedTo}</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div class="date">
|
<div class="date">
|
||||||
${new Date(this.letterData.date).getDate()}. ${new Date(this.letterData.date).toLocaleString('default', { month: 'long' })}
|
${new Intl.DateTimeFormat(this.documentSettings.languageCode, {
|
||||||
${new Date(this.letterData.date).getFullYear()}
|
dateStyle: "long",
|
||||||
|
}).format(new Date(this.letterData.date))}
|
||||||
</div>
|
</div>
|
||||||
<div class="address">
|
<div class="address">
|
||||||
<div class="from">
|
<div class="from">
|
||||||
${this.letterData.from.name}, ${this.letterData.from.address.streetName}
|
${this.letterData.from.name},
|
||||||
${this.letterData.from.address.houseNumber}, ${this.letterData.from.address.postalCode}
|
${this.letterData.from.address.streetName}
|
||||||
${this.letterData.from.address.city}, ${this.letterData.from.address.country}
|
${this.letterData.from.address.houseNumber},
|
||||||
|
${this.letterData.from.address.postalCode}
|
||||||
|
${this.letterData.from.address.city},
|
||||||
|
${this.letterData.from.address.country}
|
||||||
</div>
|
</div>
|
||||||
<div class="to">
|
<div class="to">
|
||||||
${this.letterData.to.name}<br />
|
${this.letterData.to.name}<br />
|
||||||
${this.letterData.to.address.streetName} ${this.letterData.to.address.houseNumber}<br />
|
${this.letterData.to.address.streetName}
|
||||||
${this.letterData.to.address.postalCode} ${this.letterData.to.address.city}<br />
|
${this.letterData.to.address.houseNumber}<br />
|
||||||
|
${this.letterData.to.address.postalCode}
|
||||||
|
${this.letterData.to.address.city}<br />
|
||||||
${this.letterData.from.address.country}
|
${this.letterData.from.address.country}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="recepientInfo">
|
<div class="recepientInfo">
|
||||||
<div class="label">your customer id:</div>
|
<div class="label">
|
||||||
${this.letterData.to.customerNumber || 'not registered'}
|
${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"letterhead@@customer.number"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
${this.letterData.to.customerNumber || "not registered"}
|
||||||
|
|
||||||
<div class="label">your vat id on file:</div>
|
<div class="label">
|
||||||
${this.letterData.to.vatId || 'not provided'}
|
${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"letterhead@@vat.yourId"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
${this.letterData.to.registrationDetails.vatId || "not provided"}
|
||||||
|
|
||||||
|
<!-- TODO: Make use of components -->
|
||||||
|
${this.letterData.accountingDocType === "invoice"
|
||||||
|
? html` <div class="label">
|
||||||
|
${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"letterhead@@invoice.number"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
${this.letterData.id || "not registered"}`
|
||||||
|
: null}
|
||||||
|
${this.renderDeliveryDate(
|
||||||
|
new Date(this.letterData.periodOfPerformance?.from),
|
||||||
|
new Date(this.letterData.periodOfPerformance?.to)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as tsclass from '@tsclass/tsclass';
|
import * as tsclass from "@tsclass/tsclass";
|
||||||
import {
|
import {
|
||||||
DeesElement,
|
DeesElement,
|
||||||
property,
|
property,
|
||||||
@@ -6,66 +6,66 @@ import {
|
|||||||
customElement,
|
customElement,
|
||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
css,
|
css,
|
||||||
cssManager,
|
|
||||||
unsafeCSS,
|
|
||||||
domtools,
|
domtools,
|
||||||
} from '@design.estate/dees-element';
|
} from "@design.estate/dees-element";
|
||||||
|
|
||||||
import * as plugins from '../plugins.js';
|
import * as plugins from "../plugins.js";
|
||||||
|
|
||||||
import { defaultDocumentSettings } from './document.js';
|
import { defaultDocumentSettings } from "./document.js";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'dedocument-page': DePage;
|
"dedocument-page": DePage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement('dedocument-page')
|
@customElement("dedocument-page")
|
||||||
export class DePage extends DeesElement {
|
export class DePage extends DeesElement {
|
||||||
public static demo = () => html` <dedocument-page .format="${'a4'}"></dedocument-page> `;
|
public static demo = () =>
|
||||||
|
html` <dedocument-page .format="${"a4"}"></dedocument-page> `;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number,
|
type: Number,
|
||||||
})
|
})
|
||||||
viewWidth: number = null;
|
accessor viewWidth: number = null;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number,
|
type: Number,
|
||||||
})
|
})
|
||||||
viewHeight: number = null;
|
accessor viewHeight: number = null;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: String,
|
type: String,
|
||||||
})
|
})
|
||||||
public format: 'a4' = 'a4';
|
accessor format: "a4" = "a4";
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number,
|
type: Number,
|
||||||
})
|
})
|
||||||
public pageNumber: number = 1;
|
accessor pageNumber: number = 1;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number,
|
type: Number,
|
||||||
})
|
})
|
||||||
public pageTotalNumber: number = 1;
|
accessor pageTotalNumber: number = 1;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Object,
|
type: Object,
|
||||||
})
|
})
|
||||||
public letterData: tsclass.business.ILetter = null;
|
accessor letterData: tsclass.business.TLetter = null;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
printMode = false;
|
accessor printMode = false;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Object,
|
type: Object,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
public documentSettings: plugins.shared.interfaces.IDocumentSettings = defaultDocumentSettings;
|
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings =
|
||||||
|
defaultDocumentSettings;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -100,26 +100,68 @@ export class DePage extends DeesElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topInfo {
|
|
||||||
position: absolute;
|
|
||||||
top: 60px;
|
|
||||||
left: 40px;
|
|
||||||
color: red;
|
|
||||||
transform: rotate(-5deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigDraftText {
|
.bigDraftText {
|
||||||
transform: rotate(-45deg);
|
transform: rotate(-45deg);
|
||||||
font-size: 200px;
|
font-size: 200px;
|
||||||
opacity: 0.05;
|
opacity: 0.05;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.foldMark__wrapper {
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.foldMark {
|
||||||
|
position: absolute;
|
||||||
|
border-top: 1px solid #d3d3d3;
|
||||||
|
width: 10px;
|
||||||
|
left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.foldMark--start {
|
||||||
|
top: calc(var(--DPI-FACTOR) * 8.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.foldMark--center {
|
||||||
|
top: calc(var(--DPI-FACTOR) * 14.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.foldMark--end {
|
||||||
|
top: calc(var(--DPI-FACTOR) * 19.2);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
|
const theme = this.documentSettings.theme;
|
||||||
return html`
|
return html`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
${theme?.colorPrimaryForeground ? `--theme-color-primary-fg: ${theme.colorPrimaryForeground};` : ''}
|
||||||
|
${theme?.colorPrimaryBackground ? `--theme-color-primary-bg: ${theme.colorPrimaryBackground};` : ''}
|
||||||
|
${theme?.colorAccentForeground ? `--theme-color-accent-fg: ${theme.colorAccentForeground};` : ''}
|
||||||
|
${theme?.colorAccentBackground ? `--theme-color-accent-bg: ${theme.colorAccentBackground};` : ''}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
background-size: contain;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page:not(.page--first) {
|
||||||
|
background-image: ${theme?.pageBackground ?? "none"};
|
||||||
|
}
|
||||||
|
|
||||||
|
.page.page--first {
|
||||||
|
background-image: ${theme?.coverPageBackground ?? theme?.pageBackground ?? "none"};
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<div id="scaleWrapper">
|
<div id="scaleWrapper">
|
||||||
<dedocument-pagecontainer .printMode=${this.printMode}>
|
<dedocument-pagecontainer .printMode=${this.printMode}>
|
||||||
|
<div
|
||||||
|
class="page ${this.pageNumber === 1
|
||||||
|
? "page--first"
|
||||||
|
: ""}"
|
||||||
|
></div>
|
||||||
${this.letterData
|
${this.letterData
|
||||||
? html`
|
? html`
|
||||||
${this.documentSettings.enableDefaultHeader
|
${this.documentSettings.enableDefaultHeader
|
||||||
@@ -131,7 +173,18 @@ export class DePage extends DeesElement {
|
|||||||
.pageTotalNumber="${this.pageTotalNumber}"
|
.pageTotalNumber="${this.pageTotalNumber}"
|
||||||
></dedocument-pageheader>
|
></dedocument-pageheader>
|
||||||
`
|
`
|
||||||
: ``}
|
: null}
|
||||||
|
|
||||||
|
<!-- FOLD MARKS -->
|
||||||
|
${this.documentSettings.enableFoldMarks === true
|
||||||
|
? html` <div class="foldMark__wrapper">
|
||||||
|
<span class="foldMark foldMark--start"></span>
|
||||||
|
<span class="foldMark foldMark--center"></span>
|
||||||
|
<span class="foldMark foldMark--end"></span>
|
||||||
|
</div>`
|
||||||
|
: null}
|
||||||
|
|
||||||
|
<!-- LETTER HEADER -->
|
||||||
${this.pageNumber === 1
|
${this.pageNumber === 1
|
||||||
? html`
|
? html`
|
||||||
<dedocument-letterheader
|
<dedocument-letterheader
|
||||||
@@ -141,7 +194,9 @@ export class DePage extends DeesElement {
|
|||||||
.pageTotalNumber="${this.pageTotalNumber}"
|
.pageTotalNumber="${this.pageTotalNumber}"
|
||||||
></dedocument-letterheader>
|
></dedocument-letterheader>
|
||||||
`
|
`
|
||||||
: html``}
|
: null}
|
||||||
|
|
||||||
|
<!-- PAGE CONTENT -->
|
||||||
<dedocument-pagecontent
|
<dedocument-pagecontent
|
||||||
.letterData=${this.letterData}
|
.letterData=${this.letterData}
|
||||||
.documentSettings=${this.documentSettings}
|
.documentSettings=${this.documentSettings}
|
||||||
@@ -149,7 +204,9 @@ export class DePage extends DeesElement {
|
|||||||
.pageTotalNumber="${this.pageTotalNumber}"
|
.pageTotalNumber="${this.pageTotalNumber}"
|
||||||
><slot></slot
|
><slot></slot
|
||||||
></dedocument-pagecontent>
|
></dedocument-pagecontent>
|
||||||
${this.documentSettings.enableDefaultFooter
|
|
||||||
|
<!-- DEFAULT FOOTER -->
|
||||||
|
${this.documentSettings.enableDefaultFooter === true
|
||||||
? html`
|
? html`
|
||||||
<dedocument-pagefooter
|
<dedocument-pagefooter
|
||||||
.letterData=${this.letterData}
|
.letterData=${this.letterData}
|
||||||
@@ -158,22 +215,19 @@ export class DePage extends DeesElement {
|
|||||||
.pageTotalNumber="${this.pageTotalNumber}"
|
.pageTotalNumber="${this.pageTotalNumber}"
|
||||||
></dedocument-pagefooter>
|
></dedocument-pagefooter>
|
||||||
`
|
`
|
||||||
: ``}
|
: null}
|
||||||
|
|
||||||
<div class="versionOverlay">
|
<div class="versionOverlay">
|
||||||
${this.letterData.versionInfo.type === 'draft'
|
${this.letterData.versionInfo.type === "draft"
|
||||||
? html`
|
? html`
|
||||||
${this.documentSettings.enableTopDraftText
|
<div class="bigDraftText">
|
||||||
? html`
|
${plugins.shared.translation.translate(
|
||||||
<div class="topInfo">
|
this.documentSettings.languageCode,
|
||||||
Please note: THIS IS A DRAFT ONLY. NO RIGHTS CAN BE DERIVED FROM
|
"overlay@@draft"
|
||||||
THIS.<br />
|
)}
|
||||||
-> Revision/Document version: ${this.letterData.versionInfo.version}
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ``}
|
: null}
|
||||||
<div class="bigDraftText">DRAFT</div>
|
|
||||||
`
|
|
||||||
: html``}
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: html` <slot></slot> `}
|
: html` <slot></slot> `}
|
||||||
@@ -184,33 +238,37 @@ export class DePage extends DeesElement {
|
|||||||
|
|
||||||
public async checkOverflow() {
|
public async checkOverflow() {
|
||||||
await this.elementDomReady;
|
await this.elementDomReady;
|
||||||
const pageContent = this.shadowRoot.querySelector('dedocument-pagecontent');
|
const pageContent = this.shadowRoot.querySelector("dedocument-pagecontent");
|
||||||
return pageContent.checkOverflow();
|
return pageContent.checkOverflow();
|
||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProperties: Map<string | number | symbol, unknown>): void {
|
updated(changedProperties: Map<string | number | symbol, unknown>): void {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if (changedProperties.has('viewHeight') || changedProperties.has('viewWidth')) {
|
if (
|
||||||
|
changedProperties.has("viewHeight") ||
|
||||||
|
changedProperties.has("viewWidth")
|
||||||
|
) {
|
||||||
this.adjustScaling();
|
this.adjustScaling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private adjustScaling() {
|
private adjustScaling() {
|
||||||
const scaleWrapper: HTMLDivElement = this.shadowRoot.querySelector('#scaleWrapper');
|
const scaleWrapper: HTMLDivElement =
|
||||||
|
this.shadowRoot.querySelector("#scaleWrapper");
|
||||||
|
|
||||||
if (!scaleWrapper) return;
|
if (!scaleWrapper) return;
|
||||||
|
|
||||||
let scale = 1;
|
let scale = 1;
|
||||||
if (this.viewHeight) {
|
if (this.viewHeight) {
|
||||||
scale = this.viewHeight / plugins.shared.a4Height;
|
scale = this.viewHeight / plugins.shared.A4_HEIGHT;
|
||||||
} else if (this.viewWidth) {
|
} else if (this.viewWidth) {
|
||||||
scale = this.viewWidth / plugins.shared.a4Width;
|
scale = this.viewWidth / plugins.shared.A4_WIDTH;
|
||||||
}
|
}
|
||||||
scaleWrapper.style.transform = `scale(${scale})`;
|
scaleWrapper.style.transform = `scale(${scale})`;
|
||||||
|
|
||||||
// Adjust the outer dimensions so they match the scaled content
|
// Adjust the outer dimensions so they match the scaled content
|
||||||
|
|
||||||
this.style.width = `${plugins.shared.a4Width * scale}px`;
|
this.style.width = `${plugins.shared.A4_WIDTH * scale}px`;
|
||||||
this.style.height = `${plugins.shared.a4Height * scale}px`;
|
this.style.height = `${plugins.shared.A4_HEIGHT * scale}px`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,32 +7,32 @@ import {
|
|||||||
css,
|
css,
|
||||||
cssManager,
|
cssManager,
|
||||||
unsafeCSS,
|
unsafeCSS,
|
||||||
} from '@design.estate/dees-element';
|
} from "@design.estate/dees-element";
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from "@design.estate/dees-domtools";
|
||||||
|
|
||||||
import * as plugins from '../plugins.js';
|
import * as plugins from "../plugins.js";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'dedocument-pagecontainer': DePageContainer;
|
"dedocument-pagecontainer": DePageContainer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement('dedocument-pagecontainer')
|
@customElement("dedocument-pagecontainer")
|
||||||
export class DePageContainer extends DeesElement {
|
export class DePageContainer extends DeesElement {
|
||||||
public static demo = () => html`
|
public static demo = () => html`
|
||||||
<dedocument-pagecontainer .format="${'a4'}"></dedocument-pagecontainer>
|
<dedocument-pagecontainer .format="${"a4"}"></dedocument-pagecontainer>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: String,
|
type: String,
|
||||||
})
|
})
|
||||||
public format: 'a4' = 'a4';
|
accessor format: "a4" = "a4";
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
})
|
})
|
||||||
public printMode = false;
|
accessor printMode = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -44,14 +44,13 @@ export class DePageContainer extends DeesElement {
|
|||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
background: white;
|
|
||||||
color: #333;
|
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
width: ${unsafeCSS(plugins.shared.a4Width + 'px')};
|
width: ${unsafeCSS(plugins.shared.A4_WIDTH + "px")};
|
||||||
height: ${unsafeCSS(plugins.shared.a4Height + 'px')};
|
height: ${unsafeCSS(plugins.shared.A4_HEIGHT + "px")};
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: var(--text-bg-color, #ffffff);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
@@ -60,7 +59,9 @@ export class DePageContainer extends DeesElement {
|
|||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
:host {
|
:host {
|
||||||
box-shadow: ${this.printMode ? `none` : `0px 0px 10px rgba(0,0,0,0.3)`};
|
box-shadow: ${this.printMode
|
||||||
|
? `none`
|
||||||
|
: `0px 0px 10px rgba(0,0,0,0.3)`};
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|||||||
@@ -8,37 +8,43 @@ import {
|
|||||||
cssManager,
|
cssManager,
|
||||||
unsafeCSS,
|
unsafeCSS,
|
||||||
domtools,
|
domtools,
|
||||||
} from '@design.estate/dees-element';
|
} from "@design.estate/dees-element";
|
||||||
|
|
||||||
import * as plugins from '../plugins.js';
|
import * as plugins from "../plugins.js";
|
||||||
import { dedocumentSharedStyle } from '../style.js';
|
import { dedocumentSharedStyle } from "../style.js";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'dedocument-pagecontent': DePageContent;
|
"dedocument-pagecontent": DePageContent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement('dedocument-pagecontent')
|
@customElement("dedocument-pagecontent")
|
||||||
export class DePageContent extends DeesElement {
|
export class DePageContent extends DeesElement {
|
||||||
public static demo = () => html`
|
public static demo = () => html`
|
||||||
<dedocument-pagecontent .format="${'a4'}"></dedocument-pagecontent>
|
<dedocument-pagecontent .format="${"a4"}"></dedocument-pagecontent>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number,
|
type: Number,
|
||||||
})
|
})
|
||||||
public letterData: plugins.tsclass.business.ILetter;
|
accessor letterData: plugins.tsclass.business.TLetter;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number,
|
type: Number,
|
||||||
})
|
})
|
||||||
public pageNumber: number = 1;
|
accessor pageNumber: number = 1;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number,
|
type: Number,
|
||||||
})
|
})
|
||||||
public pageTotalNumber: number = 1;
|
accessor pageTotalNumber: number = 1;
|
||||||
|
|
||||||
|
@property({
|
||||||
|
type: Object,
|
||||||
|
reflect: true,
|
||||||
|
})
|
||||||
|
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -49,19 +55,23 @@ export class DePageContent extends DeesElement {
|
|||||||
domtools.elementBasic.staticStyles,
|
domtools.elementBasic.staticStyles,
|
||||||
dedocumentSharedStyle,
|
dedocumentSharedStyle,
|
||||||
css`
|
css`
|
||||||
:host {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
left: ${unsafeCSS(plugins.shared.leftMargin + 'px')};
|
left: var(--LEFT-MARGIN);
|
||||||
right: ${unsafeCSS(plugins.shared.rightMargin + 'px')};
|
right: var(--RIGHT-MARGIN);
|
||||||
bottom: 170px;
|
bottom: calc(var(--DPI-FACTOR) * 4);
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content.page--first {
|
||||||
|
top: calc(var(--DPI-FACTOR) * 9.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content.page--notFirst {
|
||||||
|
top: calc(var(--DPI-FACTOR) * 4.5);
|
||||||
|
}
|
||||||
|
|
||||||
.content .subject {
|
.content .subject {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -83,60 +93,31 @@ export class DePageContent extends DeesElement {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.continuesOnNextPage {
|
|
||||||
display: inline-block;
|
|
||||||
background: #eeeeee;
|
|
||||||
color: #999;
|
|
||||||
border-radius: 50px;
|
|
||||||
padding: 5px 10px;
|
|
||||||
margin-top: 8px;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.finalPage {
|
|
||||||
display: inline-block;
|
|
||||||
background: #29b000;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 50px;
|
|
||||||
padding: 5px 10px;
|
|
||||||
margin-top: 8px;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
|
const firstPage = this.pageNumber === 1;
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<div class="content ${firstPage ? "page--first" : "page--notFirst"}">
|
||||||
.content {
|
${firstPage
|
||||||
top: ${this.pageNumber === 1 ? unsafeCSS('450px') : unsafeCSS('200px')};
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="content">
|
|
||||||
${this.pageNumber === 1
|
|
||||||
? html`<div class="subject">${this.letterData.subject}</div>`
|
? html`<div class="subject">${this.letterData.subject}</div>`
|
||||||
: html`
|
: null}
|
||||||
<div class="subjectRepeated">
|
|
||||||
${this.letterData.subject} (Page ${this.pageNumber})
|
|
||||||
</div>
|
|
||||||
`}
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
${this.pageTotalNumber !== this.pageNumber
|
|
||||||
? html`<div class="continuesOnNextPage">Continues on page ${this.pageNumber + 1}</div>`
|
|
||||||
: html`<div class="finalPage">This is the final page of this document.</div>`}
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public firstUpdated(_changedProperties: Map<string | number | symbol, unknown>): void {
|
public firstUpdated(
|
||||||
|
_changedProperties: Map<string | number | symbol, unknown>
|
||||||
|
): void {
|
||||||
super.firstUpdated(_changedProperties);
|
super.firstUpdated(_changedProperties);
|
||||||
this.checkOverflow();
|
this.checkOverflow();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async checkOverflow() {
|
public async checkOverflow() {
|
||||||
await this.elementDomReady;
|
await this.elementDomReady;
|
||||||
const contentContainer = this.shadowRoot.querySelector('.content');
|
const contentContainer = this.shadowRoot.querySelector(".content");
|
||||||
if (contentContainer.scrollHeight > contentContainer.clientHeight) {
|
if (contentContainer.scrollHeight > contentContainer.clientHeight) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,46 +5,45 @@ import {
|
|||||||
customElement,
|
customElement,
|
||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
css,
|
css,
|
||||||
cssManager,
|
|
||||||
unsafeCSS,
|
unsafeCSS,
|
||||||
domtools,
|
domtools,
|
||||||
} from '@design.estate/dees-element';
|
} from "@design.estate/dees-element";
|
||||||
|
|
||||||
import * as plugins from '../plugins.js';
|
import * as plugins from "../plugins.js";
|
||||||
import { dedocumentSharedStyle } from '../style.js';
|
import { dedocumentSharedStyle } from "../style.js";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'dedocument-pagefooter': DePageFooter;
|
"dedocument-pagefooter": DePageFooter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement('dedocument-pagefooter')
|
@customElement("dedocument-pagefooter")
|
||||||
export class DePageFooter extends DeesElement {
|
export class DePageFooter extends DeesElement {
|
||||||
public static demo = () => html`
|
public static demo = () => html`
|
||||||
<dedocument-pagefooter .format="${'a4'}"></dedocument-pagefooter>
|
<dedocument-pagefooter .format="${"a4"}"></dedocument-pagefooter>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Object,
|
type: Object,
|
||||||
})
|
})
|
||||||
letterData: plugins.tsclass.business.ILetter;
|
accessor letterData: plugins.tsclass.business.TLetter;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Object,
|
type: Object,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number
|
type: Number,
|
||||||
})
|
})
|
||||||
public pageNumber: number = 1;
|
accessor pageNumber: number = 1;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number
|
type: Number,
|
||||||
})
|
})
|
||||||
public pageTotalNumber: number = 1;
|
accessor pageTotalNumber: number = 1;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -67,32 +66,36 @@ export class DePageFooter extends DeesElement {
|
|||||||
left: 0px;
|
left: 0px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
height: 130px;
|
height: 130px;
|
||||||
content: '';
|
content: "";
|
||||||
padding: 30px ${unsafeCSS(plugins.shared.rightMargin + 'px')} 10px ${unsafeCSS(plugins.shared.leftMargin + 'px')};
|
padding: 30px var(--RIGHT-MARGIN) 10px var(--LEFT-MARGIN);
|
||||||
grid-template-columns: calc(100% / 4) calc(100% / 4) calc(100% / 4) calc(100% / 4);
|
grid-template-columns: calc(100% / 4) calc(100% / 4) calc(100% / 4) calc(
|
||||||
|
100% / 4
|
||||||
|
);
|
||||||
grid-gap: 5px;
|
grid-gap: 5px;
|
||||||
border-top: 2px solid #e4002b;
|
border-top: 2px solid var(--footer-separator-bg-color, #e4002b);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottomstripe .pageNumber {
|
.bottomstripe .pageNumber {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
right: ${unsafeCSS(plugins.shared.rightMargin + 'px')};
|
right: var(--RIGHT-MARGIN);
|
||||||
background: #e4002b;
|
color: var(--footer-separator-fg-color, #ffffff);
|
||||||
|
background: var(--footer-separator-bg-color, #e4002b);
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-bottom-left-radius: 3px;
|
border-bottom-left-radius: 3px;
|
||||||
border-bottom-right-radius: 3px;
|
border-bottom-right-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottomstripe .documentTitle {
|
.bottomstripe .documentTitle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -18px;
|
top: -19px;
|
||||||
right: ${unsafeCSS(plugins.shared.rightMargin + 'px')};
|
right: var(--RIGHT-MARGIN);
|
||||||
background: #dddddd;
|
color: var(--label-fg);
|
||||||
|
background: var(--label-bg);
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
color: #333;
|
|
||||||
border-top-left-radius: 3px;
|
border-top-left-radius: 3px;
|
||||||
border-top-right-radius: 3px;
|
border-top-right-radius: 3px;
|
||||||
}
|
}
|
||||||
@@ -103,36 +106,113 @@ export class DePageFooter extends DeesElement {
|
|||||||
return html`
|
return html`
|
||||||
<div class="bottomstripe">
|
<div class="bottomstripe">
|
||||||
<div>
|
<div>
|
||||||
<strong>${plugins.shared.translation.translate(this.documentSettings.languageCode, 'address', 'Address')}:</strong><br />
|
<strong
|
||||||
|
>${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"footer@@address"
|
||||||
|
)}:</strong
|
||||||
|
><br />
|
||||||
${this.letterData.from.name}<br />
|
${this.letterData.from.name}<br />
|
||||||
${this.letterData.from.address.streetName} ${this.letterData.from.address.houseNumber}<br />
|
${this.letterData.from.address.streetName}
|
||||||
${this.letterData.from.address.postalCode} ${this.letterData.from.address.city}<br />
|
${this.letterData.from.address.houseNumber}<br />
|
||||||
|
${this.letterData.from.address.postalCode}
|
||||||
|
${this.letterData.from.address.city}<br />
|
||||||
${this.letterData.from.address.country}
|
${this.letterData.from.address.country}
|
||||||
</div>
|
</div>
|
||||||
|
${this.letterData.from.registrationDetails
|
||||||
|
? html` <div>
|
||||||
|
<strong
|
||||||
|
>${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"footer@@registration.label"
|
||||||
|
)}:</strong
|
||||||
|
><br />
|
||||||
|
${this.letterData.from.registrationDetails.registrationName}<br />
|
||||||
|
<i>reg-#:</i> ${this.letterData.from.registrationDetails
|
||||||
|
.registrationId}<br />
|
||||||
|
<i>vat-id:</i> ${this.letterData.from.registrationDetails.vatId}
|
||||||
|
</div>`
|
||||||
|
: null}
|
||||||
<div>
|
<div>
|
||||||
<strong>${plugins.shared.translation.translate(this.documentSettings.languageCode, 'registrationInfo', 'Registration Info')}:</strong><br />
|
<strong
|
||||||
Amtsgericht Bremen<br />
|
>${plugins.shared.translation.translate(
|
||||||
<i>reg-#:</i> HRB 35230 HB<br />
|
this.documentSettings.languageCode,
|
||||||
<i>vat-id:</i> ${this.letterData.from.vatId}
|
"contact@@title"
|
||||||
|
)}:</strong
|
||||||
|
><br />
|
||||||
|
<i
|
||||||
|
>${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"contact@@mail"
|
||||||
|
)}:</i
|
||||||
|
>
|
||||||
|
${this.letterData.from.email}<br />
|
||||||
|
<i
|
||||||
|
>${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"contact@@phone"
|
||||||
|
)}:</i
|
||||||
|
>
|
||||||
|
${this.letterData.from.phone}<br />
|
||||||
|
<i
|
||||||
|
>${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"contact@@fax"
|
||||||
|
)}:</i
|
||||||
|
>
|
||||||
|
${this.letterData.from.fax}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>${plugins.shared.translation.translate(this.documentSettings.languageCode, 'contactInfo', 'Contact Info')}:</strong><br />
|
<strong
|
||||||
<i>email:</i> ${this.letterData.from.email}<br />
|
>${plugins.shared.translation.translate(
|
||||||
<i>phone:</i> ${this.letterData.from.phone}<br />
|
this.documentSettings.languageCode,
|
||||||
<i>fax:</i> ${this.letterData.from.fax}
|
"bankConnection@@title"
|
||||||
|
)}:</strong
|
||||||
|
><br />
|
||||||
|
<i
|
||||||
|
>${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"bankConnection@@bank.accountHolder"
|
||||||
|
)}:</i
|
||||||
|
>
|
||||||
|
${this.letterData?.from?.name}<br />
|
||||||
|
<i
|
||||||
|
>${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"bankConnection@@bank.institution"
|
||||||
|
)}:</i
|
||||||
|
>
|
||||||
|
${this.letterData?.from?.sepaConnection?.institution}<br />
|
||||||
|
<i
|
||||||
|
>${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"bankConnection@@bank.iban"
|
||||||
|
)}:</i
|
||||||
|
>
|
||||||
|
${this.letterData?.from?.sepaConnection?.iban}<br />
|
||||||
|
<i
|
||||||
|
>${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"bankConnection@@bank.bic"
|
||||||
|
)}:</i
|
||||||
|
>
|
||||||
|
${this.letterData?.from?.sepaConnection?.bic}<br />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="documentTitle">
|
||||||
<strong>${plugins.shared.translation.translate(this.documentSettings.languageCode, 'bankConnection', 'Bank Connection')}:</strong><br />
|
<b>${this.letterData?.subject}</b>
|
||||||
<i>beneficiary:</i> ${this.letterData?.from?.name}<br />
|
</div>
|
||||||
<i>institution:</i> ${this.letterData?.from?.sepaConnection?.institution}<br />
|
<div class="pageNumber">
|
||||||
<i>iban:</i> ${this.letterData?.from?.sepaConnection?.iban}<br />
|
${plugins.shared.translation.translate(
|
||||||
<i>bic:</i> ${this.letterData?.from?.sepaConnection?.bic}<br />
|
this.documentSettings.languageCode,
|
||||||
|
"footer@@page"
|
||||||
|
)}
|
||||||
|
${this.pageNumber}
|
||||||
|
${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"footer@@pageOf"
|
||||||
|
)}
|
||||||
|
${this.pageTotalNumber}
|
||||||
</div>
|
</div>
|
||||||
<div class="documentTitle">Subject: <b>${this.letterData?.subject}</b>${(() => {
|
|
||||||
const uidString = html`/ Document-UID: <b>${html`<a href="https://uid.signature.digital/">https://uid.signature.digital/</a>`}</b>`;
|
|
||||||
return ``;
|
|
||||||
})()}</div>
|
|
||||||
<div class="pageNumber">page ${this.pageNumber} of ${this.pageTotalNumber}</div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,44 +7,44 @@ import {
|
|||||||
css,
|
css,
|
||||||
cssManager,
|
cssManager,
|
||||||
unsafeCSS,
|
unsafeCSS,
|
||||||
domtools
|
domtools,
|
||||||
} from '@design.estate/dees-element';
|
} from "@design.estate/dees-element";
|
||||||
|
|
||||||
import * as plugins from '../plugins.js';
|
import * as plugins from "../plugins.js";
|
||||||
import { dedocumentSharedStyle } from '../style.js';
|
import { dedocumentSharedStyle } from "../style.js";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'dedocument-pageheader': DePageHeader;
|
"dedocument-pageheader": DePageHeader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement('dedocument-pageheader')
|
@customElement("dedocument-pageheader")
|
||||||
export class DePageHeader extends DeesElement {
|
export class DePageHeader extends DeesElement {
|
||||||
public static demo = () => html`
|
public static demo = () => html`
|
||||||
<dedocument-pageheader .format="${'a4'}"></dedocument-pageheader>
|
<dedocument-pageheader .format="${"a4"}"></dedocument-pageheader>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Object,
|
type: Object,
|
||||||
})
|
})
|
||||||
public letterData: plugins.tsclass.business.ILetter = null;
|
accessor letterData: plugins.tsclass.business.TLetter = null;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Object,
|
type: Object,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number,
|
type: Number,
|
||||||
})
|
})
|
||||||
public pageNumber: number = 1;
|
accessor pageNumber: number = 1;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Number,
|
type: Number,
|
||||||
})
|
})
|
||||||
public pageTotalNumber: number = 1;
|
accessor pageTotalNumber: number = 1;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -75,7 +75,7 @@ export class DePageHeader extends DeesElement {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
top: 130px;
|
top: 130px;
|
||||||
left: auto;
|
left: auto;
|
||||||
right: ${unsafeCSS(plugins.shared.rightMargin + 'px')};
|
right: var(--RIGHT-MARGIN);
|
||||||
height: 20px;
|
height: 20px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
color: #333;
|
color: #333;
|
||||||
@@ -86,8 +86,8 @@ export class DePageHeader extends DeesElement {
|
|||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
left: auto;
|
left: auto;
|
||||||
right: ${unsafeCSS(plugins.shared.rightMargin + 'px')};
|
right: var(--RIGHT-MARGIN);
|
||||||
font-family: 'Courier New', Courier, monospace;
|
font-family: "Courier New", Courier, monospace;
|
||||||
}
|
}
|
||||||
.topstripe .logo img {
|
.topstripe .logo img {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -100,10 +100,16 @@ export class DePageHeader extends DeesElement {
|
|||||||
return html`
|
return html`
|
||||||
<div class="topstripe">
|
<div class="topstripe">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
No logo set!
|
${plugins.shared.translation.translate(
|
||||||
|
this.documentSettings.languageCode,
|
||||||
|
"empty.logo"
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="topstripe2">${this.letterData?.from?.description || '[no letterData.from.description set]'}</div>
|
<div class="topstripe2">
|
||||||
|
${this.letterData?.from?.description ||
|
||||||
|
"[no letterData.from.description set]"}
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
95
ts_web/elements/paymentcode.ts
Normal file
95
ts_web/elements/paymentcode.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import {
|
||||||
|
css,
|
||||||
|
customElement,
|
||||||
|
DeesElement,
|
||||||
|
html,
|
||||||
|
property,
|
||||||
|
query,
|
||||||
|
type TemplateResult,
|
||||||
|
} from "@design.estate/dees-element";
|
||||||
|
import * as plugins from "../plugins.js";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dedocument-paymentcode": DedocumentPaymentCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("dedocument-paymentcode")
|
||||||
|
export class DedocumentPaymentCode extends DeesElement {
|
||||||
|
public static styles = [
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
text-align: center;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
width: inherit !important;
|
||||||
|
height: inherit !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
@query("canvas")
|
||||||
|
accessor canvasEl!: HTMLCanvasElement;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor bic: string;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor name: string;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor iban: string;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor currency: string;
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
accessor totalGross: number;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor reference: string;
|
||||||
|
|
||||||
|
private updateQRCode(): void {
|
||||||
|
if (!this.canvasEl) return;
|
||||||
|
|
||||||
|
plugins.qrcode.toCanvas(
|
||||||
|
this.canvasEl,
|
||||||
|
`BCD
|
||||||
|
001
|
||||||
|
1
|
||||||
|
SCT
|
||||||
|
${this.bic}
|
||||||
|
${this.name}
|
||||||
|
${this.iban}
|
||||||
|
${this.currency}${this.totalGross?.toFixed?.(2)}
|
||||||
|
|
||||||
|
${this.reference}`,
|
||||||
|
(error) => {
|
||||||
|
if (error) console.error(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override update(
|
||||||
|
changedProperties: Parameters<DeesElement["update"]>[0]
|
||||||
|
): void {
|
||||||
|
super.update(changedProperties);
|
||||||
|
this.updateQRCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): TemplateResult {
|
||||||
|
const allDataAvailable =
|
||||||
|
typeof this.bic === "string" &&
|
||||||
|
typeof this.name === "string" &&
|
||||||
|
typeof this.iban === "string" &&
|
||||||
|
typeof this.currency === "string" &&
|
||||||
|
typeof this.totalGross === "number" &&
|
||||||
|
typeof this.reference === "string";
|
||||||
|
|
||||||
|
return allDataAvailable ? html`<canvas></canvas>` : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import { html } from '@design.estate/dees-element';
|
import { html } from "@design.estate/dees-element";
|
||||||
import * as plugins from '../plugins.js';
|
import * as plugins from "../plugins.js";
|
||||||
|
|
||||||
export const demoFunc = () => html`
|
export const demoFunc = () => html`
|
||||||
<dedocument-viewer .letterData=${plugins.shared.demoLetter} .documentSettings=${plugins.shared.demoDocumentSettings}></dedocument-viewer>
|
<dedocument-viewer
|
||||||
|
.letterData=${plugins.shared.demoLetter}
|
||||||
|
.documentSettings=${plugins.shared.demoDocumentSettings}
|
||||||
|
></dedocument-viewer>
|
||||||
`;
|
`;
|
||||||
@@ -1,15 +1,22 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from "../plugins.js";
|
||||||
|
|
||||||
import { DeesElement, css, cssManager, customElement, html, property } from '@design.estate/dees-element';
|
import {
|
||||||
import { demoFunc } from './viewer.demo.js';
|
DeesElement,
|
||||||
|
css,
|
||||||
|
cssManager,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
property,
|
||||||
|
} from "@design.estate/dees-element";
|
||||||
|
import { demoFunc } from "./viewer.demo.js";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'dedocument-viewer': DeDocumentViewer;
|
"dedocument-viewer": DeDocumentViewer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement('dedocument-viewer')
|
@customElement("dedocument-viewer")
|
||||||
export class DeDocumentViewer extends DeesElement {
|
export class DeDocumentViewer extends DeesElement {
|
||||||
// DEMO
|
// DEMO
|
||||||
public static demo = demoFunc;
|
public static demo = demoFunc;
|
||||||
@@ -19,13 +26,13 @@ export class DeDocumentViewer extends DeesElement {
|
|||||||
type: Object,
|
type: Object,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
public letterData: plugins.tsclass.business.ILetter = null;
|
accessor letterData: plugins.tsclass.business.TLetter = null;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Object,
|
type: Object,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
})
|
})
|
||||||
public documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
@@ -34,7 +41,7 @@ export class DeDocumentViewer extends DeesElement {
|
|||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: ${cssManager.bdTheme('#eeeeeb', '#111')};
|
background: ${cssManager.bdTheme("#eeeeeb", "#111")};
|
||||||
}
|
}
|
||||||
.controls {
|
.controls {
|
||||||
top: 0px;
|
top: 0px;
|
||||||
@@ -43,7 +50,7 @@ export class DeDocumentViewer extends DeesElement {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: ${cssManager.bdTheme('#eeeeeb', '#111111ee')};
|
background: ${cssManager.bdTheme("#eeeeeb", "#111111ee")};
|
||||||
box-shadow: 0px 2px 8px 0px #000000;
|
box-shadow: 0px 2px 8px 0px #000000;
|
||||||
}
|
}
|
||||||
.controlsShadow {
|
.controlsShadow {
|
||||||
@@ -77,7 +84,12 @@ export class DeDocumentViewer extends DeesElement {
|
|||||||
<div class="maincontainer">
|
<div class="maincontainer">
|
||||||
<div class="viewport">
|
<div class="viewport">
|
||||||
${this.letterData
|
${this.letterData
|
||||||
? html` <dedocument-dedocument .letterData=${this.letterData} .documentSettings=${this.documentSettings}></dedocument-dedocument> `
|
? html`
|
||||||
|
<dedocument-dedocument
|
||||||
|
.letterData=${this.letterData}
|
||||||
|
.documentSettings=${this.documentSettings}
|
||||||
|
></dedocument-dedocument>
|
||||||
|
`
|
||||||
: html``}
|
: html``}
|
||||||
</div>
|
</div>
|
||||||
<div class="controls"></div>
|
<div class="controls"></div>
|
||||||
@@ -85,9 +97,11 @@ export class DeDocumentViewer extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
public updated = (changedProperties: Map<string | number | symbol, unknown>) => {
|
public updated = (
|
||||||
|
changedProperties: Map<string | number | symbol, unknown>
|
||||||
|
) => {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if (changedProperties.has('letterData')) {
|
if (changedProperties.has("letterData")) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from "../plugins.js";
|
||||||
import { html } from '@design.estate/dees-element';
|
import { html } from "@design.estate/dees-element";
|
||||||
|
|
||||||
export const page1 = () => html`
|
export const page1 = () => html`
|
||||||
<style>
|
<style>
|
||||||
@@ -7,11 +7,15 @@ export const page1 = () => html`
|
|||||||
margin: 16px;
|
margin: 16px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<dedocument-dedocument .printMode=${false} letterData=${plugins.smartjson.stringifyBase64({
|
<dedocument-dedocument
|
||||||
|
.printMode=${false}
|
||||||
|
letterData=${plugins.smartjson.stringifyBase64({
|
||||||
...plugins.shared.demoLetter,
|
...plugins.shared.demoLetter,
|
||||||
from: {
|
from: {
|
||||||
...plugins.shared.demoLetter.from,
|
...plugins.shared.demoLetter.from,
|
||||||
description: 'a string set via stringified JSON'
|
description: "a string set via stringified JSON",
|
||||||
}
|
},
|
||||||
} as plugins.tsclass.business.ILetter)}> </dedocument-dedocument>
|
} as plugins.tsclass.finance.TInvoice)}
|
||||||
|
>
|
||||||
|
</dedocument-dedocument>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,7 +1,38 @@
|
|||||||
import { css } from '@design.estate/dees-element';
|
import { css } from "@design.estate/dees-element";
|
||||||
|
import * as plugins from "./plugins.js";
|
||||||
|
|
||||||
export const dedocumentSharedStyle = css`
|
export const dedocumentSharedStyle = css`
|
||||||
:host {
|
:host {
|
||||||
font-family: 'Exo 2';
|
/* Primitive colors */
|
||||||
|
--color-light: #ffffff;
|
||||||
|
--color-dark: #333333;
|
||||||
|
--color-grey: #dddddd;
|
||||||
|
--color-grey-100: #dddddd;
|
||||||
|
--color-red: #e4002b;
|
||||||
|
|
||||||
|
/* Semantic colors */
|
||||||
|
--color-primary-fg: var(--theme-color-primary-fg, var(--color-dark));
|
||||||
|
--color-primary-bg: var(--theme-color-primary-bg, var(--color-light));
|
||||||
|
--color-accent-fg: var(--theme-color-accent-fg, var(--color-light));
|
||||||
|
--color-accent-bg: var(--theme-color-accent-bg, var(--color-red));
|
||||||
|
|
||||||
|
/* Functional colors */
|
||||||
|
--text-fg-color: var(--color-primary-fg);
|
||||||
|
--text-bg-color: var(--color-primary-bg);
|
||||||
|
--label-fg: var(--color-dark);
|
||||||
|
--label-bg: var(--color-grey);
|
||||||
|
--footer-separator-bg-color: var(--color-accent-bg);
|
||||||
|
--footer-separator-fg-color: var(--color-light);
|
||||||
|
|
||||||
|
/* Functional variables */
|
||||||
|
--DPI-FACTOR: ${plugins.shared.cmToPx(1)}px;
|
||||||
|
--RIGHT-MARGIN: ${plugins.shared.cmToPx(2)}px;
|
||||||
|
--LEFT-MARGIN: ${plugins.shared.cmToPx(2)}px;
|
||||||
|
--text-font-family: var(--theme-text-font-family, "Exo 2");
|
||||||
|
--text-font-size: var(--theme-text-font-size, 12px);
|
||||||
|
|
||||||
|
color: var(--text-fg-color);
|
||||||
|
font-family: var(--text-font-family);
|
||||||
|
font-size: var(--text-font-size);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"experimentalDecorators": true,
|
|
||||||
"useDefineForClassFields": false,
|
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
|
|||||||
Reference in New Issue
Block a user