fix(core): update

This commit is contained in:
Philipp Kunz 2023-10-16 18:28:12 +02:00
commit 20ffd067b2
37 changed files with 7618 additions and 0 deletions

View File

@ -0,0 +1,66 @@
name: Default (not tags)
on:
push:
tags-ignore:
- '**'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
- name: Run npm prepare
run: npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build

View File

@ -0,0 +1,124 @@
name: Default (tags)
on:
push:
tags:
- '*'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build
release:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Release
run: |
npmci node install stable
npmci npm publish
metadata:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
continue-on-error: true
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Code quality
run: |
npmci command npm install -g typescript
npmci npm install
- name: Trigger
run: npmci trigger
- name: Build docs and upload artifacts
run: |
npmci node install stable
npmci npm install
pnpm install -g @git.zone/tsdoc
npmci command tsdoc
continue-on-error: true

20
.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
.nogit/
# artifacts
coverage/
public/
pages/
# installs
node_modules/
# caches
.yarn/
.cache/
.rpt2_cache
# builds
dist/
dist_*/
# custom

1
.npmrc Normal file
View File

@ -0,0 +1 @@
registry=https://verdaccio.nevermind.cloud/

11
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "npm test",
"name": "Run npm test",
"request": "launch",
"type": "node-terminal"
}
]
}

26
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"json.schemas": [
{
"fileMatch": ["/npmextra.json"],
"schema": {
"type": "object",
"properties": {
"npmci": {
"type": "object",
"description": "settings for npmci"
},
"gitzone": {
"type": "object",
"description": "settings for gitzone",
"properties": {
"projectType": {
"type": "string",
"enum": ["website", "element", "service", "npm", "wcc"]
}
}
}
}
}
}
]
}

28
html/index.html Normal file
View File

@ -0,0 +1,28 @@
<!--gitzone element-->
<!-- made by Task Venture Capital GmbH -->
<!-- checkout https://maintainedby.lossless.com for awesome OpenSource projects -->
<html lang="en">
<head>
<!--Lets set some basic meta tags-->
<meta
name="viewport"
content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height"
/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!--Lets load standard fonts-->
<link rel="preconnect" href="https://assetbroker.lossless.one/" crossorigin>
<link rel="stylesheet" href="https://assetbroker.lossless.one/fonts/fonts.css">
<style>
body {
margin: 0px;
background: #222222;
}
</style>
<script type="module" src="/bundle.js"></script>
</head>
<body>
</body>
</html>

10
html/index.ts Normal file
View File

@ -0,0 +1,10 @@
// dees tools
import * as deesWccTools from '@design.estate/dees-wcctools';
import * as deesDomTools from '@design.estate/dees-domtools';
// elements and pages
import * as elements from '../ts_web/elements/index.js';
import * as pages from '../ts_web/pages/index.js';
deesWccTools.setupWccTools(elements as any, pages);
deesDomTools.elementBasic.setup();

