Compare commits

...

42 Commits

Author SHA1 Message Date
393db71413 feat: migrate to new tsclass schema 2025-03-26 14:58:08 +00:00
04e668ff83 feat: enhance translation and invoice layout 2025-03-26 14:58:03 +00:00
1106b9648d 1.6.11
Some checks failed
Default (tags) / security (push) Failing after 2s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-01-01 04:57:29 +01:00
1f06a73422 fix(license): Update copyright notice in license to reflect new ownership 2025-01-01 04:57:29 +01:00
7d5508e4d8 1.6.10
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2024-12-08 22:08:59 +01:00
67ae3ac22b fix(core): Improve stability and performance of document generation 2024-12-08 22:08:59 +01:00
389db4f014 1.6.9
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2024-12-08 17:37:17 +01:00
60fd21c6d3 fix(contentinvoice.ts): Improve invoice item layout and fix alignment issues. 2024-12-08 17:37:17 +01:00
89cc610aa0 1.6.8
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2024-12-07 15:38:17 +01:00
db9b53462d fix(rendering and logging): remove extensive console chattiness, remove content memory leak when rerendering 2024-12-07 15:38:17 +01:00
dbca81987c 1.6.7
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2024-12-07 14:22:42 +01:00
2e3739e765 fix(document rendering): Fixed overflow issues in document and page elements 2024-12-07 14:22:42 +01:00
76d829f5c7 1.6.6
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2024-12-07 13:55:10 +01:00
f0e4fe0521 fix(page-render): Fix layout scaling adjustment for page component 2024-12-07 13:55:10 +01:00
5b3b1f4624 1.6.5
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2024-12-05 20:23:20 +01:00
cf29de5650 fix(contentinvoice): Fix VAT group item number formatting and remove custom font style in invoice sums. 2024-12-05 20:23:19 +01:00
15573a85ec 1.6.4
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2024-12-05 20:20:30 +01:00
20abb7f837 fix(styling): Consolidated shared styles for consistent font applied across various components. 2024-12-05 20:20:29 +01:00
ebb6622637 1.6.3
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2024-12-05 19:31:17 +01:00
dead3eeaec fix(ui): Corrects font family in contentinvoice element. 2024-12-05 19:31:17 +01:00
23031d06b7 1.6.2 2024-12-05 01:42:06 +01:00
5b849b316f fix(translation): Corrected missing translation keys for VAT short form across multiple languages. 2024-12-05 01:42:05 +01:00
47b6d4f0b9 1.6.1 2024-12-05 01:39:42 +01:00
2464d28a19 fix(core): Ensure consistent project structure and code organization without additional changes. 2024-12-05 01:39:42 +01:00
784fe9ca74 1.6.0 2024-12-05 01:36:27 +01:00
83efcdd2b8 feat(documentSettings + translations + vat in itemLines): implements and fixes #2 2024-12-05 01:36:27 +01:00
f2d585ed36 update 2024-12-05 01:33:16 +01:00
9517687902 1.5.3 2024-12-02 17:05:03 +01:00
b5d3a72733 fix(elements): Update viewer attributes and fix font integration 2024-12-02 17:05:03 +01:00
08baae7b9c 1.5.2 2024-12-02 16:13:37 +01:00
c949f1b968 fix(workflow): Corrected Docker image references and package scope in YAML workflows for compatibility. 2024-12-02 16:13:37 +01:00
249d44d5fc 1.5.1 2024-12-02 16:05:44 +01:00
de02e015e8 fix(package): Fix duplicate node export in package.json 2024-12-02 16:05:44 +01:00
3e50547d91 1.5.0 2024-12-02 16:04:58 +01:00
c9ad18538b feat(core): Refactor project structure for better modularity and code organization 2024-12-02 16:04:58 +01:00
bab1ffc4b4 1.4.0 2024-12-02 15:20:58 +01:00
8986ec2543 feat(translation): Add French and Italian translations for document interface 2024-12-02 15:20:57 +01:00
49c15a98e4 1.3.2 2024-12-02 15:05:07 +01:00
840de119ad fix(core): Minor updates and optimizations in the output PDF formatting process. 2024-12-02 15:05:07 +01:00
3919956c57 Merge remote-tracking branch 'origin/master' 2024-12-02 15:04:22 +01:00
bc4f018b06 Merge pull request 'fix: use type import to avoid ts dependency in js files' (#1) from fix/use-type-import into master
Reviewed-on: #1
2024-12-02 13:37:59 +00:00
8f7677ab82 fix: use type import to avoid ts dependency in js files 2024-12-02 12:59:19 +00:00
52 changed files with 4685 additions and 3083 deletions

View File

@ -6,8 +6,8 @@ on:
- '**' - '**'
env: env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci IMAGE: code.foss.global/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}} NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}} NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}} NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
@ -26,7 +26,7 @@ jobs:
- name: Install pnpm and npmci - name: Install pnpm and npmci
run: | run: |
pnpm install -g pnpm pnpm install -g pnpm
pnpm install -g @shipzone/npmci pnpm install -g @ship.zone/npmci
- name: Run npm prepare - name: Run npm prepare
run: npmci npm prepare run: npmci npm prepare

View File

@ -6,8 +6,8 @@ on:
- '*' - '*'
env: env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci IMAGE: code.foss.global/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}} NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}} NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}} NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
@ -26,7 +26,7 @@ jobs:
- name: Prepare - name: Prepare
run: | run: |
pnpm install -g pnpm pnpm install -g pnpm
pnpm install -g @shipzone/npmci pnpm install -g @ship.zone/npmci
npmci npm prepare npmci npm prepare
- name: Audit production dependencies - name: Audit production dependencies
@ -54,7 +54,7 @@ jobs:
- name: Prepare - name: Prepare
run: | run: |
pnpm install -g pnpm pnpm install -g pnpm
pnpm install -g @shipzone/npmci pnpm install -g @ship.zone/npmci
npmci npm prepare npmci npm prepare
- name: Test stable - name: Test stable
@ -82,7 +82,7 @@ jobs:
- name: Prepare - name: Prepare
run: | run: |
pnpm install -g pnpm pnpm install -g pnpm
pnpm install -g @shipzone/npmci pnpm install -g @ship.zone/npmci
npmci npm prepare npmci npm prepare
- name: Release - name: Release
@ -104,7 +104,7 @@ jobs:
- name: Prepare - name: Prepare
run: | run: |
pnpm install -g pnpm pnpm install -g pnpm
pnpm install -g @shipzone/npmci pnpm install -g @ship.zone/npmci
npmci npm prepare npmci npm prepare
- name: Code quality - name: Code quality

1
.gitignore vendored
View File

@ -3,7 +3,6 @@
# artifacts # artifacts
coverage/ coverage/
public/ public/
pages/
# installs # installs
node_modules/ node_modules/

2
.npmrc
View File

@ -1 +1 @@
registry=https://verdaccio.nevermind.cloud/ registry=https://registry.npmjs.org/

View File

@ -1,5 +1,110 @@
# Changelog # Changelog
## 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)
Improve stability and performance of document generation
## 2024-12-08 - 1.6.9 - fix(contentinvoice.ts)
Improve invoice item layout and fix alignment issues.
- Fixed missing border on last invoice item.
- Added highlighted style for invoice lines.
- Corrected alignment and display of invoice item details.
- Included VAT percentage display adjustments.
## 2024-12-07 - 1.6.8 - fix(rendering and logging)
Removed debug logging from document rendering process.
- Removed console logs from content invoice, document rendering, page scaling, and overflow checking.
## 2024-12-07 - 1.6.7 - fix(document rendering)
Fixed overflow issues in document and page elements
- Ensured content overflow handling in document.ts
- Adjusted page element overflow settings in page.ts
## 2024-12-07 - 1.6.6 - fix(page-render)
Fix layout scaling adjustment for page component
- Ensure `font-family` is no longer explicitly set to inherit in `DePage` component.
- Adjust scaling logic to properly set width and overflow based on calculated scale.
## 2024-12-05 - 1.6.5 - fix(contentinvoice)
Fix VAT group item number formatting and remove custom font style in invoice sums.
- Removed custom font-family style from the invoice sums.
- Corrected VAT group item numbers display by properly formatting and removing trailing comma.
## 2024-12-05 - 1.6.4 - fix(styling)
Consolidated shared styles for consistent font applied across various components.
- Added a shared style file for consistent font family across components
- Applied shared style to contentinvoice, letterheader, pagecontent, pagefooter, and pageheader components
## 2024-12-05 - 1.6.3 - fix(ui)
Corrects font family in contentinvoice element.
- Updated the font-family for line items in contentinvoice.ts
## 2024-12-05 - 1.6.2 - fix(translation)
Corrected missing translation keys for VAT short form across multiple languages.
- Fixed missing 'vatShort' translation for English, German, Spanish, French, and Italian in translation.ts file.
## 2024-12-05 - 1.6.1 - fix(core)
Ensure consistent project structure and code organization without additional changes.
## 2024-12-05 - 1.6.0 - feat(documentSettings + translations + vat in itemLines)
Updated dependencies in package.json
- Updated @types/node to version ^22.10.1
- Updated qrcode package to version ^1.5.4
## 2024-12-02 - 1.5.3 - fix(elements)
Update viewer attributes and fix font integration
- Updated viewer.ts to add missing property decorators for letterData and documentSettings.
- Fixed font loading by correcting URLs in index.html.
- Set demoDocumentSettings default header and footer to false.
## 2024-12-02 - 1.5.2 - fix(workflow)
Corrected Docker image references and package scope in YAML workflows for compatibility.
- Updated Docker image reference from GitLab to code.foss.global for npmci UI workflows.
- Fixed npm package scope for `@shipzone/npmci` in YAML workflows.
- Minor formatting corrections for better code readability.
## 2024-12-02 - 1.5.1 - fix(package)
Fix duplicate node export in package.json
- Resolved a duplication issue with the './node' export in package.json exports field.
## 2024-12-02 - 1.5.0 - feat(core)
Refactor project structure for better modularity and code organization
- Moved shared components to a dedicated 'dist_ts_shared' directory for better separation of concerns.
- Updated import statements across the codebase to align with the new file structure.
- Introduced translations directly under 'ts_shared' for language-specific string management.
## 2024-12-02 - 1.4.0 - feat(translation)
Add French and Italian translations for document interface
- Expanded translation support by adding French and Italian language translations
- Fixed duplicated text in German final page statement
- Improved Spanish translation of reverse VAT note and final page statement
## 2024-12-02 - 1.3.2 - fix(core)
Minor updates and optimizations in the output PDF formatting process.
- Improved handling of page content overflow in PDF generation.
- Refined default document settings for consistent rendering.
## 2024-12-02 - 1.3.1 - fix(documentation) ## 2024-12-02 - 1.3.1 - fix(documentation)
Updated project description and enhanced documentation in package.json and README Updated project description and enhanced documentation in package.json and README