19
license Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2022 Lossless GmbH (hello@lossless.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

20
npmextra.json Normal file
View File

@ -0,0 +1,20 @@
{
"gitzone": {
"projectType": "wcc",
"module": {
"githost": "gitlab.com",
"gitscope": "designestate/private",
"gitrepo": "dedocument-catalog",
"description": "a catalog for creating documents like invoices",
"npmPackagename": "@designestate_private/dedocument-catalog",
"license": "MIT",
"projectDomain": "design.estate",
"shortDescription": "undefined variable"
}
},
"npmci": {
"npmGlobalTools": [],
"npmAccessLevel": "private",
"npmRegistryUrl": "verdaccio2.lossless.one"
}
}

65
package.json Normal file
View File

@ -0,0 +1,65 @@
{
"name": "@design.estate/dees-document",
"version": "1.0.90",
"private": false,
"description": "a catalog for creating documents like invoices",
"main": "dist_ts_web/index.js",
"typings": "dist_ts_web/index.d.ts",
"exports": {
"./ts": "./dist_ts/index.js",
"./ts_web": "./dist_ts_web/index.js"
},
"scripts": {
"test": "npm run build && tstest test/",
"build": "tsbuild --allowimplicitany && tsbuild element --allowimplicitany && tsbundle element --production",
"watch": "tswatch element"
},
"author": "Lossless GmbH",
"license": "MIT",
"dependencies": {
"@design.estate/dees-domtools": "^2.0.34",
"@design.estate/dees-element": "^2.0.23",
"@design.estate/dees-wcctools": "^1.0.76",
"@git.zone/tsrun": "^1.2.44",
"@push.rocks/smartfile": "^10.0.26",
"@push.rocks/smartjson": "^5.0.10",
"@push.rocks/smartpath": "^5.0.5",
"@push.rocks/smartpdf": "^3.0.16",
"@push.rocks/smarttime": "^4.0.1",
"@tsclass/tsclass": "^4.0.46",
"@types/qrcode": "^1.5.2",
"qrcode": "^1.5.3"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.1.66",
"@git.zone/tsbundle": "^2.0.8",
"@git.zone/tstest": "^1.0.77",
"@git.zone/tswatch": "^2.0.7",
"@push.rocks/projectinfo": "^5.0.1",
"@push.rocks/tapbundle": "^5.0.8"
},
"files": [
"ts/**/*",
"ts_web/**/*",
"dist/**/*",
"dist_*/**/*",
"dist_ts/**/*",
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
"readme.md"
],
"browserslist": [
"last 1 Chrome versions"
],
"type": "module",
"repository": {
"type": "git",
"url": "git+https://gitlab.com/designestate/private/dedocument-catalog.git"
},
"bugs": {
"url": "https://gitlab.com/designestate/private/dedocument-catalog/issues"
},
"homepage": "https://gitlab.com/designestate/private/dedocument-catalog#readme"
}

5518
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

7
readme.md Normal file
View File

@ -0,0 +1,7 @@
# @design.estate/private/dedocument-catalog
a catalog for creating documents like invoices
## Usage
Use TypeScript for best in class intellisense
For further information read the linked docs at the top of this readme.

3
test/paths.ts Normal file
View File

@ -0,0 +1,3 @@
import * as plugins from './plugins.js';
export const nogitDir = plugins.path.join(process.cwd(), '.nogit');

9
test/plugins.ts Normal file
View File

@ -0,0 +1,9 @@
import * as tsclass from '@tsclass/tsclass';
import * as smartfile from '@push.rocks/smartfile';
import * as path from 'path';
export {
tsclass,
smartfile,
path,
}

152
test/test.ts Normal file
View File

@ -0,0 +1,152 @@
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import { expect, tap } from '@push.rocks/tapbundle';
import * as deesDocumentServer from '../ts/index.js';
let testPdfServiceInstance: deesDocumentServer.PdfService;
const testLetterData: plugins.tsclass.business.ILetter = {
accentColor: null,
type: 'invoice',
date: null,
needsCoverSheet: true,
objectActions: [],
pdf: null,
content: {
invoiceData: {
id: 'XX-CLIENT-48765',
reverseCharge: true,
dueInDays: 30,
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,
deliveryDate: new Date().getTime(),
periodOfPerformance: null,
printResult: null,
items: [
{
name: 'Website Creation',
unitQuantity: 1,
unitNetPrice: 1200,
unitType: 'item',
vatPercentage: 0,
currency: 'EUR',
},
],
},
contractData: {
contractDate: Date.now(),
id: 'LL-CONTRACT-48765',
},
textData: [],
timesheetData: '',
},
from: {
name: 'PdfService Test Company',
type: 'company',
description: 'doing pdf stuff',
address: {
streetName: 'Awesome Street',
houseNumber: '5',
city: 'Bremen',
country: 'Germany',
postalCode: '28359',
},
sepaConnection: {
bic: 'BPOTBEB1',
iban: 'BE72000000001616',
},
},
to: {
name: 'Awesome To Company',
type: 'company',
description: 'a company that does stuff',
address: {
streetName: 'Awesome Street',
houseNumber: '5',
city: 'Bremen',
country: 'Germany',
postalCode: '28359',
},
},
incidenceId: null,
language: null,
legalContact: null,
logoUrl: null,
pdfAttachments: null,
subject: 'Invoice XX-CLIENT-48765',
versionInfo: {
type: 'final',
version: '1.0.0',
},
};
tap.test('should create a document from an invoice', async () => {
testPdfServiceInstance = new deesDocumentServer.PdfService({});
await testPdfServiceInstance.start();
expect(testPdfServiceInstance).toBeInstanceOf(deesDocumentServer.PdfService);
});
tap.test('should create an invoice', async () => {
let counter = 0;
const saveResult = async (letterDataArg: plugins.tsclass.business.ILetter) => {
const pdfResult = await testPdfServiceInstance.createPdfFromLetterObject(letterDataArg);
await plugins.smartfile.memory.toFs(Buffer.from(pdfResult.buffer), plugins.path.join(paths.nogitDir, `test-${counter++}.pdf`));
}
await saveResult(testLetterData);
await saveResult({
...testLetterData,
versionInfo: {
type: 'draft',
version: '1.0.0',
}
});
testLetterData.content.invoiceData.items = [
{
name: 'Website Creation',
unitQuantity: 1,
unitNetPrice: 1200,
unitType: 'item',
vatPercentage: 0,
currency: 'EUR',
},
{
name: 'Hosting',
unitQuantity: 1,
unitNetPrice: 1200,
unitType: 'item',
vatPercentage: 19,
currency: 'EUR',
},
{
name: 'Overnight Shipping',
unitQuantity: 1,
unitNetPrice: 1200,
unitType: 'item',
vatPercentage: 24,
currency: 'EUR',
},
],
await saveResult({
...testLetterData,
})
});
tap.test('should stop the service', async () => {
await testPdfServiceInstance.stop();
});
tap.start();

8
ts/00_commitinfo_data.ts Normal file
View File

@ -0,0 +1,8 @@
/**
* autocreated commitinfo by @pushrocks/commitinfo
*/
export const commitinfo = {
name: '@design.estate/dees-document',
version: '1.0.91',
description: 'a catalog for creating documents like invoices'
}

51
ts/classes.pdfservice.ts Normal file
View File

@ -0,0 +1,51 @@
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import * as helpers from './helpers.js';
export interface IPdfServiceConstructorOptions {
}
/**
* a pdf service for generating pdfs
*/
export class PdfService {
// INSTANCE
options: IPdfServiceConstructorOptions;
public smartpdfInstance: plugins.smartpdf.SmartPdf;
constructor(optionsArg: IPdfServiceConstructorOptions) {
this.options = optionsArg;
}
/**
* starts the PdfService
*/
public async start() {
this.smartpdfInstance = new plugins.smartpdf.SmartPdf();
await this.smartpdfInstance.start();
}
/**
* stops the PdfService
*/
public async stop() {
await this.smartpdfInstance.stop();
}
/**
* creates an letter
*/
public async createPdfFromLetterObject(letterDataArg: plugins.tsclass.business.ILetter) {
const html = `
<script type="module">
${await helpers.getBundleAsString()}
</script>
<dedocument-dedocument printMode letterData="${plugins.smartjson.stringifyBase64(letterDataArg)}"></dedocument-dedocument>
`;
// console.log(html);
const pdfResult = await this.smartpdfInstance.getA4PdfResultForHtmlString(html);
return pdfResult;
}
}

6
ts/helpers.ts Normal file
View File

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

2
ts/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './helpers.js';
export * from './classes.pdfservice.js';

9
ts/paths.ts Normal file
View File

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

26
ts/plugins.ts Normal file
View File

@ -0,0 +1,26 @@
// node native
import * as path from 'path';
export {
path
}
// @push.rocks/scope
import * as smartfile from '@push.rocks/smartfile';
import * as smartjson from '@push.rocks/smartjson';
import * as smartpath from '@push.rocks/smartpath';
import * as smartpdf from '@push.rocks/smartpdf';
export {
smartfile,
smartpath,
smartjson,
smartpdf,
}
// @tsclass scope
import * as tsclass from '@tsclass/tsclass';
export {
tsclass,
}

View File

@ -0,0 +1,8 @@
/**
* autocreated commitinfo by @pushrocks/commitinfo
*/
export const commitinfo = {
name: '@design.estate/dees-document',
version: '1.0.91',
description: 'a catalog for creating documents like invoices'
}

View File

@ -0,0 +1,385 @@
/**
* content for invoices
*/
import {
DeesElement,
property,
html,
customElement,
type TemplateResult,
css,
cssManager,
unsafeCSS,
render,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import * as plugins from '../plugins.js';
import * as shared from './shared/index.js';
declare global {
interface HTMLElementTagNameMap {
'dedocument-contentinvoice': DeContentInvoice;
}
}
@customElement('dedocument-contentinvoice')
export class DeContentInvoice extends DeesElement {
public static demo = () => html`
<style>
.demoContainer {
background: white;
padding: 50px;
}
</style>
<div class="demoContainer">
<dedocument-contentinvoice></dedocument-contentinvoice>
</div>
`;
@property({
type: Object,
reflect: true,
})
public letterData: plugins.tsclass.business.ILetter;
constructor() {
super();
domtools.DomTools.setupDomTools();
}
public static styles = [
domtools.elementBasic.staticStyles,
css`
:host {
color: #333;
}
.trimmedContent {
display: none;
}
.repeatedContent {
}
`,
];
public render(): TemplateResult {
return html`
<div class="trimmedContent"></div>
<div class="repeatedContent"></div>
<div class="currentContent"></div>
`;
}
public getTotalNet = (): number => {
let totalNet = 0;
if (!this.letterData) {
return totalNet;
}
for (const item of this.letterData.content.invoiceData.items) {
totalNet += item.unitNetPrice * item.unitQuantity;
}
return totalNet;
};
public getTotalGross = (): number => {
let totalVat = 0;
if (!this.letterData) {
return totalVat;
}
for (const taxgroup of this.getVatGroups()) {
totalVat += taxgroup.vatAmountSum;
}
return this.getTotalNet() + totalVat;
};
public getVatGroups = () => {
const vatGroups: {
vatPercentage: number;
items: plugins.tsclass.finance.IInvoice['items'];
vatAmountSum: number;
}[] = [];
if (!this.letterData) {
return vatGroups;
}
const taxAmounts: number[] = [];
for (const item of this.letterData.content.invoiceData.items) {
taxAmounts.includes(item.vatPercentage) ? null : taxAmounts.push(item.vatPercentage);
}
for (const taxAmount of taxAmounts) {
const matchingItems = this.letterData.content.invoiceData.items.filter(
(itemArg) => itemArg.vatPercentage === taxAmount
);
let sum = 0;
for (const matchingItem of matchingItems) {
sum += matchingItem.unitNetPrice * matchingItem.unitQuantity * (taxAmount / 100);
}
vatGroups.push({
items: matchingItems,
vatPercentage: taxAmount,
vatAmountSum: Math.round(sum * 100) / 100,
});
}
return vatGroups;
};
public async getContentNodes() {
await this.elementDomReady;
return {
currentContent: this.shadowRoot.querySelector('.currentContent') as HTMLElement,
trimmedContent: this.shadowRoot.querySelector('.trimmedContent') as HTMLElement,
repeatedContent: this.shadowRoot.querySelector('.repeatedContent') as HTMLElement,
};
}
public async getContentLength() {
await this.elementDomReady;
return (await this.getContentNodes()).currentContent.children.length;
}
public async trimEndByOne() {
await this.elementDomReady;
this.shadowRoot
.querySelector('.trimmedContent')
.append(
(await this.getContentNodes()).currentContent.children.item(
(await this.getContentNodes()).currentContent.children.length - 1
)
);
}
public async trimStartToOffset(contentOffsetArg: number) {
await this.elementDomReady;
const beginningLength = (await this.getContentNodes()).currentContent.children.length;
while (
(await this.getContentNodes()).currentContent.children.length !==
beginningLength - contentOffsetArg
) {
(await this.getContentNodes()).trimmedContent.append(
(await this.getContentNodes()).currentContent.children.item(0)
);
console.log('hey' + this.shadowRoot.children.length);
}
if (
(await this.getContentNodes()).currentContent.children
.item(0)
.classList.contains('needsDataHeader')
) {
const trimmedContent = (await this.getContentNodes()).trimmedContent;
let startPoint = trimmedContent.children.length;
while (startPoint > 0) {
const element = trimmedContent.children.item(startPoint - 1);
if (element.classList.contains('dataHeader')) {
(await this.getContentNodes()).repeatedContent.append(element);
break;
}
startPoint--;
}
}
}
public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
super.firstUpdated(_changedProperties);
this.attachInvoiceDom();
}
public async attachInvoiceDom() {
const contentNodes = await this.getContentNodes();
render(
html`
<style>
.grid {
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="lineItem">Item Pos.</div>
<div class="lineItem">Description</div>
<div class="lineItem">Quantity</div>
<div class="lineItem">Unit Type</div>
<div class="lineItem">Unit Net Price</div>
<div class="lineItem">Total Net Price</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} ${invoiceItem.currency}</div>
<div class="lineItem">
${invoiceItem.unitQuantity * invoiceItem.unitNetPrice} ${invoiceItem.currency}
</div>
</div>
`
);
})()}
<div class="sums">
<div class="sumline">
<div class="label">Total net</div>
<div class="value">${this.getTotalNet()} EUR</div>
</div>
${this.getVatGroups().map((vatGroupArg) => {
let itemNumbers = '';
for (const item of vatGroupArg.items) {
const itemIndex = this.letterData.content.invoiceData.items.indexOf(item);
itemNumbers += ` ${itemIndex + 1},`;
}
return html`
<div class="sumline">
<div class="label">
Vat ${vatGroupArg.vatPercentage}%<br />
<span style="font-weight: normal">(on item positions: ${itemNumbers})</span>
</div>
<div class="value">${vatGroupArg.vatAmountSum} EUR</div>
</div>
`;
})}
<div class="sumline">
<div class="label">Total gross</div>
<div class="value">${this.getTotalGross()} EUR</div>
</div>
</div>
<div class="divider"></div>
${this.letterData?.content.invoiceData.reverseCharge
? html`
<div class="taxNote">
VAT arises on a reverse charge basis and is payable by the customer.
</div>
`
: ``}
<div class="infoBox">
<div class="label">Payment Terms:</div>
Payment is due within 30 days starting from the reception of this invoice. Please use the
following SEPA details:
<br /><br />
Beneficiary: ${this.letterData?.from.name}<br />
IBAN: ${this.letterData?.from?.sepaConnection?.iban}<br />
BIC: ${this.letterData?.from?.sepaConnection?.bic}<br />
Description: ${this.letterData?.content.invoiceData?.id}<br />
Amount: ${this.getTotalGross()} ${this.letterData?.content.invoiceData.items[0].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
);
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);
}
}

168
ts_web/elements/document.ts Normal file
View File

@ -0,0 +1,168 @@
import {
DeesElement,
property,
html,
customElement,
type TemplateResult,
css,
state,
cssManager,
unsafeCSS,
domtools,
} from '@design.estate/dees-element';
import * as plugins from '../plugins.js';
import { DePage } from './page.js';
import { DeContentInvoice } from './contentinvoice.js';
import * as shared from './shared/index.js';
declare global {
interface HTMLElementTagNameMap {
'dedocument-dedocument': DeDocument;
}
}
@customElement('dedocument-dedocument')
export class DeDocument extends DeesElement {
public static demo = () => html`
<dedocument-dedocument .format="${'a4'}" .letterData=${shared.demoLetter}></dedocument-dedocument>
`;
@property({
type: String,
})
public format: 'a4' = 'a4';
@property({
type: Number,
})
public viewWidth: number = shared.a4Width;
@property({
type: Boolean,
})
printMode = false;
@property({
type: Object,
reflect: true,
converter: (valueArg) => {
if (typeof valueArg === 'string') {
return plugins.smartjson.parseBase64(valueArg)
} else {
return valueArg;
}
},
})
public letterData: plugins.tsclass.business.ILetter;
@property({
type: String,
})
public letterDataUrl: string;
constructor() {
super();
domtools.DomTools.setupDomTools();
}
public static styles = [
domtools.elementBasic.staticStyles,
css`
:host {
display: block;
color: #333;
padding: 0px;
position: relative;
}
.betweenPagesSpacer {
height: 20px;
}
`,
];
public render(): TemplateResult {
return html`
<style>
:host {
transform-origin: top left;
transform: ${this.viewWidth
? unsafeCSS(
`scale(${this.viewWidth / shared.a4Width}, ${this.viewWidth / shared.a4Width})`
)
: unsafeCSS('')};
}
</style>
<div class="scaleport"></div>
`;
}
public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
if (this.letterDataUrl) {
const response = await fetch(this.letterDataUrl);
this.letterData = await response.json();
}
this.renderDocument();
}
public async renderDocument() {
const domtools = await this.domtoolsPromise;
const scaleport = this.shadowRoot.querySelector('.scaleport');
let pages: DePage[] = [];
let pageCounter = 0;
let complete = false;
const content: DeContentInvoice = document.createElement(
'dedocument-contentinvoice'
) as DeContentInvoice;
content.letterData = this.letterData;
document.body.appendChild(content);
await domtools.convenience.smartdelay.delayFor(0);
let overallContentOffset: number = 0;
let currentContentOffset: number;
let trimmed: number;
while (!complete) {
pageCounter++;
const currentContent = content.cloneNode(true) as DeContentInvoice;
const newPage = new DePage();
newPage.printMode = this.printMode;
newPage.letterData = this.letterData;
pages.push(newPage);
newPage.pageNumber = pageCounter;
newPage.append(currentContent);
newPage.pageTotalNumber = pageCounter;
scaleport.append(newPage);
// betweenPagesSpacer
if (!this.printMode) {
const betweenPagesSpacerDiv = document.createElement('div');
betweenPagesSpacerDiv.classList.add('betweenPagesSpacer');
scaleport.append(betweenPagesSpacerDiv);
}
await currentContent.elementDomReady;
await currentContent.trimStartToOffset(overallContentOffset);
let newPageOverflows = await newPage.checkOverflow();
trimmed = 0;
while (newPageOverflows) {
await currentContent.trimEndByOne();
trimmed++;
newPageOverflows = await newPage.checkOverflow();
}
currentContentOffset = await currentContent.getContentLength();
overallContentOffset = overallContentOffset + currentContentOffset;
if (trimmed === 0) {
complete = true;
}
// complete = true;
console.log(currentContentOffset);
}
document.body.removeChild(content);
for (const page of pages) {
page.pageTotalNumber = pageCounter;
}
}
}