View File

@ -1,6 +1,3 @@
<!--gitzone element-->
<!-- made by Task Venture Capital GmbH -->
<!-- checkout https://maintainedby.lossless.com for awesome OpenSource projects -->
<html lang="en"> <html lang="en">
<head> <head>
<!--Lets set some basic meta tags--> <!--Lets set some basic meta tags-->

View File

@ -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

View File

@ -1,13 +1,16 @@
{ {
"name": "@design.estate/dees-document", "name": "@design.estate/dees-document",
"version": "1.3.1", "version": "1.6.11",
"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",
"typings": "dist_ts_web/index.d.ts", "typings": "dist_ts_web/index.d.ts",
"exports": { "exports": {
"./ts": "./dist_ts/index.js", ".": "./dist_ts/index.js",
"./ts_web": "./dist_ts_web/index.js" "./node": "./dist_ts/index.js",
"./web": "./dist_ts_web/index.js",
"./shared": "./dist_ts_shared/index.js",
"./interfaces": "./dist_ts_shared/interfaces/index.js"
}, },
"scripts": { "scripts": {
"test": "npm run build && tstest test/", "test": "npm run build && tstest test/",
@ -18,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": "^1.4.1",
"@design.estate/dees-domtools": "^2.3.2",
"@design.estate/dees-element": "^2.0.39", "@design.estate/dees-element": "^2.0.39",
"@design.estate/dees-wcctools": "^1.0.90", "@design.estate/dees-wcctools": "^1.0.90",
"@git.zone/tsrun": "^1.3.3", "@git.zone/tsrun": "^1.3.3",
"@push.rocks/smartfile": "^11.0.21", "@push.rocks/smartfile": "^11.2.0",
"@push.rocks/smartjson": "^5.0.20", "@push.rocks/smartjson": "^5.0.20",
"@push.rocks/smartpath": "^5.0.18", "@push.rocks/smartpath": "^5.0.18",
"@push.rocks/smartpdf": "^3.1.8", "@push.rocks/smartpdf": "^3.2.2",
"@push.rocks/smarttime": "^4.0.8", "@push.rocks/smarttime": "^4.1.1",
"@tsclass/tsclass": "^4.1.2", "@tsclass/tsclass": "^8.0.3",
"@types/node": "^22.10.1", "@types/node": "^22.13.13",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"puppeteer": "^24.4.0",
"qrcode": "^1.5.4" "qrcode": "^1.5.4"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.2.0", "@git.zone/tsbuild": "^2.3.2",
"@git.zone/tsbundle": "^2.1.0", "@git.zone/tsbundle": "^2.2.5",
"@git.zone/tstest": "^1.0.90", "@git.zone/tstest": "^1.0.96",
"@git.zone/tswatch": "^2.0.25", "@git.zone/tswatch": "^2.1.0",
"@push.rocks/projectinfo": "^5.0.2", "@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/tapbundle": "^5.5.3" "@push.rocks/tapbundle": "^5.6.0"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",
@ -58,12 +63,12 @@
"type": "module", "type": "module",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://gitlab.com/designestate/private/dedocument-catalog.git" "url": "git+https://code.foss.global/designestate/private/dedocument-catalog.git"
}, },
"bugs": { "bugs": {
"url": "https://gitlab.com/designestate/private/dedocument-catalog/issues" "url": "https://code.foss.global/designestate/private/dedocument-catalog/issues"
}, },
"homepage": "https://gitlab.com/designestate/private/dedocument-catalog#readme", "homepage": "https://code.foss.global/designestate/private/dedocument-catalog#readme",
"keywords": [ "keywords": [
"document generation", "document generation",
"invoice automation", "invoice automation",

4845
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -47,48 +47,48 @@ import { ILetter } from '@design.estate/dees-document';
const invoiceTemplate: ILetter = { const invoiceTemplate: ILetter = {
from: { from: {
name: "Your Company Name", name: 'Your Company Name',
address: { address: {
streetName: "Your Street", streetName: 'Your Street',
houseNumber: "123", houseNumber: '123',
city: "Your City", city: 'Your City',
country: "Your Country", country: 'Your Country',
postalCode: "12345", postalCode: '12345',
}, },
email: "your-email@example.com", email: 'your-email@example.com',
phone: "123-456-7890", phone: '123-456-7890',
}, },
to: { to: {
name: "Recipient Company Name", name: 'Recipient Company Name',
address: { address: {
streetName: "Recipient Street", streetName: 'Recipient Street',
houseNumber: "456", houseNumber: '456',
city: "Recipient City", city: 'Recipient City',
country: "Recipient Country", country: 'Recipient Country',
postalCode: "67890", postalCode: '67890',
}, },
email: "recipient-email@example.com", email: 'recipient-email@example.com',
phone: "098-765-4321", phone: '098-765-4321',
}, },
content: { content: {
invoiceData: { invoiceData: {
items: [ items: [
{ {
name: "Service or Product Name", name: 'Service or Product Name',
unitQuantity: 2, unitQuantity: 2,
unitNetPrice: 100.00, unitNetPrice: 100.0,
unitType: 'service', unitType: 'service',
vatPercentage: 19, vatPercentage: 19,
currency: 'EUR', currency: 'EUR',
}
]
}
}, },
subject: "Invoice for Services Rendered", ],
},
},
subject: 'Invoice for Services Rendered',
date: new Date().getTime(), date: new Date().getTime(),
versionInfo: { versionInfo: {
type: "final", type: 'final',
version: "1.0.0" version: '1.0.0',
}, },
}; };
``` ```
@ -133,22 +133,28 @@ This script encompasses initializing services and generating a PDF in a streamli
`@design.estate/dees-document` provides several advanced functionalities, enabling rich document creation: `@design.estate/dees-document` provides several advanced functionalities, enabling rich document creation:
1. **Custom Templates & Styling** 1. **Custom Templates & Styling**
- Customize the styling through CSS or using inline styles in TypeScript. - Customize the styling through CSS or using inline styles in TypeScript.
- Templates can be adjusted to present different document types (e.g., contracts, reports). - Templates can be adjusted to present different document types (e.g., contracts, reports).
2. **Modular Components and Reuse** 2. **Modular Components and Reuse**
- Utilize modular components to create reusable parts across different documents, enhancing maintainability and reducing redundancy. - Utilize modular components to create reusable parts across different documents, enhancing maintainability and reducing redundancy.
3. **Interactive Documents** 3. **Interactive Documents**
- Integrate interactivities like forms, buttons, and interactive charts within your documents. - Integrate interactivities like forms, buttons, and interactive charts within your documents.
4. **Localization Support** 4. **Localization Support**
- Documents can be localized to support multiple languages, enhancing accessibility and usability. - Documents can be localized to support multiple languages, enhancing accessibility and usability.
5. **Responsive and Adaptive Designs** 5. **Responsive and Adaptive Designs**
- Create documents that adjust layout dynamically depending on print or digital medium, maintaining consistency across platforms. - Create documents that adjust layout dynamically depending on print or digital medium, maintaining consistency across platforms.
6. **Security Features** 6. **Security Features**
- Apply digital signatures and encrypt sensitive documents to ensure secure and authentic document distribution. - Apply digital signatures and encrypt sensitive documents to ensure secure and authentic document distribution.
7. **Complex Business Logic** 7. **Complex Business Logic**

View File

@ -2,8 +2,4 @@ import * as tsclass from '@tsclass/tsclass';
import * as smartfile from '@push.rocks/smartfile'; import * as smartfile from '@push.rocks/smartfile';
import * as path from 'path'; import * as path from 'path';
export { export { tsclass, smartfile, path };
tsclass,
smartfile,
path,
}

View File

@ -1,116 +1,104 @@
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/interfaces/index.js'; import * as interfaces from "../ts_shared/interfaces/index.js";
import { expect, tap } from '@push.rocks/tapbundle'; import { expect, tap } from "@push.rocks/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(
optionsArg
);
await plugins.smartfile.memory.toFs( await plugins.smartfile.memory.toFs(
Buffer.from(pdfResult.buffer), Buffer.from(pdfResult.buffer),
plugins.path.join(paths.nogitDir, `test-${counter++}.pdf`) plugins.path.join(paths.nogitDir, `test-${counter++}.pdf`)
@ -124,106 +112,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,7 +222,7 @@ 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();
}); });

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@design.estate/dees-document', name: '@design.estate/dees-document',
version: '1.3.1', version: '1.6.11',
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.'
} }