8
ts_web/elements/index.ts Normal file
View File

@ -0,0 +1,8 @@
export * from './contentinvoice.js';
export * from './document.js';
export * from './letterheader.js';
export * from './page.js';
export * from './pagecontainer.js';
export * from './pagecontent.js';
export * from './pagefooter.js';
export * from './pageheader.js';

View File

@ -0,0 +1,125 @@
import {
DeesElement,
property,
html,
customElement,
type TemplateResult,
css,
cssManager,
unsafeCSS,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import * as shared from './shared/index.js';
import * as tsclass from '@tsclass/tsclass';
declare global {
interface HTMLElementTagNameMap {
'dedocument-letterheader': DeLetterHeader;
}
}
@customElement('dedocument-letterheader')
export class DeLetterHeader extends DeesElement {
public static demo = () => html`
<dedocument-letterheader .format="${'a4'}" .letterData=${shared.demoLetter}></dedocument-letterheader>
`;
@property({
type: Object,
reflect: true
})
public letterData: tsclass.business.ILetter;
@property({
type: Number,
reflect: true,
})
public pageNumber: number = 1;
@property({
type: Number,
reflect: true,
})
public pageTotalNumber: number = 1;
constructor() {
super();
domtools.DomTools.setupDomTools();
}
public static styles = [
domtools.elementBasic.staticStyles,
css`
:host {
color: #333;
}
.recepientInfo {
position: absolute;
display: block;
overflow: hidden;
top: 200px;
right: ${unsafeCSS(shared.rightMargin + 'px')};
width: 200px;
text-align: right;
}
.recepientInfo .label {
margin-top: 10px;
margin-bottom: 3px;
font-size: 10px;
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 {
font-size: 10px;
}
.address .to {
margin-top: 10px;
}
`,
];
public render(): TemplateResult {
return html`
<div class="date">
${new Date(this.letterData.date).getDate()}. ${new Date(this.letterData.date).toLocaleString('default', { month: 'long' })}
${new Date(this.letterData.date).getFullYear()}
</div>
<div class="address">
<div class="from">
${this.letterData.from.name}, ${this.letterData.from.address.streetName}
${this.letterData.from.address.houseNumber}, ${this.letterData.from.address.postalCode}
${this.letterData.from.address.city}, ${this.letterData.from.address.country}
</div>
<div class="to">
${this.letterData.to.name}<br />
${this.letterData.to.address.streetName} ${this.letterData.to.address.houseNumber}<br />
${this.letterData.to.address.postalCode} ${this.letterData.to.address.city}<br />
${this.letterData.from.address.country}
</div>
</div>
<div class="recepientInfo">
<div class="label">your customer id:</div>
${this.letterData.to.customerNumber || 'not registered'}
<div class="label">your vat id on file:</div>
${this.letterData.to.vatId || 'not provided'}
</div>
`;
}
}

142
ts_web/elements/page.ts Normal file
View File

@ -0,0 +1,142 @@
import * as tsclass from '@tsclass/tsclass';
import {
DeesElement,
property,
html,
customElement,
type TemplateResult,
css,
cssManager,
unsafeCSS,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import * as shared from './shared/index.js';
declare global {
interface HTMLElementTagNameMap {
'dedocument-page': DePage;
}
}
@customElement('dedocument-page')
export class DePage extends DeesElement {
public static demo = () => html` <dedocument-page .format="${'a4'}"></dedocument-page> `;
@property({
type: String,
})
public format: 'a4' = 'a4';
@property({
type: Number,
})
public pageNumber: number = 1;
@property({
type: Number,
})
public pageTotalNumber: number = 1;
@property({
type: Object,
})
public letterData: tsclass.business.ILetter = shared.demoLetter;
@property({
type: Boolean,
reflect: true,
})
printMode = false;
constructor() {
super();
domtools.DomTools.setupDomTools();
}
public static styles = [
domtools.elementBasic.staticStyles,
css`
:host {
}
.versionOverlay {
pointer-events: none;
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
font-family: monospace;
font-size: 16px;
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
}
.topInfo {
position: absolute;
top: 60px;
left: 40px;
color: red;
transform: rotate(-5deg);
}
.bigDraftText {
transform: rotate(-45deg);
font-size: 200px;
opacity: 0.05;
}
`,
];
public render(): TemplateResult {
return html`
<dedocument-pagecontainer .printMode=${this.printMode}>
<dedocument-pageheader
.letterData=${this.letterData}
.pageNumber="${this.pageNumber}"
.pageTotalNumber="${this.pageTotalNumber}"
></dedocument-pageheader>
${this.pageNumber === 1
? html`
<dedocument-letterheader
.pageNumber="${this.pageNumber}"
.letterData=${this.letterData}
.pageTotalNumber="${this.pageTotalNumber}"
></dedocument-letterheader>
`
: html``}
<dedocument-pagecontent
.pageNumber="${this.pageNumber}"
.pageTotalNumber="${this.pageTotalNumber}"
.letterData=${this.letterData}
><slot></slot
></dedocument-pagecontent>
<dedocument-pagefooter
.letterData=${this.letterData}
.pageNumber="${this.pageNumber}"
.pageTotalNumber="${this.pageTotalNumber}"
></dedocument-pagefooter>
<div class="versionOverlay">
${this.letterData.versionInfo.type === 'draft'
? html`
<div class="topInfo">
Please note: THIS IS A DRAFT ONLY. NO RIGHTS CAN BE DERIVED FROM THIS.<br>
-> Revision/Document version: ${this.letterData.versionInfo.version}
</div>
<div class="bigDraftText">DRAFT</div>
`
: html``}
</div>
</dedocument-pagecontainer>
`;
}
public async checkOverflow() {
await this.elementDomReady;
const pageContent = this.shadowRoot.querySelector('dedocument-pagecontent');
return pageContent.checkOverflow();
}
}

View File

@ -0,0 +1,69 @@
import {
DeesElement,
property,
html,
customElement,
type TemplateResult,
css,
cssManager,
unsafeCSS,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import * as shared from './shared/index.js';
declare global {
interface HTMLElementTagNameMap {
'dedocument-pagecontainer': DePageContainer;
}
}
@customElement('dedocument-pagecontainer')
export class DePageContainer extends DeesElement {
public static demo = () => html`
<dedocument-pagecontainer .format="${'a4'}"></dedocument-pagecontainer>
`;
@property({
type: String,
})
public format: 'a4' = 'a4';
@property({
type: Boolean,
})
public printMode = false;
constructor() {
super();
domtools.DomTools.setupDomTools();
}
public static styles = [
domtools.elementBasic.staticStyles,
css`
:host {
display: block;
background: white;
color: #333;
padding: 0px;
width: ${unsafeCSS(shared.a4Width + 'px')};
height: ${unsafeCSS(shared.a4Height + 'px')};
position: relative;
border-radius: 3px;
overflow: hidden;
}
`,
];
public render(): TemplateResult {
return html`
<style>
:host {
box-shadow: ${this.printMode ? `none` : `0px 0px 10px rgba(0,0,0,0.3)`};
}
</style>
<slot></slot>
`;
}
}

View File

@ -0,0 +1,147 @@
import {
DeesElement,
property,
html,
customElement,
type TemplateResult,
css,
cssManager,
unsafeCSS,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import * as shared from './shared/index.js';
import * as tsclass from '@tsclass/tsclass';
declare global {
interface HTMLElementTagNameMap {
'dedocument-pagecontent': DePageContent;
}
}
@customElement('dedocument-pagecontent')
export class DePageContent extends DeesElement {
public static demo = () => html`
<dedocument-pagecontent .format="${'a4'}"></dedocument-pagecontent>
`;
@property({
type: Number,
})
public letterData: tsclass.business.ILetter;
@property({
type: Number,
})
public pageNumber: number = 1;
@property({
type: Number,
})
public pageTotalNumber: number = 1;
constructor() {
super();
domtools.DomTools.setupDomTools();
}
public static styles = [
domtools.elementBasic.staticStyles,
css`
:host {
color: #333;
}
.content {
position: absolute;
left: ${unsafeCSS(shared.leftMargin + 'px')};
right: ${unsafeCSS(shared.rightMargin + 'px')};
bottom: 170px;
overflow: visible;
}
.content .subject {
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
}
.content .text {
text-align: left;
}
.subjectRepeated {
position: relative;
text-align: center;
background: #eeeeee;
color: #999;
border-radius: 50px;
padding: 5px 10px;
margin: auto;
margin-bottom: 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 {
return html`
<style>
.content {
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="subjectRepeated">
${this.letterData.subject} (Page ${this.pageNumber})
</div>
`}
<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>
`;
}
public firstUpdated(_changedProperties: Map<string | number | symbol, unknown>): void {
super.firstUpdated(_changedProperties);
this.checkOverflow();
}
public async checkOverflow() {
await this.elementDomReady;
const contentContainer = this.shadowRoot.querySelector('.content');
if (contentContainer.scrollHeight > contentContainer.clientHeight) {
console.log('overflows');
return true;
} else {
console.log('does not overflow!');
return false;
}
}
}

View File

@ -0,0 +1,132 @@
import {
DeesElement,
property,
html,
customElement,
type TemplateResult,
css,
cssManager,
unsafeCSS,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import * as shared from './shared/index.js';
import * as tsclass from '@tsclass/tsclass';
declare global {
interface HTMLElementTagNameMap {
'dedocument-pagefooter': DePageFooter;
}
}
@customElement('dedocument-pagefooter')
export class DePageFooter extends DeesElement {
public static demo = () => html`
<dedocument-pagefooter .format="${'a4'}"></dedocument-pagefooter>
`;
@property({
type: Object,
})
letterData: tsclass.business.ILetter;
@property({
type: Number
})
public pageNumber: number = 1;
@property({
type: Number
})
public pageTotalNumber: number = 1;
constructor() {
super();
domtools.DomTools.setupDomTools();
}
public static styles = [
domtools.elementBasic.staticStyles,
css`
:host {
color: #333;
}
.bottomstripe {
position: absolute;
display: grid;
font-size: 11px;
overflow: visible;
bottom: 0px;
left: 0px;
right: 0px;
height: 130px;
content: '';
padding: 30px ${unsafeCSS(shared.rightMargin + 'px')} 10px ${unsafeCSS(shared.leftMargin + 'px')};
grid-template-columns: calc(100% / 4) calc(100% / 4) calc(100% / 4) calc(100% / 4);
grid-gap: 5px;
border-top: 2px solid #e4002b;
}
.bottomstripe .pageNumber {
position: absolute;
top: 0px;
right: ${unsafeCSS(shared.rightMargin + 'px')};
background: #e4002b;
padding: 3px;
font-size: 9px;
color: #fff;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
.bottomstripe .documentTitle {
position: absolute;
top: -18px;
right: ${unsafeCSS(shared.rightMargin + 'px')};
background: #dddddd;
padding: 3px;
font-size: 9px;
color: #333;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
`,
];
public render(): TemplateResult {
return html`
<div class="bottomstripe">
<div>
<strong>Address:</strong><br />
${this.letterData.from.name}<br />
${this.letterData.from.address.streetName} ${this.letterData.from.address.houseNumber}<br />
${this.letterData.from.address.postalCode} ${this.letterData.from.address.city}<br />
${this.letterData.from.address.country}
</div>
<div>
<strong>Registration Info:</strong><br />
Amtsgericht Bremen<br />
<i>reg-#:</i> HRB 35230 HB<br />
<i>vat-id:</i> ${this.letterData.from.vatId}
</div>
<div>
<strong>Contact Info:</strong><br />
<i>email:</i> ${this.letterData.from.email}<br />
<i>phone:</i> ${this.letterData.from.phone}<br />
<i>fax:</i> ${this.letterData.from.fax}
</div>
<div>
<strong>Bank Connection:</strong><br />
<i>beneficiary:</i> ${this.letterData?.from?.name}<br />
<i>institution:</i> ${this.letterData?.from?.sepaConnection?.institution}<br />
<i>iban:</i> ${this.letterData?.from?.sepaConnection?.iban}<br />
<i>bic:</i> ${this.letterData?.from?.sepaConnection?.bic}<br />
</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>
`;
}
}

View File

@ -0,0 +1,96 @@
import {
DeesElement,
property,
html,
customElement,
type TemplateResult,
css,
cssManager,
unsafeCSS,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import * as shared from './shared/index.js';
import * as tsclass from '@tsclass/tsclass';
declare global {
interface HTMLElementTagNameMap {
'dedocument-pageheader': DePageHeader;
}
}
@customElement('dedocument-pageheader')
export class DePageHeader extends DeesElement {
public static demo = () => html`
<dedocument-pageheader .format="${'a4'}"></dedocument-pageheader>
`;
@property({
type: Object,
})
public letterData: tsclass.business.ILetter = null;
@property({
type: Number,
})
public pageNumber: number = 1;
@property({
type: Number,
})
public pageTotalNumber: number = 1;
constructor() {
super();
domtools.DomTools.setupDomTools();
}
public static styles = [
domtools.elementBasic.staticStyles,
css`
:host {
color: #333;
}
.topstripe {
position: absolute;
overflow: hidden;
top: 0px;
left: 0px;
right: 0px;
height: 130px;
color: #333;
text-align: center;
border-bottom: 2px solid #00000020;
}
.topstripe2 {
position: absolute;
overflow: hidden;
top: 130px;
left: auto;
right: ${unsafeCSS(shared.rightMargin + 'px')};
height: 20px;
line-height: 20px;
color: #333;
font-size: 10px;
}
.topstripe img {
filter: invert(1);
position: absolute;
bottom: 10px;
height: 25px;
left: auto;
right: ${unsafeCSS(shared.rightMargin + 'px')};
}
`,
];
public render(): TemplateResult {
return html`
<div class="topstripe">
<img src="https://assetbroker.lossless.one/brandfiles/lossless/svg-minimal-bright.svg" />
</div>
<div class="topstripe2">${this.letterData?.from?.description || '[no letterData.from.description set]'}</div>
`;
}
}

View File

@ -0,0 +1,121 @@
import * as tsclass from '@tsclass/tsclass';
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,
items: [
{
name: 'Item with 19% VAT',
unitQuantity: 1,
unitNetPrice: 100,
unitType: 'hours',
vatPercentage: 19,
currency: 'EUR',
},
{
name: 'Item with 7% VAT',
unitQuantity: 1,
unitNetPrice: 100,
unitType: 'hours',
vatPercentage: 7,
currency: 'EUR',
},
{
name: 'Item with 7% VAT',
unitQuantity: 1,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 7,
currency: 'EUR',
},
{
name: 'Item with 21% VAT',
unitQuantity: 1,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 21,
currency: 'EUR',
},
{
name: 'Item with 0% VAT',
unitQuantity: 1,
unitNetPrice: 230,
unitType: 'hours',
vatPercentage: 0,
currency: 'EUR',
},
],
}
},
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',
}

View File

@ -0,0 +1,6 @@
export const a4Height = 1122;
export const a4Width = 794;
export const rightMargin = 70;
export const leftMargin = 90;
export * from './demoletter.js';

1
ts_web/index.ts Normal file
View File

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

15
ts_web/plugins.ts Normal file
View File

@ -0,0 +1,15 @@
// tsclass scope
import * as tsclass from '@tsclass/tsclass';
export { tsclass };
// @pushrocks scope
import * as smartjson from '@push.rocks/smartjson';
import * as smarttime from '@push.rocks/smarttime';
export { smartjson, smarttime };
// third party
import * as qrcode from 'qrcode';
export { qrcode };

14
tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false,
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"verbatimModuleSyntax": true
},
"exclude": [
"dist_*/**/*.d.ts"
]
}