View File

@ -1,11 +1,7 @@
import * as plugins from './plugins.js'; import * as plugins from './plugins.js';
import * as paths from './paths.js';
import * as helpers from './helpers.js'; import * as helpers from './helpers.js';
import * as interfaces from '../ts/interfaces/index.js';
export interface IPdfServiceConstructorOptions { export interface IPdfServiceConstructorOptions {}
}
/** /**
* a pdf service for generating pdfs * a pdf service for generating pdfs
@ -46,8 +42,8 @@ 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.ILetter;
documentSettings: interfaces.IDocumentSettings documentSettings: plugins.shared.interfaces.IDocumentSettings;
}) { }) {
const html = ` const html = `
<script type="module"> <script type="module">

View File

@ -3,4 +3,4 @@ import * as paths from './paths.js';
export const getBundleAsString = async () => { export const getBundleAsString = async () => {
return plugins.smartfile.fs.toStringSync(paths.bundleFile); return plugins.smartfile.fs.toStringSync(paths.bundleFile);
} };

View File

@ -1,8 +0,0 @@
import * as shared from '../shared/index.js';
export interface IDocumentSettings {
enableTopDraftText?: boolean;
enableDefaultHeader?: boolean;
enableDefaultFooter?: boolean;
languageCode?: shared.translation.TLanguageCode;
}

View File

@ -1,2 +0,0 @@
export * from './document.js';
export * from './translation.js';

View File

@ -1,19 +0,0 @@
export interface IDeDocumentTranslations {
address: string;
bankConnection: string;
contactInfo: string;
description: string;
invoice: string;
itemPos: string;
quantity: string;
registrationInfo: string;
reverseVatNote: string;
totalNetPrice: string;
unitNetPrice: string;
unitType: string;
yourCustomerId: string;
yourVatId: string;
continuesOnPage: string;
finalPageStatement: string;
page: string;
}

View File

@ -1,6 +1,9 @@
import * as plugins from './plugins.js'; import * as plugins from './plugins.js';
export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '../'); export const packageDir = plugins.path.join(
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
'../',
);
export const nogitDir = plugins.path.join(packageDir, '.nogit/'); export const nogitDir = plugins.path.join(packageDir, '.nogit/');
plugins.smartfile.fs.ensureDirSync(nogitDir); plugins.smartfile.fs.ensureDirSync(nogitDir);

View File

@ -1,9 +1,12 @@
// node native // node native
import * as path from 'path'; import * as path from 'path';
export { export { path };
path
} // dees-document scope
import * as shared from '../dist_ts_shared/index.js';
export { shared };
// @push.rocks/scope // @push.rocks/scope
import * as smartfile from '@push.rocks/smartfile'; import * as smartfile from '@push.rocks/smartfile';
@ -11,16 +14,9 @@ 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 { export { smartfile, smartpath, smartjson, smartpdf };
smartfile,
smartpath,
smartjson,
smartpdf,
}
// @tsclass scope // @tsclass scope
import * as tsclass from '@tsclass/tsclass'; import * as tsclass from '@tsclass/tsclass';
export { export { tsclass };
tsclass,
}

View File

@ -1,219 +0,0 @@
import * as tsclass from '@tsclass/tsclass';
import * as interfaces from '../interfaces/index.js';
const fromContact: tsclass.business.IContact = {
name: 'Awesome From Company',
type: 'company',
description: 'a company that does stuff',
address: {
streetName: 'Awesome Street',
houseNumber: '5',
city: 'Bremen',
country: 'Germany',
postalCode: '28359',
},
vatId: 'DE12345678',
sepaConnection: {
bic: 'BPOTBEB1',
iban: 'BE01234567891616'
},
email: 'hello@awesome.company',
phone: '+49 421 1234567',
fax: '+49 421 1234568',
};
const toContact: tsclass.business.IContact = {
name: 'Awesome To GmbH',
type: 'company',
customerNumber: 'LL-CLIENT-123',
description: 'a company that does stuff',
address: {
streetName: 'Awesome Street',
houseNumber: '5',
city: 'Bremen',
country: 'Germany',
postalCode: '28359'
},
vatId: 'BE12345678',
}
export const demoLetter: tsclass.business.ILetter = {
versionInfo: {
type: 'draft',
version: '1.0.0',
},
accentColor: null,
content: {
textData: null,
timesheetData: null,
contractData: {
contractDate: Date.now(),
id: 'someid'
},
invoiceData: {
id: 'LL-INV-48765',
reverseCharge: true,
dueInDays: 30,
billedBy: fromContact,
billedTo: toContact,
status: null,
deliveryDate: new Date().getTime(),
periodOfPerformance: null,
printResult: null,
currency: 'EUR',
notes: [],
type: 'debitnote',
items: [
{
name: 'Item with 19% VAT',
unitQuantity: 2,
unitNetPrice: 100,
unitType: 'hours',
vatPercentage: 19,
position: 0,
},
{
name: 'Item with 7% VAT',
unitQuantity: 4,
unitNetPrice: 100,
unitType: 'hours',
vatPercentage: 7,
position: 1,
},
{
name: 'Item with 7% VAT',
unitQuantity: 3,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 7,
position: 2,
},
{
name: 'Item with 21% VAT',
unitQuantity: 1,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 21,
position: 3,
},
{
name: 'Item with 0% VAT',
unitQuantity: 6,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 0,
position: 4,
},{
name: 'Item with 19% VAT',
unitQuantity: 8,
unitNetPrice: 100,
unitType: 'hours',
vatPercentage: 19,
position: 5,
},
{
name: 'Item with 7% VAT',
unitQuantity: 9,
unitNetPrice: 100,
unitType: 'hours',
vatPercentage: 7,
position: 6,
},
{
name: 'Item with 7% VAT',
unitQuantity: 4,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 7,
position: 8,
},
{
name: 'Item with 21% VAT',
unitQuantity: 3,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 21,
position: 9,
},
{
name: 'Item with 0% VAT',
unitQuantity: 1,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 0,
position: 10,
},
{
name: 'Item with 0% VAT',
unitQuantity: 1,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 0,
position: 10,
},
{
name: 'Item with 0% VAT',
unitQuantity: 1,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 0,
position: 10,
},
{
name: 'Item with 0% VAT',
unitQuantity: 1,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 0,
position: 10,
},
{
name: 'Item with 0% VAT',
unitQuantity: 1,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 0,
position: 10,
},
{
name: 'Item with 0% VAT',
unitQuantity: 1,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 0,
position: 10,
},
{
name: 'Item with 0% VAT',
unitQuantity: 1,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 0,
position: 10,
},
],
}
},
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 = {
enableTopDraftText: true,
enableDefaultHeader: true,
enableDefaultFooter: true,
languageCode: 'DE',
};

View File

@ -1,9 +0,0 @@
export const a4Height = 1122;
export const a4Width = 794;
export const rightMargin = 70;
export const leftMargin = 90;
import * as translation from './translation.js';
export { translation };
export * from './demoletter.js';

View File

@ -1,82 +0,0 @@
import * as interfaces from '../interfaces/index.js';
type TTranslationImplementation = {
[key in keyof interfaces.IDeDocumentTranslations]: string;
}
export const EN_translations: TTranslationImplementation = {
address: 'Address',
bankConnection: 'Bank Connection',
contactInfo: 'Contact Info',
description: 'Description',
invoice: 'Invoice',
itemPos: 'Item Pos.',
quantity: 'Quantity',
registrationInfo: 'Registration Info',
reverseVatNote: 'VAT arises on a reverse charge basis and is payable by the customer.',
totalNetPrice: 'Total Net Price',
unitNetPrice: 'Unit Net Price',
unitType: 'Unit Type',
yourCustomerId: 'Your Customer ID:',
yourVatId: 'Your vat id on file:',
continuesOnPage: 'Continues on page',
finalPageStatement: 'This is the final page of this document.',
page: 'Page',
};
export const DE_translations: TTranslationImplementation = {
address: 'Adresse',
bankConnection: 'Bankverbindung',
contactInfo: 'Kontaktinformationen',
description: 'Beschreibung',
invoice: 'Rechnung',
itemPos: 'Pos.',
quantity: 'Anzahl',
registrationInfo: 'HRA/HRB Info',
reverseVatNote: 'Umkehr der Umsatzsteuerpflicht: Der Rechnungsempfänger ist für die korrekte Abrechnung der Umsatzsteuer zuständig.',
totalNetPrice: 'Nettopreis Summe',
unitNetPrice: 'Nettopreis',
unitType: 'Einheit',
yourCustomerId: 'Ihre Kundennummer:',
yourVatId: 'Ihre Umsatzsteuer-ID:',
continuesOnPage: 'Fortsetzung auf Seite',
finalPageStatement: 'Diese ist die letzte Seite einer Dokumente.',
page: 'Seite',
};
export const ES_translations: TTranslationImplementation = {
address: 'Dirección',
bankConnection: 'Conexión bancaria',
contactInfo: 'Información de contacto',
description: 'Descripción',
invoice: 'Factura',
itemPos: 'Pos.',
quantity: 'Cantidad',
registrationInfo: 'Información de registro',
reverseVatNote: 'La declaración de IVA se aplica en base a la factura de venta, y se paga por el cliente.',
totalNetPrice: 'Precio total neto',
unitNetPrice: 'Precio unitario neto',
unitType: 'Tipo de unidad',
yourCustomerId: 'Su número de cliente:',
yourVatId: 'Su ID de IVA:',
continuesOnPage: 'Continues on page',
finalPageStatement: 'This is the final page of this document.',
page: 'Page',
};
export const languageCodeMap = {
'DE': DE_translations,
'EN': EN_translations,
'ES': ES_translations,
};
export type TLanguageCode = keyof typeof languageCodeMap;
export const translate = (languageCode: TLanguageCode, key: string, defaultValue: string) => {
const translations = languageCodeMap[languageCode] || EN_translations;
if (translations && translations[key]) {
return translations[key];
} else {
return defaultValue;
}
};

3
ts/tspublish.json Normal file
View File

@ -0,0 +1,3 @@
{
"order": 2
}

247
ts_shared/demoletter.ts Normal file
View File

@ -0,0 +1,247 @@
import * as plugins from "./plugins.js";
import * as interfaces from "./interfaces/index.js";
const fromContact: plugins.tsclass.business.TContact = {
name: "Awesome From Company",
type: "company",
status: "active",
foundedDate: { day: 1, month: 1, year: 2025 },
description: "a company that does stuff",
address: {
streetName: "Awesome Street",
houseNumber: "5",
city: "Bremen",
country: "Germany",
postalCode: "28359",
},
sepaConnection: {
bic: "BPOTBEB1",
iban: "BE01234567891616",
},
email: "hello@awesome.company",
phone: "+49 421 1234567",
fax: "+49 421 1234568",
registrationDetails: {
registrationId: "HRB 35230 HB",
registrationName: "Amtsgericht Bremen",
vatId: "DE12345678",
},
};
const toContact: plugins.tsclass.business.TContact = {
name: "Awesome To GmbH",
type: "company",
status: "active",
foundedDate: { day: 1, month: 1, year: 2025 },
customerNumber: "LL-CLIENT-123",
description: "a company that does stuff",
address: {
streetName: "Awesome Street",
houseNumber: "5",
city: "Bremen",
country: "Germany",
postalCode: "28359",
},
registrationDetails: {
registrationId: "HRB 35230 HB",
registrationName: "Amtsgericht Bremen",
vatId: "DE12345678",
},
};
export const demoLetter: plugins.tsclass.finance.TInvoice = {
type: "invoice",
id: "LL-INV-48765",
versionInfo: {
version: "1.0.0",
type: "draft",
},
language: "de",
date: Date.now(),
incidenceId: "LL-INV-48765",
invoiceId: "LL-INV-48765",
subject: "LL-INV-48765",
reverseCharge: true,
dueInDays: 30,
from: fromContact,
to: toContact,
status: null,
deliveryDate: new Date().getTime(),
periodOfPerformance: {
from: +new Date().setDate(new Date().getDate() - 7),
to: +new Date(),
},
printResult: null,
currency: "EUR",
notes: [],
invoiceType: "debitnote",
items: [
{
name: "Item with 19% VAT",
unitQuantity: 2,
unitNetPrice: 100,
unitType: "hours",
vatPercentage: 19,
position: 0,
},
{
name: "Item with 7% VAT",
unitQuantity: 4,
unitNetPrice: 100,
unitType: "hours",
vatPercentage: 7,
position: 1,
},
{
name: "Item with 7% VAT",
unitQuantity: 3,
unitNetPrice: 230,
unitType: "hours",
vatPercentage: 7,
position: 2,
},
{
name: "Item with 21% VAT",
unitQuantity: 1,
unitNetPrice: 230,
unitType: "hours",
vatPercentage: 21,
position: 3,
},
{
name: "Item with 0% VAT",
unitQuantity: 6,
unitNetPrice: 230,
unitType: "hours",
vatPercentage: 0,
position: 4,
},
{
name: "Item with 19% VAT",
unitQuantity: 8,
unitNetPrice: 100,
unitType: "hours",
vatPercentage: 19,
position: 5,
},
{
name: "Item with 7% VAT",
unitQuantity: 9,
unitNetPrice: 100,
unitType: "hours",
vatPercentage: 7,
position: 6,
},
{
name: "Item with 7% VAT",
unitQuantity: 4,
unitNetPrice: 230,
unitType: "hours",
vatPercentage: 7,
position: 8,
},
{
name: "Item with 21% VAT",
unitQuantity: 3,
unitNetPrice: 230,
unitType: "hours",
vatPercentage: 21,
position: 9,
},
{
name: "Item with 0% VAT",
unitQuantity: 1,
unitNetPrice: 230,
unitType: "hours",
vatPercentage: 0,
position: 10,
},
{
name: "Item with 0% VAT",
unitQuantity: 1,
unitNetPrice: 230,
unitType: "hours",
vatPercentage: 0,
position: 11,
},
{
name: "Item with 0% VAT",
unitQuantity: 1,
unitNetPrice: 230,
unitType: "hours",
vatPercentage: 0,
position: 12,
},
{
name: "Item with 0% VAT",
unitQuantity: 1,
unitNetPrice: 230,
unitType: "hours",
vatPercentage: 0,
position: 13,
},
{
name: "Item with 0% VAT",
unitQuantity: 1,
unitNetPrice: 230,
unitType: "hours",
vatPercentage: 0,
position: 14,
},
{
name: "Item with 0% VAT",
unitQuantity: 1,
unitNetPrice: 230,
unitType: "hours",
vatPercentage: 0,
position: 15,
},
{
name: "Item with 0% VAT",
unitQuantity: 1,
unitNetPrice: 230,
unitType: "hours",
vatPercentage: 0,
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,
},
],
};
export const demoDocumentSettings: interfaces.IDocumentSettings = {
enableTopDraftText: true,
enableDefaultHeader: true,
enableDefaultFooter: true,
languageCode: "DE",
};

15
ts_shared/index.ts Normal file
View File

@ -0,0 +1,15 @@
const DPI = 96 / 2.54; // <PX> / <INCH>
export const A4_HEIGHT = cmToPx(29.7); // DPI * 29.7cm
export const A4_WIDTH = cmToPx(21); // DPI * 21cm
export function cmToPx(value: number): number {
return DPI * value;
}
import * as interfaces from "./interfaces/index.js";
export { interfaces };
import * as translation from "./translation.js";
export { translation };
export * from "./demoletter.js";

View File

@ -0,0 +1,23 @@
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 {
enableTopDraftText?: boolean;
enableDefaultHeader?: boolean;
enableDefaultFooter?: boolean;
enableFoldMarks?: boolean;
enableInvoiceContractRefSection?: boolean;
languageCode?: translation.LanguageCode;
dateStyle?: Intl.DateTimeFormatOptions["dateStyle"];
vatGroupPositions?: boolean;
theme?: IDocumentTheme;
}

View File

@ -0,0 +1 @@
export * from './document.js';

4
ts_shared/plugins.ts Normal file
View File

@ -0,0 +1,4 @@
// tsclass scope
import * as tsclass from '@tsclass/tsclass';
export { tsclass };

272
ts_shared/translation.ts Normal file
View File

@ -0,0 +1,272 @@
// Define English translations without enforcing TTranslationImplementation yet
export const EN_translations = {
address: "Address",
"bank.accountHolder": "beneficiary",
"bank.bic": "bic",
"bank.iban": "iban",
"bank.institution": "institution",
"bankConnection@@title": "Bank Connection",
"contact@@title": "Contact Info",
"customer.number": "Your Customer ID",
description: "Description",
"empty.logo": "no logo provided",
"empty.number.customer": "not registered",
empty: "not provided",
fax: "Fax",
introStatement: "We hereby invoice the following products and services",
"invoice.number": "Invoice number",
invoice: "Invoice",
"item.position": "Pos.",
mail: "Mail",
"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
/**
* 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
export type Dictionary = {
[key in TranslationKey]: string;
};
// Define German translations
export const DE_translations: Dictionary = {
address: "Adresse",
"bank.accountHolder": "Kontoinhaber",
"bank.bic": "BIC",
"bank.iban": "IBAN",
"bank.institution": "Bankinstitut",
"bankConnection@@title": "Bankverbindung",
"contact@@title": "Kontaktinformationen",
"customer.number": "Ihre Kundennummer",
description: "Beschreibung",
"empty.logo": "Kein Logo gesetzt",
"empty.number.customer": "nicht registriert",
empty: "nicht angegeben",
fax: "Fax",
introStatement:
"Wir stellen Ihnen hiermit folgende Produkte und Dienstleistungen in Rechnung",
"invoice.number": "Rechnungsnr.",
invoice: "Rechnung",
"item.position": "Pos.",
mail: "E-Mail",
"overlay@@draft": "Entwurf",
"page.continueNext": "Fortsetzung auf Seite",
"page.final": "Dies ist die letzte Seite dieses Dokuments.",
page: "Seite",
pageOf: "von",
"payment.qr": "Überweisen per QR-Code",
"payment.qr.description":
"Den QR-Code einfach mit der Banking-App einscannen",
"payment.terms": "Zahlungsbedingungen",
"payment.terms.direct": "Ohne Abzug bis zum",
"periodOfPerformance.day": "Lieferdatum",
"periodOfPerformance.range": "Lieferzeitraum",
phone: "Telefon",
"price.total.net": "Gesamtpreis",
"price.unit.net": "Stückpreis",
price: "Preis",
quantity: "Menge",
referencedContract: "Referenzierter Vertrag",
"referencedContract.text":
"Diese Rechnung bezieht sich auf die getroffenen Vertragsvereinbarungen vom",
"registration.label": "Registrierungsinfo",
subject: "Betreff",
sum: "Summe",
totalGross: "Gesamtbetrag brutto",
"unit.type": "Einheit",
"vat.position": "auf Positionen",
"vat.reverseCharge.note":
"Die Umsatzsteuer entsteht im Reverse-Charge-Verfahren und ist vom Kunden zu zahlen.",
"vat.short": "MwSt.",
"vat.yourId": "Ihre hinterlegte USt-Id",
vat: "Umsatzsteuer",
};
// Define Spanish translations
// export const ES_translations: TTranslationImplementation = {
// address: "Dirección",
// bankConnection: "Conexión bancaria",
// contactInfo: "Información de contacto",
// description: "Descripción",
// invoice: "Factura",
// itemPos: "Pos.",
// quantity: "Cantidad",
// registrationInfo: "Información de registro",
// reverseVatNote:
// "El IVA se aplica por inversión del sujeto pasivo y debe ser pagado por el cliente.",
// totalNetPrice: "Precio total neto",
// unitNetPrice: "Precio unitario neto",
// unitType: "Tipo de unidad",
// yourCustomerId: "Su número de cliente:",
// yourVatId: "Su ID de IVA:",
// continuesOnPage: "Continúa en la página",
// finalPageStatement: "Esta es la última página de este documento.",
// page: "Página",
// vatShort: "IVA",
// };
// Define French translations
// export const FR_translations: TTranslationImplementation = {
// address: "Adresse",
// bankConnection: "Coordonnées bancaires",
// contactInfo: "Informations de contact",
// description: "Description",
// invoice: "Facture",
// 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
export const languageCodeMap: Record<string, Dictionary> = {
EN: EN_translations,
DE: DE_translations,
// ES: ES_translations,
// FR: FR_translations,
// IT: IT_translations,
};
// Language Code Type
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
export const translate = (
languageCode: LanguageCode,
key: TranslationKey
): string => {
const dictionary = languageCodeMap[languageCode] || EN_translations;
const lookupHierarchy = getTranslationKeyHierarchy(key);
let found: string;
for (let keyOption of lookupHierarchy) {
found = dictionary[keyOption] || EN_translations[keyOption];
if (found) {
break;
}
}
return found;
};

3
ts_shared/tspublish.json Normal file
View File

@ -0,0 +1,3 @@
{
"order": 1
}

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@design.estate/dees-document', name: '@design.estate/dees-document',
version: '1.3.1', version: '1.6.11',
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.'
} }

View File

@ -9,23 +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 * as shared from '../../ts/shared/index.js';
import * as interfaces from '../../ts/interfaces/index.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>
@ -43,13 +41,13 @@ export class DeContentInvoice extends DeesElement {
type: Object, type: Object,
reflect: true, reflect: true,
}) })
public letterData: plugins.tsclass.business.ILetter; public letterData: plugins.tsclass.finance.TInvoice;
@property({ @property({
type: Object, type: Object,
reflect: true, reflect: true,
}) })
public documentSettings: interfaces.IDocumentSettings; public documentSettings: plugins.shared.interfaces.IDocumentSettings;
constructor() { constructor() {
super(); super();
@ -58,16 +56,98 @@ export class DeContentInvoice extends DeesElement {
public static styles = [ public static styles = [
domtools.elementBasic.staticStyles, domtools.elementBasic.staticStyles,
dedocumentSharedStyle,
css` css`
:host {
color: #333;
}
.trimmedContent { .trimmedContent {
display: none; display: none;
} }
.repeatedContent { .grid {
display: grid;
grid-template-columns: 40px auto 50px 50px 100px 50px 100px;
}
.topLine {
margin-top: 5px;
background: #e7e7e7;
font-weight: bold;
}
.lineItem {
font-size: 12px;
padding: 5px;
border-right: 1px dashed #ccc;
white-space: nowrap;
}
.lineItem:last-child {
border-right: none;
}
.value.rightAlign,
.lineItem.rightAlign {
text-align: right;
}
.invoiceLine {
background: #ffffff00;
border-bottom: 1px dotted #ccc;
}
.invoiceLine.highlighted {
transition: background 0.2s;
background: #ffc18f;
border: 1px solid #ff9d4d;
box-sizing: content-box;
}
.sums {
margin-top: 5px;
font-size: 12px;
padding-left: 50%;
}
.sums .sumline {
margin-top: 3px;
display: grid;
grid-template-columns: auto 100px;
}
.sums .sumline .label {
padding: 2px 5px;
border-right: 1px solid #ccc;
text-align: right;
font-weight: bold;
}
.sums .sumline .value {
padding: 2px 5px;
}
.sums .sumline .value--total {
font-weight: bold;
}
.divider {
margin-top: 8px;
border-top: 1px dotted #ccc;
}
.taxNote {
font-size: 12px;
padding: 4px;
background: #eeeeeb;
text-align: center;
}
.infoBox {
margin-top: 22px;
line-height: 1.4em;
}
.infoBox .label {
padding-bottom: 2px;
font-weight: bold;
} }
`, `,
]; ];
@ -80,6 +160,17 @@ export class DeContentInvoice extends DeesElement {
`; `;
} }
protected formatPrice(
value: number,
currency = "EUR",
lang = "de-DE"
): string {
return new Intl.NumberFormat(lang, {
style: "currency",
currency,
}).format(value);
}
public getTotalNet = (): number => { public getTotalNet = (): number => {
let totalNet = 0; let totalNet = 0;
@ -87,7 +178,7 @@ export class DeContentInvoice extends DeesElement {
return totalNet; return totalNet;
} }
for (const item of this.letterData.content.invoiceData.items) { for (const item of this.letterData.items) {
totalNet += item.unitNetPrice * item.unitQuantity; totalNet += item.unitNetPrice * item.unitQuantity;
} }
return totalNet; return totalNet;
@ -109,7 +200,7 @@ export class DeContentInvoice extends DeesElement {
public getVatGroups = () => { public getVatGroups = () => {
const vatGroups: { const vatGroups: {
vatPercentage: number; vatPercentage: number;
items: plugins.tsclass.finance.IInvoice['items']; items: plugins.tsclass.finance.TInvoice["items"];
vatAmountSum: number; vatAmountSum: number;
}[] = []; }[] = [];
@ -118,17 +209,22 @@ export class DeContentInvoice extends DeesElement {
} }
const taxAmounts: number[] = []; const taxAmounts: number[] = [];
for (const item of this.letterData.content.invoiceData.items) { for (const item of this.letterData.items) {
taxAmounts.includes(item.vatPercentage) ? null : taxAmounts.push(item.vatPercentage); taxAmounts.includes(item.vatPercentage)
? null
: taxAmounts.push(item.vatPercentage);
} }
for (const taxAmount of taxAmounts) { for (const taxAmount of taxAmounts) {
const matchingItems = this.letterData.content.invoiceData.items.filter( const matchingItems = this.letterData.items.filter(
(itemArg) => itemArg.vatPercentage === taxAmount (itemArg) => itemArg.vatPercentage === taxAmount
); );
let sum = 0; let sum = 0;
for (const matchingItem of matchingItems) { for (const matchingItem of matchingItems) {
sum += matchingItem.unitNetPrice * matchingItem.unitQuantity * (taxAmount / 100); sum +=
matchingItem.unitNetPrice *
matchingItem.unitQuantity *
(taxAmount / 100);
} }
vatGroups.push({ vatGroups.push({
items: matchingItems, items: matchingItems,
@ -136,15 +232,21 @@ export class DeContentInvoice extends DeesElement {
vatAmountSum: Math.round(sum * 100) / 100, vatAmountSum: Math.round(sum * 100) / 100,
}); });
} }
return vatGroups; return vatGroups.sort((a, b) => b.vatPercentage - a.vatPercentage);
}; };
public async getContentNodes() { public async getContentNodes() {
await this.elementDomReady; await this.elementDomReady;
return { return {
currentContent: this.shadowRoot.querySelector('.currentContent') as HTMLElement, currentContent: this.shadowRoot.querySelector(
trimmedContent: this.shadowRoot.querySelector('.trimmedContent') as HTMLElement, ".currentContent"
repeatedContent: this.shadowRoot.querySelector('.repeatedContent') as HTMLElement, ) as HTMLElement,
trimmedContent: this.shadowRoot.querySelector(
".trimmedContent"
) as HTMLElement,
repeatedContent: this.shadowRoot.querySelector(
".repeatedContent"
) as HTMLElement,
}; };
} }
@ -156,7 +258,7 @@ export class DeContentInvoice extends DeesElement {
public async trimEndByOne() { public async trimEndByOne() {
await this.elementDomReady; await this.elementDomReady;
this.shadowRoot this.shadowRoot
.querySelector('.trimmedContent') .querySelector(".trimmedContent")
.append( .append(
(await this.getContentNodes()).currentContent.children.item( (await this.getContentNodes()).currentContent.children.item(
(await this.getContentNodes()).currentContent.children.length - 1 (await this.getContentNodes()).currentContent.children.length - 1
@ -166,7 +268,8 @@ export class DeContentInvoice extends DeesElement {
public async trimStartToOffset(contentOffsetArg: number) { public async trimStartToOffset(contentOffsetArg: number) {
await this.elementDomReady; await this.elementDomReady;
const beginningLength = (await this.getContentNodes()).currentContent.children.length; const beginningLength = (await this.getContentNodes()).currentContent
.children.length;
while ( while (
(await this.getContentNodes()).currentContent.children.length !== (await this.getContentNodes()).currentContent.children.length !==
beginningLength - contentOffsetArg beginningLength - contentOffsetArg
@ -174,18 +277,17 @@ export class DeContentInvoice extends DeesElement {
(await this.getContentNodes()).trimmedContent.append( (await this.getContentNodes()).trimmedContent.append(
(await this.getContentNodes()).currentContent.children.item(0) (await this.getContentNodes()).currentContent.children.item(0)
); );
console.log('hey' + this.shadowRoot.children.length);
} }
if ( if (
(await this.getContentNodes()).currentContent.children (await this.getContentNodes()).currentContent.children
.item(0) .item(0)
.classList.contains('needsDataHeader') .classList.contains("needsDataHeader")
) { ) {
const trimmedContent = (await this.getContentNodes()).trimmedContent; const trimmedContent = (await this.getContentNodes()).trimmedContent;
let startPoint = trimmedContent.children.length; let startPoint = trimmedContent.children.length;
while (startPoint > 0) { while (startPoint > 0) {
const element = trimmedContent.children.item(startPoint - 1); const element = trimmedContent.children.item(startPoint - 1);
if (element.classList.contains('dataHeader')) { if (element.classList.contains("dataHeader")) {
(await this.getContentNodes()).repeatedContent.append(element); (await this.getContentNodes()).repeatedContent.append(element);
break; break;
} }
@ -194,197 +296,194 @@ export class DeContentInvoice extends DeesElement {
} }
} }
public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) { 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); super.firstUpdated(_changedProperties);
this.attachInvoiceDom(); 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() { public async attachInvoiceDom() {
const contentNodes = await this.getContentNodes(); const contentNodes = await this.getContentNodes();
render( render(
html` html`
<style> <div>${this.translateKey("invoice@@introStatement")}</div>
.grid {
display: grid;
grid-template-columns: 40px auto 80px 80px 90px 90px;
}
.topLine {
margin-top: 5px;
background: #e7e7e7;
font-weight: bold;
}
.lineItem {
font-size: 12px;
padding: 5px;
}
.invoiceLine {
border-bottom: 1px dotted #ccc;
}
.sums {
margin-top: 5px;
font-size: 12px;
padding-left: 50%;
}
.sums .sumline {
margin-top: 3px;
display: grid;
grid-template-columns: auto 90px;
}
.sums .sumline .label {
padding: 2px 5px;
border-right: 1px solid #ccc;
text-align: right;
font-weight: bold;
}
.sums .sumline .value {
padding: 2px 5px;
font-weight: bold;
}
.divider {
margin-top: 8px;
border-top: 1px dotted #ccc;
}
.taxNote {
font-size: 12px;
padding: 4px;
background: #eeeeeb;
text-align: center;
}
.infoBox {
border-radius: 7px;
border: 1px solid #ddd;
border-left: 3px solid #c5e1a5;
padding: 8px;
margin-top: 16px;
line-height: 1.4em;
font-size: 16px;
}
.infoBox .label {
padding-bottom: 2px;
font-size: 12px;
font-weight: bold;
}
.paymentCode {
text-align: center;
border-left: 2px solid #666;
}
</style>
<div>We hereby invoice products and services provided to you by Lossless GmbH:</div>
<div class="grid topLine dataHeader"> <div class="grid topLine dataHeader">
<div class="lineItem">${shared.translation.translate(this.documentSettings.languageCode, 'itemPos', 'Item Pos.')}</div> <div class="lineItem rightAlign">
<div class="lineItem">${shared.translation.translate(this.documentSettings.languageCode, 'description', 'Description')}</div> ${this.translateKey("invoice@@item.position")}
<div class="lineItem">${shared.translation.translate(this.documentSettings.languageCode, 'quantity', 'Quantity')}</div>
<div class="lineItem">${shared.translation.translate(this.documentSettings.languageCode, 'unitType', 'Unit Type')}</div>
<div class="lineItem">${shared.translation.translate(this.documentSettings.languageCode, 'unitNetPrice', 'Unit Net Price')}</div>
<div class="lineItem">${shared.translation.translate(this.documentSettings.languageCode, 'totalNetPrice', 'Total Net Price')}</div>
</div> </div>
${(() => {
let counter = 1;
return this.letterData?.content.invoiceData?.items?.map(
(invoiceItem) => html`
<div class="grid invoiceLine needsDataHeader">
<div class="lineItem">${counter++}</div>
<div class="lineItem">${invoiceItem.name}</div>
<div class="lineItem">${invoiceItem.unitQuantity}</div>
<div class="lineItem">${invoiceItem.unitType}</div>
<div class="lineItem">${invoiceItem.unitNetPrice} ${this.letterData?.content.invoiceData.currency}</div>
<div class="lineItem"> <div class="lineItem">
${invoiceItem.unitQuantity * invoiceItem.unitNetPrice} ${this.letterData?.content.invoiceData.currency} ${this.translateKey("invoice@@description")}
</div>
<div class="lineItem rightAlign">
${this.translateKey("invoice@@quantity")}
</div>
<div class="lineItem">${this.translateKey("invoice@@unit.type")}</div>
<div class="lineItem rightAlign">
${this.translateKey("invoice@@price.unit.net")}
</div>
<div class="lineItem rightAlign">
${this.translateKey("invoice@@vat.short")}
</div>
<div class="lineItem rightAlign">
${this.translateKey("invoice@@price.total.net")}
</div>
</div>
${this.letterData?.items?.map(
(invoiceItem, index) => html`
<div class="grid needsDataHeader">
<div class="lineItem rightAlign">${index + 1}</div>
<div class="lineItem">${invoiceItem.name}</div>
<div class="lineItem rightAlign">${invoiceItem.unitQuantity}</div>
<div class="lineItem">${invoiceItem.unitType}</div>
<div class="lineItem rightAlign">
${this.formatPrice(invoiceItem.unitNetPrice)}
</div>
<div class="lineItem rightAlign">
${invoiceItem.vatPercentage}%
</div>
<div class="lineItem rightAlign">
${this.formatPrice(
invoiceItem.unitQuantity * invoiceItem.unitNetPrice
)}
</div> </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
for (const item of vatGroupArg.items) { .map((item) => this.letterData.items.indexOf(item) + 1)
const itemIndex = this.letterData.content.invoiceData.items.indexOf(item); .join(", ");
itemNumbers += ` ${itemIndex + 1},`;
}
return html` return html`
<div class="sumline"> <div class="sumline">
<div class="label"> <div class="label">
Vat ${vatGroupArg.vatPercentage}%<br /> ${this.translateKey("vat.short")}
<span style="font-weight: normal">(on item positions: ${itemNumbers})</span> ${vatGroupArg.vatPercentage}%
${this.documentSettings.vatGroupPositions
? html`
<br /><span style="font-weight: normal"
>(${this.translateKey("invoice@@vat.position")}:
${itemNumbers})</span
>
`
: html``}
</div>
<div class="value rightAlign">
${this.formatPrice(vatGroupArg.vatAmountSum)}
</div> </div>
<div class="value">${vatGroupArg.vatAmountSum} EUR</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">
${shared.translation.translate(this.documentSettings.languageCode, 'reverseVatNote', 'VAT arises on a reverse charge basis and is payable by the customer.')} ${this.translateKey("invoice@@vat.reverseCharge.note")}
</div> </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);
console.log('success!');
}
);
contentNodes.currentContent.querySelector('.paymentCode').append(canvas);
} }
} }

View File

@ -1,6 +1,10 @@
import * as shared from '../../ts/shared/index.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=${shared.demoLetter}></dedocument-dedocument> <dedocument-dedocument
.format="${"a4"}"
.letterData=${plugins.shared.demoLetter}
></dedocument-dedocument>
`; `;

View File

@ -5,35 +5,36 @@ 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 interfaces from '../../ts/interfaces/index.js'; export const defaultDocumentSettings: plugins.shared.interfaces.IDocumentSettings =
import * as plugins from '../plugins.js'; {
export const defaultDocumentSettings: interfaces.IDocumentSettings = {
enableTopDraftText: true, enableTopDraftText: true,
enableDefaultHeader: true, enableDefaultHeader: true,
enableDefaultFooter: true, enableDefaultFooter: true,
enableFoldMarks: true,
enableInvoiceContractRefSection: true,
languageCode: "EN",
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 { dedocumentSharedStyle } from "../style.js";
import type { TInvoice } from "@tsclass/tsclass/dist_ts/finance/invoice.js";
import * as shared from '../../ts/shared/index.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;
@ -41,7 +42,7 @@ export class DeDocument extends DeesElement {
type: String, type: String,
reflect: true, reflect: true,
}) })
public format: 'a4' = 'a4'; public format: "a4" = "a4";
@property({ @property({
type: Number, type: Number,
@ -65,27 +66,28 @@ export class DeDocument extends DeesElement {
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; public 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: interfaces.IDocumentSettings = defaultDocumentSettings; public documentSettings: plugins.shared.interfaces.IDocumentSettings =
defaultDocumentSettings;
constructor() { constructor() {
super(); super();
@ -94,12 +96,10 @@ export class DeDocument extends DeesElement {
public static styles = [ public static styles = [
domtools.elementBasic.staticStyles, domtools.elementBasic.staticStyles,
dedocumentSharedStyle,
css` css`
:host { :host {
display: block; display: block;
color: #333;
padding: 0px;
position: relative;
} }
.betweenPagesSpacer { .betweenPagesSpacer {
@ -109,12 +109,18 @@ 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 () => {
this.documentSettings = {
...defaultDocumentSettings,
...this.documentSettings,
};
});
const resizeObserver = new ResizeObserver((entries) => { const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) { for (const entry of entries) {
const width = entry.contentRect.width; const width = entry.contentRect.width;
@ -126,17 +132,24 @@ export class DeDocument extends DeesElement {
resizeObserver.observe(this); resizeObserver.observe(this);
this.registerGarbageFunction(() => { this.registerGarbageFunction(() => {
resizeObserver.disconnect(); resizeObserver.disconnect();
}) });
} }
public latestRenderedLetterData: plugins.tsclass.business.ILetter = null; public latestDocumentSettings: plugins.shared.interfaces.IDocumentSettings =
null;
public latestRenderedLetterData: plugins.tsclass.business.TLetter = null;
public cleanupStore: any[] = [];
public async renderDocument() { public async renderDocument() {
this.latestDocumentSettings = this.documentSettings;
this.latestRenderedLetterData = this.letterData;
const cleanUpStoreCurrentRender = [];
const cleanUpStoreNextRender = [];
const domtools = await this.domtoolsPromise; const domtools = await this.domtoolsPromise;
const documentBuildContainer = document.createElement("div");
cleanUpStoreCurrentRender.push(documentBuildContainer);
const documentBuildContainer = document.createElement('div');
document.body.appendChild(documentBuildContainer); document.body.appendChild(documentBuildContainer);
let pages: DePage[] = []; let pages: DePage[] = [];
@ -145,7 +158,8 @@ export class DeDocument extends DeesElement {
// lets append the content // lets append the content
const content: DeContentInvoice = new DeContentInvoice(); const content: DeContentInvoice = new DeContentInvoice();
content.letterData = this.letterData; cleanUpStoreCurrentRender.push(content);
content.letterData = this.letterData as unknown as TInvoice;
content.documentSettings = this.documentSettings; content.documentSettings = this.documentSettings;
document.body.appendChild(content); document.body.appendChild(content);
@ -156,6 +170,7 @@ export class DeDocument extends DeesElement {
while (!complete) { while (!complete) {
pageCounter++; pageCounter++;
const currentContent = content.cloneNode(true) as DeContentInvoice; const currentContent = content.cloneNode(true) as DeContentInvoice;
cleanUpStoreNextRender.push(currentContent);
const newPage = new DePage(); const newPage = new DePage();
newPage.printMode = this.printMode; newPage.printMode = this.printMode;
newPage.letterData = this.letterData; newPage.letterData = this.letterData;
@ -164,6 +179,9 @@ export class DeDocument extends DeesElement {
newPage.pageNumber = pageCounter; newPage.pageNumber = pageCounter;
newPage.append(currentContent); newPage.append(currentContent);
newPage.pageTotalNumber = pageCounter; newPage.pageTotalNumber = pageCounter;
// store current page
cleanUpStoreNextRender.push(newPage);
documentBuildContainer.append(newPage); documentBuildContainer.append(newPage);
await currentContent.elementDomReady; await currentContent.elementDomReady;
@ -180,40 +198,61 @@ export class DeDocument extends DeesElement {
if (trimmed === 0) { if (trimmed === 0) {
complete = true; complete = true;
} }
// complete = true;
console.log(currentContentOffset);
} }
document.body.removeChild(content);
document.body.removeChild(documentBuildContainer);
const documentContainer = this.shadowRoot.querySelector('.documentContainer'); for (const cleanUp of this.cleanupStore) {
cleanUp.remove();
}
this.cleanupStore = cleanUpStoreNextRender;
cleanUpStoreCurrentRender.forEach((cleanUp) => {
cleanUp.remove();
});
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) => documentContainer.removeChild(child)); children.forEach((child) => {
documentContainer.removeChild(child);
child.remove();
});
} }
for (const page of pages) { for (const page of pages) {
page.pageTotalNumber = pageCounter; page.pageTotalNumber = pageCounter;
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();
this.latestRenderedLetterData = this.letterData;
} }
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;
const renderedDocIsUpToDate = domtools.convenience.smartjson.deepEqualObjects(this.letterData, this.latestRenderedLetterData); let renderedDocIsUpToDate =
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();
} }
} }
@ -224,7 +263,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) => {
@ -233,7 +272,6 @@ export class DeDocument extends DeesElement {
} }
if (this.viewWidth) { if (this.viewWidth) {
page.viewWidth = this.viewWidth; page.viewWidth = this.viewWidth;
console.log('setting viewWidth: ', this.viewWidth);
} }
}); });
} }

View File

@ -1,11 +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";
export * from '../../ts/shared/index.js';

View File

@ -5,31 +5,33 @@ import {
customElement, customElement,
type TemplateResult, type TemplateResult,
css, css,
cssManager,
unsafeCSS, unsafeCSS,
} from '@design.estate/dees-element'; domtools,
import * as domtools from '@design.estate/dees-domtools'; } from "@design.estate/dees-element";
import * as shared from '../../ts/shared/index.js'; import * as plugins from "../plugins.js";
import * as tsclass from '@tsclass/tsclass'; 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=${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: tsclass.business.ILetter; public letterData: plugins.tsclass.finance.TInvoice;
@property({ @property({
type: Number, type: Number,
@ -43,6 +45,12 @@ export class DeLetterHeader extends DeesElement {
}) })
public pageTotalNumber: number = 1; public pageTotalNumber: number = 1;
@property({
type: Object,
reflect: true,
})
public documentSettings: plugins.shared.interfaces.IDocumentSettings;
constructor() { constructor() {
super(); super();
domtools.DomTools.setupDomTools(); domtools.DomTools.setupDomTools();
@ -50,20 +58,31 @@ export class DeLetterHeader extends DeesElement {
public static styles = [ public static styles = [
domtools.elementBasic.staticStyles, domtools.elementBasic.staticStyles,
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(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;
@ -71,19 +90,6 @@ export class DeLetterHeader extends DeesElement {
font-weight: bold; font-weight: bold;
} }
.date {
position: absolute;
top: 180px;
right: ${unsafeCSS(shared.rightMargin + 'px')};
text-align: right;
}
.address {
position: absolute;
top: 180px;
left: ${unsafeCSS(shared.leftMargin + 'px')};
}
.address .from { .address .from {
font-size: 10px; font-size: 10px;
} }
@ -94,31 +100,93 @@ export class DeLetterHeader extends DeesElement {
`, `,
]; ];
private renderDeliveryDate(from: Date, to: Date): TemplateResult {
if (this.letterData.type !== "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.type === "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>
`; `;
} }

View File

@ -1,4 +1,4 @@
import * as tsclass from '@tsclass/tsclass'; import * as tsclass from "@tsclass/tsclass";
import { import {
DeesElement, DeesElement,
property, property,
@ -6,26 +6,24 @@ 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 interfaces from '../../ts/interfaces/index.js'; import * as plugins from "../plugins.js";
import { defaultDocumentSettings } from './document.js'; import { defaultDocumentSettings } from "./document.js";
import { dedocumentSharedStyle } from "../style.js";
import * as shared from '../../ts/shared/index.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,
@ -40,7 +38,7 @@ export class DePage extends DeesElement {
@property({ @property({
type: String, type: String,
}) })
public format: 'a4' = 'a4'; public format: "a4" = "a4";
@property({ @property({
type: Number, type: Number,
@ -55,7 +53,7 @@ export class DePage extends DeesElement {
@property({ @property({
type: Object, type: Object,
}) })
public letterData: tsclass.business.ILetter = null; public letterData: tsclass.business.TLetter = null;
@property({ @property({
type: Boolean, type: Boolean,
@ -67,7 +65,8 @@ export class DePage extends DeesElement {
type: Object, type: Object,
reflect: true, reflect: true,
}) })
public documentSettings: interfaces.IDocumentSettings = defaultDocumentSettings; public documentSettings: plugins.shared.interfaces.IDocumentSettings =
defaultDocumentSettings;
constructor() { constructor() {
super(); super();
@ -76,9 +75,11 @@ export class DePage extends DeesElement {
public static styles = [ public static styles = [
domtools.elementBasic.staticStyles, domtools.elementBasic.staticStyles,
dedocumentSharedStyle,
css` css`
:host { :host {
display: block; display: block;
overflow: hidden;
} }
#scaleWrapper { #scaleWrapper {
@ -101,26 +102,75 @@ 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 {
return html` return html`
<style>
:host {
--theme-color-primary-fg: ${this.documentSettings.theme
?.colorPrimaryForeground};
--theme-color-primary-bg: ${this.documentSettings.theme
?.colorPrimaryBackground};
--theme-color-accent-fg: ${this.documentSettings.theme
?.colorAccentForeground};
--theme-color-accent-bg: ${this.documentSettings.theme
?.colorAccentBackground};
}
.page {
background-size: contain;
height: 100%;
}
.page:not(.page--first) {
background-image: ${this.documentSettings.theme?.pageBackground ??
"none"};
}
.page.page--first {
background-image: ${this.documentSettings.theme
?.coverPageBackground ??
this.documentSettings.theme?.pageBackground ??
"none"};
}
</style>
<div id="scaleWrapper"> <div id="scaleWrapper">
<dedocument-pagecontainer .printMode=${this.printMode}> <dedocument-pagecontainer .printMode=${this.printMode}>
<div
class="page page__background ${this.pageNumber === 1
? "page--first"
: ""}"
></div>
${this.letterData ${this.letterData
? html` ? html`
${this.documentSettings.enableDefaultHeader ${this.documentSettings.enableDefaultHeader
@ -132,7 +182,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
@ -142,7 +203,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}
@ -150,7 +213,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}
@ -159,22 +224,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> `}
@ -185,35 +247,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() {
console.log('page scale adjustment triggered.'); const scaleWrapper: HTMLDivElement =
const scaleWrapper: HTMLDivElement = this.shadowRoot.querySelector('#scaleWrapper'); 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 / shared.a4Height; scale = this.viewHeight / plugins.shared.A4_HEIGHT;
} else if (this.viewWidth) { } else if (this.viewWidth) {
scale = this.viewWidth / shared.a4Width; scale = this.viewWidth / plugins.shared.A4_WIDTH;
} }
console.log(`new scale is ${scale}`);
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 = `${shared.a4Width * scale}px`; this.style.width = `${plugins.shared.A4_WIDTH * scale}px`;
this.style.height = `${shared.a4Height * scale}px`; this.style.height = `${plugins.shared.A4_HEIGHT * scale}px`;
} }
} }

View File

@ -7,27 +7,27 @@ 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 shared from '../../ts/shared/index.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'; public format: "a4" = "a4";
@property({ @property({
type: Boolean, type: Boolean,
@ -44,11 +44,9 @@ export class DePageContainer extends DeesElement {
css` css`
:host { :host {
display: block; display: block;
background: white;
color: #333;
padding: 0px; padding: 0px;
width: ${unsafeCSS(shared.a4Width + 'px')}; width: ${unsafeCSS(plugins.shared.A4_WIDTH + "px")};
height: ${unsafeCSS(shared.a4Height + 'px')}; height: ${unsafeCSS(plugins.shared.A4_HEIGHT + "px")};
position: relative; position: relative;
border-radius: 3px; border-radius: 3px;
overflow: hidden; overflow: hidden;
@ -60,7 +58,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>

View File

@ -7,28 +7,28 @@ import {
css, css,
cssManager, cssManager,
unsafeCSS, unsafeCSS,
} from '@design.estate/dees-element'; domtools,
import * as domtools from '@design.estate/dees-domtools'; } from "@design.estate/dees-element";
import * as shared from '../../ts/shared/index.js'; import * as plugins from "../plugins.js";
import * as tsclass from '@tsclass/tsclass'; 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: tsclass.business.ILetter; public letterData: plugins.tsclass.business.TLetter;
@property({ @property({
type: Number, type: Number,
@ -40,6 +40,12 @@ export class DePageContent extends DeesElement {
}) })
public pageTotalNumber: number = 1; public pageTotalNumber: number = 1;
@property({
type: Object,
reflect: true,
})
public documentSettings: plugins.shared.interfaces.IDocumentSettings;
constructor() { constructor() {
super(); super();
domtools.DomTools.setupDomTools(); domtools.DomTools.setupDomTools();
@ -47,20 +53,25 @@ export class DePageContent extends DeesElement {
public static styles = [ public static styles = [
domtools.elementBasic.staticStyles, domtools.elementBasic.staticStyles,
dedocumentSharedStyle,
css` css`
:host {
color: #333;
}
.content { .content {
position: absolute; position: absolute;
left: ${unsafeCSS(shared.leftMargin + 'px')}; left: var(--LEFT-MARGIN);
right: ${unsafeCSS(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;
@ -82,65 +93,34 @@ 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) {
console.log('overflows');
return true; return true;
} else { } else {
console.log('does not overflow!');
return false; return false;
} }
} }

View File

@ -5,46 +5,43 @@ 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 interfaces from '../../ts/interfaces/index.js'; import * as plugins from "../plugins.js";
import { dedocumentSharedStyle } from "../style.js";
import * as shared from '../../ts/shared/index.js';
import * as tsclass from '@tsclass/tsclass';
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: tsclass.business.ILetter; letterData: plugins.tsclass.business.TLetter;
@property({ @property({
type: Object, type: Object,
reflect: true, reflect: true,
}) })
documentSettings: interfaces.IDocumentSettings; documentSettings: plugins.shared.interfaces.IDocumentSettings;
@property({ @property({
type: Number type: Number,
}) })
public pageNumber: number = 1; public pageNumber: number = 1;
@property({ @property({
type: Number type: Number,
}) })
public pageTotalNumber: number = 1; public pageTotalNumber: number = 1;
@ -55,6 +52,7 @@ export class DePageFooter extends DeesElement {
public static styles = [ public static styles = [
domtools.elementBasic.staticStyles, domtools.elementBasic.staticStyles,
dedocumentSharedStyle,
css` css`
:host { :host {
color: #333; color: #333;
@ -68,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(shared.rightMargin + 'px')} 10px ${unsafeCSS(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(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(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;
} }
@ -104,36 +106,113 @@ export class DePageFooter extends DeesElement {
return html` return html`
<div class="bottomstripe"> <div class="bottomstripe">
<div> <div>
<strong>${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>${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>${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>${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>
`; `;
} }

View File

@ -7,36 +7,34 @@ import {
css, css,
cssManager, cssManager,
unsafeCSS, unsafeCSS,
domtools domtools,
} from '@design.estate/dees-element'; } from "@design.estate/dees-element";
import * as interfaces from '../../ts/interfaces/index.js'; import * as plugins from "../plugins.js";
import { dedocumentSharedStyle } from "../style.js";
import * as shared from '../../ts/shared/index.js';
import * as tsclass from '@tsclass/tsclass';
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: tsclass.business.ILetter = null; public letterData: plugins.tsclass.business.TLetter = null;
@property({ @property({
type: Object, type: Object,
reflect: true, reflect: true,
}) })
documentSettings: interfaces.IDocumentSettings; documentSettings: plugins.shared.interfaces.IDocumentSettings;
@property({ @property({
type: Number, type: Number,
@ -55,6 +53,7 @@ export class DePageHeader extends DeesElement {
public static styles = [ public static styles = [
domtools.elementBasic.staticStyles, domtools.elementBasic.staticStyles,
dedocumentSharedStyle,
css` css`
:host { :host {
color: #333; color: #333;
@ -76,7 +75,7 @@ export class DePageHeader extends DeesElement {
overflow: hidden; overflow: hidden;
top: 130px; top: 130px;
left: auto; left: auto;
right: ${unsafeCSS(shared.rightMargin + 'px')}; right: var(--RIGHT-MARGIN);
height: 20px; height: 20px;
line-height: 20px; line-height: 20px;
color: #333; color: #333;
@ -87,8 +86,8 @@ export class DePageHeader extends DeesElement {
bottom: 10px; bottom: 10px;
height: 25px; height: 25px;
left: auto; left: auto;
right: ${unsafeCSS(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;
@ -101,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>
`; `;
} }
} }

View 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")
private canvasEl!: HTMLCanvasElement;
@property()
public bic: string;
@property()
public name: string;
@property()
public iban: string;
@property()
public currency: string;
@property({ type: Number })
public totalGross: number;
@property()
public 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;
}
}

View File

@ -1,6 +1,9 @@
import { html } from '@design.estate/dees-element'; import { html } from "@design.estate/dees-element";
import * as shared from '../../ts/shared/index.js'; import * as plugins from "../plugins.js";
export const demoFunc = () => html` export const demoFunc = () => html`
<dedocument-viewer .letterData=${shared.demoLetter} .documentSettings=${shared.demoDocumentSettings}></dedocument-viewer> <dedocument-viewer
.letterData=${plugins.shared.demoLetter}
.documentSettings=${plugins.shared.demoDocumentSettings}
></dedocument-viewer>
`; `;

View File

@ -1,24 +1,38 @@
import * as plugins from '../plugins.js'; import * as plugins from "../plugins.js";
import * as interfaces from '../../ts/interfaces/index.js';
import { DeesElement, css, cssManager, customElement, html } 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;
// INSTANCE // INSTANCE
public letterData: plugins.tsclass.business.ILetter = null; @property({
type: Object,
reflect: true,
})
public letterData: plugins.tsclass.business.TLetter = null;
public documentSettings: interfaces.IDocumentSettings; @property({
type: Object,
reflect: true,
})
public documentSettings: plugins.shared.interfaces.IDocumentSettings;
public static styles = [ public static styles = [
cssManager.defaultStyles, cssManager.defaultStyles,
@ -27,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;
@ -36,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 {
@ -70,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>
@ -78,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")) {
} }
}; };
} }

2
ts_web/pages/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './page1.js';
export * from './page2.js';

21
ts_web/pages/page1.ts Normal file
View File

@ -0,0 +1,21 @@
import * as plugins from "../plugins.js";
import { html } from "@design.estate/dees-element";
export const page1 = () => html`
<style>
dedocument-dedocument {
margin: 16px;
}
</style>
<dedocument-dedocument
.printMode=${false}
letterData=${plugins.smartjson.stringifyBase64({
...plugins.shared.demoLetter,
from: {
...plugins.shared.demoLetter.from,
description: "a string set via stringified JSON",
},
} as plugins.tsclass.finance.TInvoice)}
>
</dedocument-dedocument>
`;

18
ts_web/pages/page2.ts Normal file
View File

@ -0,0 +1,18 @@
import { html } from "@design.estate/dees-element";
export const page2 = () => html`
<style>
.demoPort {
margin: auto;
width: 400px;
}
dedocument-page {
margin-top: 16px;
margin-bottom: 16px;
}
</style>
<div class="demoPort">
<dedocument-page .format="${"a4"}" .viewWidth=${400}></dedocument-page>
<dedocument-page .format="${"a4"}" .viewWidth=${400}></dedocument-page>
</div>
`;

View File

@ -1,3 +1,8 @@
// dees-document scope
import * as shared from '../dist_ts_shared/index.js';
export { shared };
// tsclass scope // tsclass scope
import * as tsclass from '@tsclass/tsclass'; import * as tsclass from '@tsclass/tsclass';

39
ts_web/style.ts Normal file
View File

@ -0,0 +1,39 @@
import { css } from "@design.estate/dees-element";
import * as plugins from "./plugins.js";
export const dedocumentSharedStyle = css`
:host {
/* 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);
--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);
background: var(--text-bg-color);
font-family: var(--text-font-family);
font-size: var(--text-font-size);
}
`;

3
ts_web/tspublish.json Normal file
View File

@ -0,0 +1,3 @@
{
"order": 3
}

View File

@ -6,7 +6,13 @@
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"esModuleInterop": true, "esModuleInterop": true,
"verbatimModuleSyntax": true "verbatimModuleSyntax": true,
"baseUrl": ".",
"paths": {
"undefined": [
"./ts_web/index.js"
]
}
}, },
"exclude": [ "exclude": [
"dist_*/**/*.d.ts" "dist_*/**/*.d.ts"