feat: enhance translation and invoice layout
This commit is contained in:
parent
7d5508e4d8
commit
256cf74a45
@ -21,6 +21,7 @@
|
||||
"author": "Lossless GmbH",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@design.estate/dees-catalog": "^1.4.1",
|
||||
"@design.estate/dees-domtools": "^2.0.65",
|
||||
"@design.estate/dees-element": "^2.0.39",
|
||||
"@design.estate/dees-wcctools": "^1.0.90",
|
||||
@ -30,9 +31,10 @@
|
||||
"@push.rocks/smartpath": "^5.0.18",
|
||||
"@push.rocks/smartpdf": "^3.1.8",
|
||||
"@push.rocks/smarttime": "^4.0.8",
|
||||
"@tsclass/tsclass": "^4.1.2",
|
||||
"@tsclass/tsclass": "^4.4.3",
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"puppeteer": "^24.3.0",
|
||||
"qrcode": "^1.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
881
pnpm-lock.yaml
generated
881
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,47 +1,46 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as interfaces from './interfaces/index.js';
|
||||
import * as plugins from "./plugins.js";
|
||||
import * as interfaces from "./interfaces/index.js";
|
||||
|
||||
const fromContact: plugins.tsclass.business.IContact = {
|
||||
name: 'Awesome From Company',
|
||||
type: 'company',
|
||||
description: 'a company that does stuff',
|
||||
name: "Awesome From Company",
|
||||
type: "company",
|
||||
description: "a company that does stuff",
|
||||
address: {
|
||||
streetName: 'Awesome Street',
|
||||
houseNumber: '5',
|
||||
city: 'Bremen',
|
||||
country: 'Germany',
|
||||
postalCode: '28359',
|
||||
streetName: "Awesome Street",
|
||||
houseNumber: "5",
|
||||
city: "Bremen",
|
||||
country: "Germany",
|
||||
postalCode: "28359",
|
||||
},
|
||||
vatId: 'DE12345678',
|
||||
vatId: "DE12345678",
|
||||
sepaConnection: {
|
||||
bic: 'BPOTBEB1',
|
||||
iban: 'BE01234567891616'
|
||||
bic: "BPOTBEB1",
|
||||
iban: "BE01234567891616",
|
||||
},
|
||||
email: 'hello@awesome.company',
|
||||
phone: '+49 421 1234567',
|
||||
fax: '+49 421 1234568',
|
||||
|
||||
email: "hello@awesome.company",
|
||||
phone: "+49 421 1234567",
|
||||
fax: "+49 421 1234568",
|
||||
};
|
||||
|
||||
const toContact: plugins.tsclass.business.IContact = {
|
||||
name: 'Awesome To GmbH',
|
||||
type: 'company',
|
||||
customerNumber: 'LL-CLIENT-123',
|
||||
description: 'a company that does stuff',
|
||||
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'
|
||||
streetName: "Awesome Street",
|
||||
houseNumber: "5",
|
||||
city: "Bremen",
|
||||
country: "Germany",
|
||||
postalCode: "28359",
|
||||
},
|
||||
vatId: 'BE12345678',
|
||||
}
|
||||
vatId: "BE12345678",
|
||||
};
|
||||
|
||||
export const demoLetter: plugins.tsclass.business.ILetter = {
|
||||
versionInfo: {
|
||||
type: 'draft',
|
||||
version: '1.0.0',
|
||||
type: "draft",
|
||||
version: "1.0.0",
|
||||
},
|
||||
accentColor: null,
|
||||
content: {
|
||||
@ -49,155 +48,192 @@ export const demoLetter: plugins.tsclass.business.ILetter = {
|
||||
timesheetData: null,
|
||||
contractData: {
|
||||
contractDate: Date.now(),
|
||||
id: 'someid'
|
||||
id: "someid",
|
||||
},
|
||||
letterData: {} as plugins.tsclass.business.ILetter,
|
||||
invoiceData: {
|
||||
id: 'LL-INV-48765',
|
||||
id: "LL-INV-48765",
|
||||
reverseCharge: true,
|
||||
dueInDays: 30,
|
||||
billedBy: fromContact,
|
||||
billedTo: toContact,
|
||||
status: null,
|
||||
deliveryDate: new Date().getTime(),
|
||||
periodOfPerformance: null,
|
||||
periodOfPerformance: {
|
||||
from: +new Date().setDate(new Date().getDate() - 7),
|
||||
to: +new Date(),
|
||||
},
|
||||
printResult: null,
|
||||
currency: 'EUR',
|
||||
currency: "EUR",
|
||||
notes: [],
|
||||
type: 'debitnote',
|
||||
type: "debitnote",
|
||||
items: [
|
||||
{
|
||||
name: 'Item with 19% VAT',
|
||||
name: "Item with 19% VAT",
|
||||
unitQuantity: 2,
|
||||
unitNetPrice: 100,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 19,
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
name: 'Item with 7% VAT',
|
||||
name: "Item with 7% VAT",
|
||||
unitQuantity: 4,
|
||||
unitNetPrice: 100,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 7,
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
name: 'Item with 7% VAT',
|
||||
name: "Item with 7% VAT",
|
||||
unitQuantity: 3,
|
||||
unitNetPrice: 230,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 7,
|
||||
position: 2,
|
||||
},
|
||||
{
|
||||
name: 'Item with 21% VAT',
|
||||
name: "Item with 21% VAT",
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 230,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 21,
|
||||
position: 3,
|
||||
},
|
||||
{
|
||||
name: 'Item with 0% VAT',
|
||||
name: "Item with 0% VAT",
|
||||
unitQuantity: 6,
|
||||
unitNetPrice: 230,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 0,
|
||||
position: 4,
|
||||
},{
|
||||
name: 'Item with 19% VAT',
|
||||
},
|
||||
{
|
||||
name: "Item with 19% VAT",
|
||||
unitQuantity: 8,
|
||||
unitNetPrice: 100,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 19,
|
||||
position: 5,
|
||||
},
|
||||
{
|
||||
name: 'Item with 7% VAT',
|
||||
name: "Item with 7% VAT",
|
||||
unitQuantity: 9,
|
||||
unitNetPrice: 100,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 7,
|
||||
position: 6,
|
||||
},
|
||||
{
|
||||
name: 'Item with 7% VAT',
|
||||
name: "Item with 7% VAT",
|
||||
unitQuantity: 4,
|
||||
unitNetPrice: 230,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 7,
|
||||
position: 8,
|
||||
},
|
||||
{
|
||||
name: 'Item with 21% VAT',
|
||||
name: "Item with 21% VAT",
|
||||
unitQuantity: 3,
|
||||
unitNetPrice: 230,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 21,
|
||||
position: 9,
|
||||
},
|
||||
{
|
||||
name: 'Item with 0% VAT',
|
||||
name: "Item with 0% VAT",
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 230,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 0,
|
||||
position: 10,
|
||||
},
|
||||
{
|
||||
name: 'Item with 0% VAT',
|
||||
name: "Item with 0% VAT",
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 230,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 0,
|
||||
position: 10,
|
||||
position: 11,
|
||||
},
|
||||
{
|
||||
name: 'Item with 0% VAT',
|
||||
name: "Item with 0% VAT",
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 230,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 0,
|
||||
position: 10,
|
||||
position: 12,
|
||||
},
|
||||
{
|
||||
name: 'Item with 0% VAT',
|
||||
name: "Item with 0% VAT",
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 230,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 0,
|
||||
position: 10,
|
||||
position: 13,
|
||||
},
|
||||
{
|
||||
name: 'Item with 0% VAT',
|
||||
name: "Item with 0% VAT",
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 230,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 0,
|
||||
position: 10,
|
||||
position: 14,
|
||||
},
|
||||
{
|
||||
name: 'Item with 0% VAT',
|
||||
name: "Item with 0% VAT",
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 230,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 0,
|
||||
position: 10,
|
||||
position: 15,
|
||||
},
|
||||
{
|
||||
name: 'Item with 0% VAT',
|
||||
name: "Item with 0% VAT",
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 230,
|
||||
unitType: 'hours',
|
||||
unitType: "hours",
|
||||
vatPercentage: 0,
|
||||
position: 10,
|
||||
position: 16,
|
||||
},
|
||||
{
|
||||
name: "Item with 0% VAT",
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 230,
|
||||
unitType: "hours",
|
||||
vatPercentage: 0,
|
||||
position: 17,
|
||||
},
|
||||
{
|
||||
name: "Item with 0% VAT",
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 230,
|
||||
unitType: "hours",
|
||||
vatPercentage: 0,
|
||||
position: 18,
|
||||
},
|
||||
{
|
||||
name: "Item with 0% VAT",
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 230,
|
||||
unitType: "hours",
|
||||
vatPercentage: 0,
|
||||
position: 19,
|
||||
},
|
||||
{
|
||||
name: "Item with 0% VAT",
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 230,
|
||||
unitType: "hours",
|
||||
vatPercentage: 0,
|
||||
position: 20,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
date: Date.now(),
|
||||
type: 'invoice',
|
||||
type: "invoice",
|
||||
needsCoverSheet: false,
|
||||
objectActions: [],
|
||||
pdf: null,
|
||||
@ -208,12 +244,12 @@ export const demoLetter: plugins.tsclass.business.ILetter = {
|
||||
legalContact: null,
|
||||
logoUrl: null,
|
||||
pdfAttachments: null,
|
||||
subject: 'Invoice: LL-INV-48765',
|
||||
}
|
||||
subject: "Invoice: LL-INV-48765",
|
||||
};
|
||||
|
||||
export const demoDocumentSettings: interfaces.IDocumentSettings = {
|
||||
enableTopDraftText: true,
|
||||
enableDefaultHeader: true,
|
||||
enableDefaultFooter: true,
|
||||
languageCode: 'DE',
|
||||
};
|
||||
languageCode: "DE",
|
||||
};
|
||||
|
@ -1,12 +1,15 @@
|
||||
export const a4Height = 1122;
|
||||
export const a4Width = 794;
|
||||
export const rightMargin = 70;
|
||||
export const leftMargin = 90;
|
||||
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
|
||||
|
||||
import * as interfaces from './interfaces/index.js';
|
||||
export function cmToPx(value: number): number {
|
||||
return DPI * value;
|
||||
}
|
||||
|
||||
import * as interfaces from "./interfaces/index.js";
|
||||
export { interfaces };
|
||||
|
||||
import * as translation from './translation.js';
|
||||
import * as translation from "./translation.js";
|
||||
export { translation };
|
||||
|
||||
export * from './demoletter.js';
|
||||
export * from "./demoletter.js";
|
||||
|
@ -1,9 +1,23 @@
|
||||
import * as translation from '../translation.js';
|
||||
import * as translation from "../translation.js";
|
||||
|
||||
export interface IDocumentTheme {
|
||||
colorPrimaryForeground?: string;
|
||||
colorPrimaryBackground?: string;
|
||||
colorAccentForeground?: string;
|
||||
colorAccentBackground?: string;
|
||||
fontFamily?: string;
|
||||
pageBackground?: string;
|
||||
coverPageBackground?: string;
|
||||
}
|
||||
|
||||
export interface IDocumentSettings {
|
||||
enableTopDraftText?: boolean;
|
||||
enableDefaultHeader?: boolean;
|
||||
enableDefaultFooter?: boolean;
|
||||
languageCode?: translation.TLanguageCode;
|
||||
enableFoldMarks?: boolean;
|
||||
enableInvoiceContractRefSection?: boolean;
|
||||
languageCode?: translation.LanguageCode;
|
||||
dateStyle?: Intl.DateTimeFormatOptions["dateStyle"];
|
||||
vatGroupPositions?: boolean;
|
||||
}
|
||||
theme?: IDocumentTheme;
|
||||
}
|
||||
|
@ -1,143 +1,272 @@
|
||||
import * as interfaces from './interfaces/index.js';
|
||||
|
||||
// Define English translations without enforcing TTranslationImplementation yet
|
||||
export const EN_translations = {
|
||||
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',
|
||||
vatShort: 'VAT',
|
||||
} as const;
|
||||
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
|
||||
export type TTranslationKey = keyof typeof EN_translations;
|
||||
|
||||
/**
|
||||
* For example:
|
||||
* - price
|
||||
*/
|
||||
type RawTranslationKeys = keyof typeof EN_translations;
|
||||
|
||||
/**
|
||||
* For example:
|
||||
* - price.item
|
||||
* - price.sum
|
||||
* - price.unit
|
||||
* - vat.yourId
|
||||
*/
|
||||
type NestedTranslationKeys =
|
||||
| RawTranslationKeys
|
||||
| `${RawTranslationKeys}.${string}`
|
||||
| `${RawTranslationKeys}.${string}.${string}`
|
||||
| `${RawTranslationKeys}.${string}.${string}.${string}`
|
||||
| `${RawTranslationKeys}.${string}.${string}.${string}.${string}`;
|
||||
|
||||
/**
|
||||
* For example:
|
||||
* - contact@@mail
|
||||
* - vat = 'VAT'
|
||||
* - vat.yourId = 'your vat id'
|
||||
* - footer@@vat.yourId = 'your vat id'
|
||||
* - header@@vat.yourId = 'THIS IS YOUR VAT'
|
||||
*/
|
||||
type LocationBasedTranslationKeys = `${string}@@${NestedTranslationKeys}`;
|
||||
|
||||
/**
|
||||
* Mix of everything
|
||||
*/
|
||||
export type TranslationKey =
|
||||
| NestedTranslationKeys
|
||||
| LocationBasedTranslationKeys;
|
||||
|
||||
// Define the type for all translations based on EN_translations keys
|
||||
export type TTranslationImplementation = {
|
||||
[key in TTranslationKey]: string;
|
||||
export type Dictionary = {
|
||||
[key in TranslationKey]: string;
|
||||
};
|
||||
|
||||
// Define German translations
|
||||
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: 'Summe netto',
|
||||
unitNetPrice: 'Einheit netto',
|
||||
unitType: 'Einheit',
|
||||
yourCustomerId: 'Ihre Kundennummer:',
|
||||
yourVatId: 'Ihre Umsatzsteuer-ID:',
|
||||
continuesOnPage: 'Fortsetzung auf Seite',
|
||||
finalPageStatement: 'Dies ist die letzte Seite dieses Dokuments.',
|
||||
page: 'Seite',
|
||||
vatShort: 'USt',
|
||||
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',
|
||||
};
|
||||
// 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',
|
||||
};
|
||||
// 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',
|
||||
};
|
||||
// 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, TTranslationImplementation> = {
|
||||
export const languageCodeMap: Record<string, Dictionary> = {
|
||||
EN: EN_translations,
|
||||
DE: DE_translations,
|
||||
ES: ES_translations,
|
||||
FR: FR_translations,
|
||||
IT: IT_translations,
|
||||
// ES: ES_translations,
|
||||
// FR: FR_translations,
|
||||
// IT: IT_translations,
|
||||
};
|
||||
|
||||
// Language Code Type
|
||||
export type TLanguageCode = keyof typeof languageCodeMap;
|
||||
export type LanguageCode = keyof typeof languageCodeMap;
|
||||
|
||||
function* getTranslationKeyHierarchy(
|
||||
key: TranslationKey
|
||||
): Generator<TranslationKey, TranslationKey> {
|
||||
yield key;
|
||||
|
||||
const areaSplit = key.split("@@") as [TranslationKey, TranslationKey];
|
||||
let rest = areaSplit[1];
|
||||
|
||||
if (rest) {
|
||||
yield rest;
|
||||
} else {
|
||||
rest = areaSplit[0];
|
||||
}
|
||||
|
||||
if (!rest.includes(".")) return;
|
||||
|
||||
const parts = rest.split(".");
|
||||
for (let i = parts.length - 1; i > 0; i--) {
|
||||
yield parts.slice(0, i).join(".") as TranslationKey;
|
||||
}
|
||||
}
|
||||
|
||||
// Translate Function
|
||||
export const translate = (
|
||||
languageCode: TLanguageCode,
|
||||
key: TTranslationKey,
|
||||
defaultValue: string
|
||||
languageCode: LanguageCode,
|
||||
key: TranslationKey
|
||||
): string => {
|
||||
const translations = languageCodeMap[languageCode] || EN_translations;
|
||||
return translations[key] || defaultValue;
|
||||
};
|
||||
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;
|
||||
};
|
||||
|
@ -9,22 +9,21 @@ import {
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
css,
|
||||
cssManager,
|
||||
unsafeCSS,
|
||||
render,
|
||||
domtools,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as plugins from '../plugins.js';
|
||||
} from "@design.estate/dees-element";
|
||||
import * as plugins from "../plugins.js";
|
||||
|
||||
import { dedocumentSharedStyle } from '../style.js';
|
||||
import { dedocumentSharedStyle } from "../style.js";
|
||||
import type { TranslationKey } from "ts_shared/translation.js";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dedocument-contentinvoice': DeContentInvoice;
|
||||
"dedocument-contentinvoice": DeContentInvoice;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dedocument-contentinvoice')
|
||||
@customElement("dedocument-contentinvoice")
|
||||
export class DeContentInvoice extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<style>
|
||||
@ -59,15 +58,96 @@ export class DeContentInvoice extends DeesElement {
|
||||
domtools.elementBasic.staticStyles,
|
||||
dedocumentSharedStyle,
|
||||
css`
|
||||
:host {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.trimmedContent {
|
||||
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 => {
|
||||
let totalNet = 0;
|
||||
|
||||
@ -109,7 +200,7 @@ export class DeContentInvoice extends DeesElement {
|
||||
public getVatGroups = () => {
|
||||
const vatGroups: {
|
||||
vatPercentage: number;
|
||||
items: plugins.tsclass.finance.IInvoice['items'];
|
||||
items: plugins.tsclass.finance.IInvoice["items"];
|
||||
vatAmountSum: number;
|
||||
}[] = [];
|
||||
|
||||
@ -119,7 +210,9 @@ export class DeContentInvoice extends DeesElement {
|
||||
|
||||
const taxAmounts: number[] = [];
|
||||
for (const item of this.letterData.content.invoiceData.items) {
|
||||
taxAmounts.includes(item.vatPercentage) ? null : taxAmounts.push(item.vatPercentage);
|
||||
taxAmounts.includes(item.vatPercentage)
|
||||
? null
|
||||
: taxAmounts.push(item.vatPercentage);
|
||||
}
|
||||
|
||||
for (const taxAmount of taxAmounts) {
|
||||
@ -128,7 +221,10 @@ export class DeContentInvoice extends DeesElement {
|
||||
);
|
||||
let sum = 0;
|
||||
for (const matchingItem of matchingItems) {
|
||||
sum += matchingItem.unitNetPrice * matchingItem.unitQuantity * (taxAmount / 100);
|
||||
sum +=
|
||||
matchingItem.unitNetPrice *
|
||||
matchingItem.unitQuantity *
|
||||
(taxAmount / 100);
|
||||
}
|
||||
vatGroups.push({
|
||||
items: matchingItems,
|
||||
@ -136,15 +232,21 @@ export class DeContentInvoice extends DeesElement {
|
||||
vatAmountSum: Math.round(sum * 100) / 100,
|
||||
});
|
||||
}
|
||||
return vatGroups;
|
||||
return vatGroups.sort((a, b) => b.vatPercentage - a.vatPercentage);
|
||||
};
|
||||
|
||||
public async getContentNodes() {
|
||||
await this.elementDomReady;
|
||||
return {
|
||||
currentContent: this.shadowRoot.querySelector('.currentContent') as HTMLElement,
|
||||
trimmedContent: this.shadowRoot.querySelector('.trimmedContent') as HTMLElement,
|
||||
repeatedContent: this.shadowRoot.querySelector('.repeatedContent') as HTMLElement,
|
||||
currentContent: this.shadowRoot.querySelector(
|
||||
".currentContent"
|
||||
) 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() {
|
||||
await this.elementDomReady;
|
||||
this.shadowRoot
|
||||
.querySelector('.trimmedContent')
|
||||
.querySelector(".trimmedContent")
|
||||
.append(
|
||||
(await this.getContentNodes()).currentContent.children.item(
|
||||
(await this.getContentNodes()).currentContent.children.length - 1
|
||||
@ -166,7 +268,8 @@ export class DeContentInvoice extends DeesElement {
|
||||
|
||||
public async trimStartToOffset(contentOffsetArg: number) {
|
||||
await this.elementDomReady;
|
||||
const beginningLength = (await this.getContentNodes()).currentContent.children.length;
|
||||
const beginningLength = (await this.getContentNodes()).currentContent
|
||||
.children.length;
|
||||
while (
|
||||
(await this.getContentNodes()).currentContent.children.length !==
|
||||
beginningLength - contentOffsetArg
|
||||
@ -178,13 +281,13 @@ export class DeContentInvoice extends DeesElement {
|
||||
if (
|
||||
(await this.getContentNodes()).currentContent.children
|
||||
.item(0)
|
||||
.classList.contains('needsDataHeader')
|
||||
.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')) {
|
||||
if (element.classList.contains("dataHeader")) {
|
||||
(await this.getContentNodes()).repeatedContent.append(element);
|
||||
break;
|
||||
}
|
||||
@ -193,275 +296,198 @@ 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);
|
||||
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?.content.invoiceData.dueInDays
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private renderPaymentInfo(): TemplateResult {
|
||||
const bic =
|
||||
this.letterData?.content.invoiceData.billedBy.sepaConnection.bic;
|
||||
const name = this.letterData?.content.invoiceData.billedBy.name;
|
||||
const iban =
|
||||
this.letterData?.content.invoiceData.billedBy.sepaConnection.iban;
|
||||
const currency = this.letterData?.content.invoiceData.currency;
|
||||
const totalGross = this.getTotalGross();
|
||||
const reference = this.letterData?.content.invoiceData.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 this.documentSettings.enableInvoiceContractRefSection &&
|
||||
this.letterData?.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.letterData?.content.contractData.contractDate)
|
||||
)}.
|
||||
</div>
|
||||
`
|
||||
: null;
|
||||
}
|
||||
|
||||
public async attachInvoiceDom() {
|
||||
const contentNodes = await this.getContentNodes();
|
||||
render(
|
||||
html`
|
||||
<style>
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 40px auto 60px 60px 84px 84px 46px;
|
||||
}
|
||||
.topLine {
|
||||
margin-top: 5px;
|
||||
background: #e7e7e7;
|
||||
font-weight: bold;
|
||||
}
|
||||
.lineItem {
|
||||
font-size: 12px;
|
||||
padding: 5px;
|
||||
border-right: 1px dashed #ccc;
|
||||
}
|
||||
|
||||
.lineItem:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.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 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>${this.translateKey("invoice@@introStatement")}</div>
|
||||
<div class="grid topLine dataHeader">
|
||||
<div class="lineItem rightAlign">
|
||||
${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
'itemPos',
|
||||
'Item Pos.'
|
||||
)}
|
||||
${this.translateKey("invoice@@item.position")}
|
||||
</div>
|
||||
<div class="lineItem">
|
||||
${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
'description',
|
||||
'Description'
|
||||
)}
|
||||
${this.translateKey("invoice@@description")}
|
||||
</div>
|
||||
<div class="lineItem rightAlign">
|
||||
${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
'quantity',
|
||||
'Quantity'
|
||||
)}
|
||||
${this.translateKey("invoice@@quantity")}
|
||||
</div>
|
||||
<div class="lineItem">
|
||||
${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
'unitType',
|
||||
'Unit Type'
|
||||
)}
|
||||
<div class="lineItem">${this.translateKey("invoice@@unit.type")}</div>
|
||||
<div class="lineItem rightAlign">
|
||||
${this.translateKey("invoice@@price.unit.net")}
|
||||
</div>
|
||||
<div class="lineItem rightAlign">
|
||||
${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
'unitNetPrice',
|
||||
'Unit Net Price'
|
||||
)}
|
||||
${this.translateKey("invoice@@vat.short")}
|
||||
</div>
|
||||
<div class="lineItem rightAlign">
|
||||
${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
'totalNetPrice',
|
||||
'Total Net Price'
|
||||
)}
|
||||
</div>
|
||||
<div class="lineItem rightAlign">
|
||||
${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
'vatShort',
|
||||
'VAT'
|
||||
)}
|
||||
${this.translateKey("invoice@@price.total.net")}
|
||||
</div>
|
||||
</div>
|
||||
${(() => {
|
||||
let counter = 1;
|
||||
return this.letterData?.content.invoiceData?.items?.map((invoiceItem) => {
|
||||
const isHighlighted = false; // TODO: implement rest of highlight logic
|
||||
return html`
|
||||
<div class="grid invoiceLine needsDataHeader ${isHighlighted ? 'highlighted' : ''}">
|
||||
<div class="lineItem rightAlign">${counter++}</div>
|
||||
<div class="lineItem">${invoiceItem.name}</div>
|
||||
<div class="lineItem rightAlign">${invoiceItem.unitQuantity}</div>
|
||||
<div class="lineItem">${invoiceItem.unitType}</div>
|
||||
<div class="lineItem rightAlign">
|
||||
${invoiceItem.unitNetPrice} ${this.letterData?.content.invoiceData.currency}
|
||||
</div>
|
||||
<div class="lineItem rightAlign">
|
||||
${invoiceItem.unitQuantity * invoiceItem.unitNetPrice}
|
||||
${this.letterData?.content.invoiceData.currency}
|
||||
</div>
|
||||
<div class="lineItem rightAlign">${invoiceItem.vatPercentage}%</div>
|
||||
${this.letterData?.content.invoiceData?.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 class="sums">
|
||||
<div class="sumline">
|
||||
<div class="label">Total net</div>
|
||||
<div class="value">${this.getTotalNet()} EUR</div>
|
||||
<div class="label">
|
||||
${this.translateKey("invoice@@sum.total.net")}
|
||||
</div>
|
||||
<div class="value value--total rightAlign">
|
||||
${this.formatPrice(this.getTotalNet())}
|
||||
</div>
|
||||
</div>
|
||||
${this.getVatGroups().map((vatGroupArg) => {
|
||||
let itemNumbers = '';
|
||||
let first = true;
|
||||
for (const item of vatGroupArg.items) {
|
||||
const itemIndex = this.letterData.content.invoiceData.items.indexOf(item);
|
||||
itemNumbers += `${first ? '' : ', '}${itemIndex + 1}`;
|
||||
first = false;
|
||||
}
|
||||
let itemNumbers = vatGroupArg.items
|
||||
.map(
|
||||
(item) =>
|
||||
this.letterData.content.invoiceData.items.indexOf(item) + 1
|
||||
)
|
||||
.join(", ");
|
||||
return html`
|
||||
<div class="sumline">
|
||||
<div class="label">
|
||||
Vat ${vatGroupArg.vatPercentage}%
|
||||
${this.translateKey("vat.short")}
|
||||
${vatGroupArg.vatPercentage}%
|
||||
${this.documentSettings.vatGroupPositions
|
||||
? html`
|
||||
<br /><span style="font-weight: normal"
|
||||
>(on item positions: ${itemNumbers})</span
|
||||
>(${this.translateKey("invoice@@vat.position")}:
|
||||
${itemNumbers})</span
|
||||
>
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
<div class="value">${vatGroupArg.vatAmountSum} EUR</div>
|
||||
<div class="value rightAlign">
|
||||
${this.formatPrice(vatGroupArg.vatAmountSum)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
<div class="sumline">
|
||||
<div class="label">Total gross</div>
|
||||
<div class="value">${this.getTotalGross()} EUR</div>
|
||||
<div class="label">${this.translateKey("invoice@@totalGross")}</div>
|
||||
<div class="value value--total rightAlign">
|
||||
${this.formatPrice(this.getTotalGross())}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
|
||||
${this.letterData?.content.invoiceData.reverseCharge
|
||||
? html`
|
||||
<div class="taxNote">
|
||||
${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
'reverseVatNote',
|
||||
'VAT arises on a reverse charge basis and is payable by the customer.'
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
? html`<div class="taxNote">
|
||||
${this.translateKey("invoice@@vat.reverseCharge.note")}
|
||||
</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.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>
|
||||
|
||||
<!-- REFERENCED CONTRACT -->
|
||||
${this.renderReferencedContract()}
|
||||
|
||||
<!-- PAYMENT TERMS -->
|
||||
${this.renderPaymentTerms()}
|
||||
|
||||
<!-- PAYMENT INFO -->
|
||||
${this.renderPaymentInfo()}
|
||||
`,
|
||||
contentNodes.currentContent
|
||||
);
|
||||
const canvas = document.createElement('canvas');
|
||||
plugins.qrcode.toCanvas(
|
||||
canvas,
|
||||
`BCD
|
||||
001
|
||||
1
|
||||
SCT
|
||||
${this.letterData.content.invoiceData.billedBy.sepaConnection.bic}
|
||||
${this.letterData.content.invoiceData.billedBy.name}
|
||||
${this.letterData.content.invoiceData.billedBy.sepaConnection.iban}
|
||||
EUR${this.getTotalGross()}
|
||||
CHAR
|
||||
${this.letterData.content.invoiceData.id}
|
||||
${this.letterData.content.invoiceData.id}
|
||||
EPC QR Code`,
|
||||
(error) => {
|
||||
if (error) console.error(error);
|
||||
}
|
||||
);
|
||||
contentNodes.currentContent.querySelector('.paymentCode').append(canvas);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as plugins from "../plugins.js";
|
||||
|
||||
import { html } from '@design.estate/dees-element';
|
||||
import { html } from "@design.estate/dees-element";
|
||||
|
||||
export const demoFunc = () => html`
|
||||
<dedocument-dedocument .format="${'a4'}" .letterData=${plugins.shared.demoLetter}></dedocument-dedocument>
|
||||
`;
|
||||
<dedocument-dedocument
|
||||
.format="${"a4"}"
|
||||
.letterData=${plugins.shared.demoLetter}
|
||||
></dedocument-dedocument>
|
||||
`;
|
||||
|
@ -5,34 +5,35 @@ import {
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
css,
|
||||
state,
|
||||
cssManager,
|
||||
unsafeCSS,
|
||||
domtools,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as plugins from '../plugins.js';
|
||||
} from "@design.estate/dees-element";
|
||||
import * as plugins from "../plugins.js";
|
||||
|
||||
export const defaultDocumentSettings: plugins.shared.interfaces.IDocumentSettings = {
|
||||
enableTopDraftText: true,
|
||||
enableDefaultHeader: true,
|
||||
enableDefaultFooter: true,
|
||||
languageCode: 'EN',
|
||||
vatGroupPositions: true,
|
||||
};
|
||||
export const defaultDocumentSettings: plugins.shared.interfaces.IDocumentSettings =
|
||||
{
|
||||
enableTopDraftText: true,
|
||||
enableDefaultHeader: 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 { DeContentInvoice } from './contentinvoice.js';
|
||||
|
||||
import { demoFunc } from './document.demo.js';
|
||||
import { demoFunc } from "./document.demo.js";
|
||||
import { dedocumentSharedStyle } from "../style.js";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dedocument-dedocument': DeDocument;
|
||||
"dedocument-dedocument": DeDocument;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dedocument-dedocument')
|
||||
@customElement("dedocument-dedocument")
|
||||
export class DeDocument extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
|
||||
@ -40,7 +41,7 @@ export class DeDocument extends DeesElement {
|
||||
type: String,
|
||||
reflect: true,
|
||||
})
|
||||
public format: 'a4' = 'a4';
|
||||
public format: "a4" = "a4";
|
||||
|
||||
@property({
|
||||
type: Number,
|
||||
@ -64,8 +65,8 @@ export class DeDocument extends DeesElement {
|
||||
type: Object,
|
||||
reflect: true,
|
||||
converter: (valueArg) => {
|
||||
if (typeof valueArg === 'string') {
|
||||
return plugins.smartjson.parseBase64(valueArg)
|
||||
if (typeof valueArg === "string") {
|
||||
return plugins.smartjson.parseBase64(valueArg);
|
||||
} else {
|
||||
return valueArg;
|
||||
}
|
||||
@ -77,14 +78,15 @@ export class DeDocument extends DeesElement {
|
||||
type: Object,
|
||||
reflect: true,
|
||||
converter: (valueArg) => {
|
||||
if (typeof valueArg === 'string') {
|
||||
return plugins.smartjson.parseBase64(valueArg)
|
||||
if (typeof valueArg === "string") {
|
||||
return plugins.smartjson.parseBase64(valueArg);
|
||||
} else {
|
||||
return valueArg;
|
||||
}
|
||||
},
|
||||
})
|
||||
public documentSettings: plugins.shared.interfaces.IDocumentSettings = defaultDocumentSettings;
|
||||
public documentSettings: plugins.shared.interfaces.IDocumentSettings =
|
||||
defaultDocumentSettings;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -93,13 +95,10 @@ export class DeDocument extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
domtools.elementBasic.staticStyles,
|
||||
dedocumentSharedStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
color: #333;
|
||||
padding: 0px;
|
||||
position: relative;
|
||||
font-family: 'Dees Sans', sans-serif;
|
||||
}
|
||||
|
||||
.betweenPagesSpacer {
|
||||
@ -109,41 +108,17 @@ export class DeDocument extends DeesElement {
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="documentContainer"></div>
|
||||
`;
|
||||
return html` <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,
|
||||
}
|
||||
|
||||
while (false) {
|
||||
await domtools.plugins.smartdelay.delayFor(1000);
|
||||
this.letterData = {
|
||||
...this.letterData,
|
||||
content: {
|
||||
...this.letterData.content,
|
||||
invoiceData: {
|
||||
...this.letterData.content.invoiceData,
|
||||
items: [
|
||||
...this.letterData.content.invoiceData.items,
|
||||
{
|
||||
name: 'Test Item',
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 100,
|
||||
unitType: 'hours',
|
||||
vatPercentage: 19,
|
||||
position: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
@ -156,16 +131,15 @@ export class DeDocument extends DeesElement {
|
||||
resizeObserver.observe(this);
|
||||
this.registerGarbageFunction(() => {
|
||||
resizeObserver.disconnect();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public latestDocumentSettings: plugins.shared.interfaces.IDocumentSettings = null;
|
||||
public latestDocumentSettings: plugins.shared.interfaces.IDocumentSettings =
|
||||
null;
|
||||
public latestRenderedLetterData: plugins.tsclass.business.ILetter = null;
|
||||
public cleanupStore: any[] = [];
|
||||
|
||||
|
||||
public async renderDocument() {
|
||||
|
||||
this.latestDocumentSettings = this.documentSettings;
|
||||
this.latestRenderedLetterData = this.letterData;
|
||||
|
||||
@ -173,7 +147,7 @@ export class DeDocument extends DeesElement {
|
||||
const cleanUpStoreNextRender = [];
|
||||
|
||||
const domtools = await this.domtoolsPromise;
|
||||
const documentBuildContainer = document.createElement('div');
|
||||
const documentBuildContainer = document.createElement("div");
|
||||
cleanUpStoreCurrentRender.push(documentBuildContainer);
|
||||
document.body.appendChild(documentBuildContainer);
|
||||
|
||||
@ -208,7 +182,7 @@ export class DeDocument extends DeesElement {
|
||||
// store current page
|
||||
cleanUpStoreNextRender.push(newPage);
|
||||
documentBuildContainer.append(newPage);
|
||||
|
||||
|
||||
await currentContent.elementDomReady;
|
||||
await currentContent.trimStartToOffset(overallContentOffset);
|
||||
let newPageOverflows = await newPage.checkOverflow();
|
||||
@ -224,17 +198,18 @@ export class DeDocument extends DeesElement {
|
||||
complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (const cleanUp of this.cleanupStore) {
|
||||
cleanUp.remove();
|
||||
}
|
||||
this.cleanupStore = cleanUpStoreNextRender
|
||||
this.cleanupStore = cleanUpStoreNextRender;
|
||||
|
||||
cleanUpStoreCurrentRender.forEach((cleanUp) => {
|
||||
cleanUp.remove();
|
||||
});
|
||||
|
||||
const documentContainer = this.shadowRoot.querySelector('.documentContainer');
|
||||
const documentContainer =
|
||||
this.shadowRoot.querySelector(".documentContainer");
|
||||
if (documentContainer) {
|
||||
const children = Array.from(documentContainer.children);
|
||||
children.forEach((child) => {
|
||||
@ -247,24 +222,36 @@ export class DeDocument extends DeesElement {
|
||||
documentContainer.append(page);
|
||||
// betweenPagesSpacer
|
||||
if (!this.printMode) {
|
||||
const betweenPagesSpacerDiv = document.createElement('div');
|
||||
betweenPagesSpacerDiv.classList.add('betweenPagesSpacer');
|
||||
const betweenPagesSpacerDiv = document.createElement("div");
|
||||
betweenPagesSpacerDiv.classList.add("betweenPagesSpacer");
|
||||
documentContainer.appendChild(betweenPagesSpacerDiv);
|
||||
}
|
||||
}
|
||||
this.adjustDePageScaling();
|
||||
}
|
||||
|
||||
async updated(changedProperties: Map<string | number | symbol, unknown>): Promise<void> {
|
||||
async updated(
|
||||
changedProperties: Map<string | number | symbol, unknown>
|
||||
): Promise<void> {
|
||||
super.updated(changedProperties);
|
||||
const domtools = await this.domtoolsPromise;
|
||||
let renderedDocIsUpToDate = domtools.convenience.smartjson.deepEqualObjects(this.letterData, this.latestRenderedLetterData)
|
||||
&& domtools.convenience.smartjson.deepEqualObjects(this.documentSettings, this.latestDocumentSettings);
|
||||
let renderedDocIsUpToDate =
|
||||
domtools.convenience.smartjson.deepEqualObjects(
|
||||
this.letterData,
|
||||
this.latestRenderedLetterData
|
||||
) &&
|
||||
domtools.convenience.smartjson.deepEqualObjects(
|
||||
this.documentSettings,
|
||||
this.latestDocumentSettings
|
||||
);
|
||||
if (!renderedDocIsUpToDate) {
|
||||
this.renderDocument();
|
||||
}
|
||||
|
||||
if (changedProperties.has('viewHeight') || changedProperties.has('viewWidth')) {
|
||||
if (
|
||||
changedProperties.has("viewHeight") ||
|
||||
changedProperties.has("viewWidth")
|
||||
) {
|
||||
this.adjustDePageScaling();
|
||||
}
|
||||
}
|
||||
@ -275,7 +262,7 @@ export class DeDocument extends DeesElement {
|
||||
}
|
||||
this.viewWidth = this.clientWidth;
|
||||
// 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
|
||||
pages.forEach((page: DePage) => {
|
||||
|
@ -1,9 +1,10 @@
|
||||
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';
|
||||
export * from './viewer.js';
|
||||
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";
|
||||
export * from "./viewer.js";
|
||||
export * from "./paymentcode.js";
|
||||
|
@ -5,29 +5,31 @@ import {
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
css,
|
||||
cssManager,
|
||||
unsafeCSS,
|
||||
domtools,
|
||||
} from '@design.estate/dees-element';
|
||||
} from "@design.estate/dees-element";
|
||||
|
||||
import * as plugins from '../plugins.js';
|
||||
import { dedocumentSharedStyle } from '../style.js';
|
||||
import * as plugins from "../plugins.js";
|
||||
import { dedocumentSharedStyle } from "../style.js";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dedocument-letterheader': DeLetterHeader;
|
||||
"dedocument-letterheader": DeLetterHeader;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dedocument-letterheader')
|
||||
@customElement("dedocument-letterheader")
|
||||
export class DeLetterHeader extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<dedocument-letterheader .format="${'a4'}" .letterData=${plugins.shared.demoLetter}></dedocument-letterheader>
|
||||
<dedocument-letterheader
|
||||
.format="${"a4"}"
|
||||
.letterData=${plugins.shared.demoLetter}
|
||||
></dedocument-letterheader>
|
||||
`;
|
||||
|
||||
@property({
|
||||
type: Object,
|
||||
reflect: true
|
||||
reflect: true,
|
||||
})
|
||||
public letterData: plugins.tsclass.business.ILetter;
|
||||
|
||||
@ -43,6 +45,12 @@ export class DeLetterHeader extends DeesElement {
|
||||
})
|
||||
public pageTotalNumber: number = 1;
|
||||
|
||||
@property({
|
||||
type: Object,
|
||||
reflect: true,
|
||||
})
|
||||
public documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
domtools.DomTools.setupDomTools();
|
||||
@ -52,19 +60,29 @@ export class DeLetterHeader extends DeesElement {
|
||||
domtools.elementBasic.staticStyles,
|
||||
dedocumentSharedStyle,
|
||||
css`
|
||||
:host {
|
||||
color: #333;
|
||||
.address {
|
||||
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 {
|
||||
position: absolute;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
top: 200px;
|
||||
right: ${unsafeCSS(plugins.shared.rightMargin + 'px')};
|
||||
top: calc(var(--DPI-FACTOR) * 5.5);
|
||||
right: var(--RIGHT-MARGIN);
|
||||
width: 200px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.recepientInfo .label {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 3px;
|
||||
@ -72,19 +90,6 @@ export class DeLetterHeader extends DeesElement {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.date {
|
||||
position: absolute;
|
||||
top: 180px;
|
||||
right: ${unsafeCSS(plugins.shared.rightMargin + 'px')};
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.address {
|
||||
position: absolute;
|
||||
top: 180px;
|
||||
left: ${unsafeCSS(plugins.shared.leftMargin + 'px')};
|
||||
}
|
||||
|
||||
.address .from {
|
||||
font-size: 10px;
|
||||
}
|
||||
@ -95,31 +100,97 @@ 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 {
|
||||
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()}
|
||||
${new Intl.DateTimeFormat(this.documentSettings.languageCode, {
|
||||
dateStyle: "long",
|
||||
}).format(new Date(this.letterData.date))}
|
||||
</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}
|
||||
${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.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">
|
||||
${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>
|
||||
${this.letterData.to.vatId || 'not provided'}
|
||||
<div class="label">
|
||||
${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
"letterhead@@vat.yourId"
|
||||
)}
|
||||
</div>
|
||||
${this.letterData.to.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.content.invoiceData.id || "not registered"}`
|
||||
: null}
|
||||
${this.renderDeliveryDate(
|
||||
new Date(
|
||||
this.letterData.content?.invoiceData?.periodOfPerformance?.from
|
||||
),
|
||||
new Date(
|
||||
this.letterData.content?.invoiceData?.periodOfPerformance?.to
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as tsclass from '@tsclass/tsclass';
|
||||
import * as tsclass from "@tsclass/tsclass";
|
||||
import {
|
||||
DeesElement,
|
||||
property,
|
||||
@ -6,24 +6,24 @@ import {
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
css,
|
||||
cssManager,
|
||||
unsafeCSS,
|
||||
domtools,
|
||||
} from '@design.estate/dees-element';
|
||||
} from "@design.estate/dees-element";
|
||||
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as plugins from "../plugins.js";
|
||||
|
||||
import { defaultDocumentSettings } from './document.js';
|
||||
import { defaultDocumentSettings } from "./document.js";
|
||||
import { dedocumentSharedStyle } from "../style.js";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dedocument-page': DePage;
|
||||
"dedocument-page": DePage;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dedocument-page')
|
||||
@customElement("dedocument-page")
|
||||
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({
|
||||
type: Number,
|
||||
@ -38,7 +38,7 @@ export class DePage extends DeesElement {
|
||||
@property({
|
||||
type: String,
|
||||
})
|
||||
public format: 'a4' = 'a4';
|
||||
public format: "a4" = "a4";
|
||||
|
||||
@property({
|
||||
type: Number,
|
||||
@ -65,7 +65,8 @@ export class DePage extends DeesElement {
|
||||
type: Object,
|
||||
reflect: true,
|
||||
})
|
||||
public documentSettings: plugins.shared.interfaces.IDocumentSettings = defaultDocumentSettings;
|
||||
public documentSettings: plugins.shared.interfaces.IDocumentSettings =
|
||||
defaultDocumentSettings;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -74,6 +75,7 @@ export class DePage extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
domtools.elementBasic.staticStyles,
|
||||
dedocumentSharedStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
@ -100,26 +102,75 @@ export class DePage extends DeesElement {
|
||||
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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
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">
|
||||
<dedocument-pagecontainer .printMode=${this.printMode}>
|
||||
<div
|
||||
class="page page__background ${this.pageNumber === 1
|
||||
? "page--first"
|
||||
: ""}"
|
||||
></div>
|
||||
${this.letterData
|
||||
? html`
|
||||
${this.documentSettings.enableDefaultHeader
|
||||
@ -131,7 +182,18 @@ export class DePage extends DeesElement {
|
||||
.pageTotalNumber="${this.pageTotalNumber}"
|
||||
></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
|
||||
? html`
|
||||
<dedocument-letterheader
|
||||
@ -141,7 +203,9 @@ export class DePage extends DeesElement {
|
||||
.pageTotalNumber="${this.pageTotalNumber}"
|
||||
></dedocument-letterheader>
|
||||
`
|
||||
: html``}
|
||||
: null}
|
||||
|
||||
<!-- PAGE CONTENT -->
|
||||
<dedocument-pagecontent
|
||||
.letterData=${this.letterData}
|
||||
.documentSettings=${this.documentSettings}
|
||||
@ -149,7 +213,9 @@ export class DePage extends DeesElement {
|
||||
.pageTotalNumber="${this.pageTotalNumber}"
|
||||
><slot></slot
|
||||
></dedocument-pagecontent>
|
||||
${this.documentSettings.enableDefaultFooter
|
||||
|
||||
<!-- DEFAULT FOOTER -->
|
||||
${this.documentSettings.enableDefaultFooter === true
|
||||
? html`
|
||||
<dedocument-pagefooter
|
||||
.letterData=${this.letterData}
|
||||
@ -158,22 +224,19 @@ export class DePage extends DeesElement {
|
||||
.pageTotalNumber="${this.pageTotalNumber}"
|
||||
></dedocument-pagefooter>
|
||||
`
|
||||
: ``}
|
||||
: null}
|
||||
|
||||
<div class="versionOverlay">
|
||||
${this.letterData.versionInfo.type === 'draft'
|
||||
${this.letterData.versionInfo.type === "draft"
|
||||
? html`
|
||||
${this.documentSettings.enableTopDraftText
|
||||
? 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>
|
||||
<div class="bigDraftText">
|
||||
${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
"overlay@@draft"
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: html``}
|
||||
: null}
|
||||
</div>
|
||||
`
|
||||
: html` <slot></slot> `}
|
||||
@ -184,33 +247,37 @@ export class DePage extends DeesElement {
|
||||
|
||||
public async checkOverflow() {
|
||||
await this.elementDomReady;
|
||||
const pageContent = this.shadowRoot.querySelector('dedocument-pagecontent');
|
||||
const pageContent = this.shadowRoot.querySelector("dedocument-pagecontent");
|
||||
return pageContent.checkOverflow();
|
||||
}
|
||||
|
||||
updated(changedProperties: Map<string | number | symbol, unknown>): void {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has('viewHeight') || changedProperties.has('viewWidth')) {
|
||||
if (
|
||||
changedProperties.has("viewHeight") ||
|
||||
changedProperties.has("viewWidth")
|
||||
) {
|
||||
this.adjustScaling();
|
||||
}
|
||||
}
|
||||
|
||||
private adjustScaling() {
|
||||
const scaleWrapper: HTMLDivElement = this.shadowRoot.querySelector('#scaleWrapper');
|
||||
const scaleWrapper: HTMLDivElement =
|
||||
this.shadowRoot.querySelector("#scaleWrapper");
|
||||
|
||||
if (!scaleWrapper) return;
|
||||
|
||||
let scale = 1;
|
||||
if (this.viewHeight) {
|
||||
scale = this.viewHeight / plugins.shared.a4Height;
|
||||
scale = this.viewHeight / plugins.shared.A4_HEIGHT;
|
||||
} else if (this.viewWidth) {
|
||||
scale = this.viewWidth / plugins.shared.a4Width;
|
||||
scale = this.viewWidth / plugins.shared.A4_WIDTH;
|
||||
}
|
||||
scaleWrapper.style.transform = `scale(${scale})`;
|
||||
|
||||
// Adjust the outer dimensions so they match the scaled content
|
||||
|
||||
this.style.width = `${plugins.shared.a4Width * scale}px`;
|
||||
this.style.height = `${plugins.shared.a4Height * scale}px`;
|
||||
this.style.width = `${plugins.shared.A4_WIDTH * scale}px`;
|
||||
this.style.height = `${plugins.shared.A4_HEIGHT * scale}px`;
|
||||
}
|
||||
}
|
||||
|
@ -7,27 +7,27 @@ import {
|
||||
css,
|
||||
cssManager,
|
||||
unsafeCSS,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
} from "@design.estate/dees-element";
|
||||
import * as domtools from "@design.estate/dees-domtools";
|
||||
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as plugins from "../plugins.js";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dedocument-pagecontainer': DePageContainer;
|
||||
"dedocument-pagecontainer": DePageContainer;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dedocument-pagecontainer')
|
||||
@customElement("dedocument-pagecontainer")
|
||||
export class DePageContainer extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<dedocument-pagecontainer .format="${'a4'}"></dedocument-pagecontainer>
|
||||
<dedocument-pagecontainer .format="${"a4"}"></dedocument-pagecontainer>
|
||||
`;
|
||||
|
||||
@property({
|
||||
type: String,
|
||||
})
|
||||
public format: 'a4' = 'a4';
|
||||
public format: "a4" = "a4";
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
@ -44,11 +44,9 @@ export class DePageContainer extends DeesElement {
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
background: white;
|
||||
color: #333;
|
||||
padding: 0px;
|
||||
width: ${unsafeCSS(plugins.shared.a4Width + 'px')};
|
||||
height: ${unsafeCSS(plugins.shared.a4Height + 'px')};
|
||||
width: ${unsafeCSS(plugins.shared.A4_WIDTH + "px")};
|
||||
height: ${unsafeCSS(plugins.shared.A4_HEIGHT + "px")};
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
@ -60,7 +58,9 @@ export class DePageContainer extends DeesElement {
|
||||
return html`
|
||||
<style>
|
||||
: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>
|
||||
<slot></slot>
|
||||
|
@ -8,21 +8,21 @@ import {
|
||||
cssManager,
|
||||
unsafeCSS,
|
||||
domtools,
|
||||
} from '@design.estate/dees-element';
|
||||
} from "@design.estate/dees-element";
|
||||
|
||||
import * as plugins from '../plugins.js';
|
||||
import { dedocumentSharedStyle } from '../style.js';
|
||||
import * as plugins from "../plugins.js";
|
||||
import { dedocumentSharedStyle } from "../style.js";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dedocument-pagecontent': DePageContent;
|
||||
"dedocument-pagecontent": DePageContent;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dedocument-pagecontent')
|
||||
@customElement("dedocument-pagecontent")
|
||||
export class DePageContent extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<dedocument-pagecontent .format="${'a4'}"></dedocument-pagecontent>
|
||||
<dedocument-pagecontent .format="${"a4"}"></dedocument-pagecontent>
|
||||
`;
|
||||
|
||||
@property({
|
||||
@ -40,6 +40,12 @@ export class DePageContent extends DeesElement {
|
||||
})
|
||||
public pageTotalNumber: number = 1;
|
||||
|
||||
@property({
|
||||
type: Object,
|
||||
reflect: true,
|
||||
})
|
||||
public documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
domtools.DomTools.setupDomTools();
|
||||
@ -49,19 +55,23 @@ export class DePageContent extends DeesElement {
|
||||
domtools.elementBasic.staticStyles,
|
||||
dedocumentSharedStyle,
|
||||
css`
|
||||
:host {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: absolute;
|
||||
|
||||
left: ${unsafeCSS(plugins.shared.leftMargin + 'px')};
|
||||
right: ${unsafeCSS(plugins.shared.rightMargin + 'px')};
|
||||
bottom: 170px;
|
||||
left: var(--LEFT-MARGIN);
|
||||
right: var(--RIGHT-MARGIN);
|
||||
bottom: calc(var(--DPI-FACTOR) * 4);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.content.page--first {
|
||||
top: calc(var(--DPI-FACTOR) * 9.85);
|
||||
}
|
||||
|
||||
.content.page--notFirst {
|
||||
top: calc(var(--DPI-FACTOR) * 4.5);
|
||||
}
|
||||
|
||||
.content .subject {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
@ -83,60 +93,31 @@ export class DePageContent extends DeesElement {
|
||||
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 {
|
||||
const firstPage = this.pageNumber === 1;
|
||||
return html`
|
||||
<style>
|
||||
.content {
|
||||
top: ${this.pageNumber === 1 ? unsafeCSS('450px') : unsafeCSS('200px')};
|
||||
}
|
||||
</style>
|
||||
<div class="content">
|
||||
${this.pageNumber === 1
|
||||
<div class="content ${firstPage ? "page--first" : "page--notFirst"}">
|
||||
${firstPage
|
||||
? html`<div class="subject">${this.letterData.subject}</div>`
|
||||
: html`
|
||||
<div class="subjectRepeated">
|
||||
${this.letterData.subject} (Page ${this.pageNumber})
|
||||
</div>
|
||||
`}
|
||||
: null}
|
||||
<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 {
|
||||
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');
|
||||
const contentContainer = this.shadowRoot.querySelector(".content");
|
||||
if (contentContainer.scrollHeight > contentContainer.clientHeight) {
|
||||
return true;
|
||||
} else {
|
||||
|
@ -5,24 +5,23 @@ import {
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
css,
|
||||
cssManager,
|
||||
unsafeCSS,
|
||||
domtools,
|
||||
} from '@design.estate/dees-element';
|
||||
} from "@design.estate/dees-element";
|
||||
|
||||
import * as plugins from '../plugins.js';
|
||||
import { dedocumentSharedStyle } from '../style.js';
|
||||
import * as plugins from "../plugins.js";
|
||||
import { dedocumentSharedStyle } from "../style.js";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dedocument-pagefooter': DePageFooter;
|
||||
"dedocument-pagefooter": DePageFooter;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dedocument-pagefooter')
|
||||
@customElement("dedocument-pagefooter")
|
||||
export class DePageFooter extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<dedocument-pagefooter .format="${'a4'}"></dedocument-pagefooter>
|
||||
<dedocument-pagefooter .format="${"a4"}"></dedocument-pagefooter>
|
||||
`;
|
||||
|
||||
@property({
|
||||
@ -37,12 +36,12 @@ export class DePageFooter extends DeesElement {
|
||||
documentSettings: plugins.shared.interfaces.IDocumentSettings;
|
||||
|
||||
@property({
|
||||
type: Number
|
||||
type: Number,
|
||||
})
|
||||
public pageNumber: number = 1;
|
||||
|
||||
@property({
|
||||
type: Number
|
||||
type: Number,
|
||||
})
|
||||
public pageTotalNumber: number = 1;
|
||||
|
||||
@ -67,32 +66,36 @@ export class DePageFooter extends DeesElement {
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
height: 130px;
|
||||
content: '';
|
||||
padding: 30px ${unsafeCSS(plugins.shared.rightMargin + 'px')} 10px ${unsafeCSS(plugins.shared.leftMargin + 'px')};
|
||||
grid-template-columns: calc(100% / 4) calc(100% / 4) calc(100% / 4) calc(100% / 4);
|
||||
content: "";
|
||||
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-gap: 5px;
|
||||
border-top: 2px solid #e4002b;
|
||||
border-top: 2px solid var(--footer-separator-bg-color, #e4002b);
|
||||
}
|
||||
|
||||
.bottomstripe .pageNumber {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: ${unsafeCSS(plugins.shared.rightMargin + 'px')};
|
||||
background: #e4002b;
|
||||
right: var(--RIGHT-MARGIN);
|
||||
color: var(--footer-separator-fg-color, #ffffff);
|
||||
background: var(--footer-separator-bg-color, #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(plugins.shared.rightMargin + 'px')};
|
||||
background: #dddddd;
|
||||
top: -19px;
|
||||
right: var(--RIGHT-MARGIN);
|
||||
color: var(--label-fg);
|
||||
background: var(--label-bg);
|
||||
padding: 3px;
|
||||
font-size: 9px;
|
||||
color: #333;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
@ -103,36 +106,110 @@ export class DePageFooter extends DeesElement {
|
||||
return html`
|
||||
<div class="bottomstripe">
|
||||
<div>
|
||||
<strong>${plugins.shared.translation.translate(this.documentSettings.languageCode, 'address', 'Address')}:</strong><br />
|
||||
<strong
|
||||
>${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
"footer@@address"
|
||||
)}:</strong
|
||||
><br />
|
||||
${this.letterData.from.name}<br />
|
||||
${this.letterData.from.address.streetName} ${this.letterData.from.address.houseNumber}<br />
|
||||
${this.letterData.from.address.postalCode} ${this.letterData.from.address.city}<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>${plugins.shared.translation.translate(this.documentSettings.languageCode, 'registrationInfo', 'Registration Info')}:</strong><br />
|
||||
<strong
|
||||
>${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
"footer@@registration.label"
|
||||
)}:</strong
|
||||
><br />
|
||||
Amtsgericht Bremen<br />
|
||||
<i>reg-#:</i> HRB 35230 HB<br />
|
||||
<i>vat-id:</i> ${this.letterData.from.vatId}
|
||||
</div>
|
||||
<div>
|
||||
<strong>${plugins.shared.translation.translate(this.documentSettings.languageCode, 'contactInfo', '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}
|
||||
<strong
|
||||
>${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
"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>
|
||||
<strong>${plugins.shared.translation.translate(this.documentSettings.languageCode, 'bankConnection', '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 />
|
||||
<strong
|
||||
>${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
"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 class="documentTitle">
|
||||
<b>${this.letterData?.subject}</b>
|
||||
</div>
|
||||
<div class="pageNumber">
|
||||
${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
"footer@@page"
|
||||
)}
|
||||
${this.pageNumber}
|
||||
${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
"footer@@pageOf"
|
||||
)}
|
||||
${this.pageTotalNumber}
|
||||
</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>
|
||||
`;
|
||||
}
|
||||
|
@ -7,22 +7,22 @@ import {
|
||||
css,
|
||||
cssManager,
|
||||
unsafeCSS,
|
||||
domtools
|
||||
} from '@design.estate/dees-element';
|
||||
domtools,
|
||||
} from "@design.estate/dees-element";
|
||||
|
||||
import * as plugins from '../plugins.js';
|
||||
import { dedocumentSharedStyle } from '../style.js';
|
||||
import * as plugins from "../plugins.js";
|
||||
import { dedocumentSharedStyle } from "../style.js";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dedocument-pageheader': DePageHeader;
|
||||
"dedocument-pageheader": DePageHeader;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dedocument-pageheader')
|
||||
@customElement("dedocument-pageheader")
|
||||
export class DePageHeader extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<dedocument-pageheader .format="${'a4'}"></dedocument-pageheader>
|
||||
<dedocument-pageheader .format="${"a4"}"></dedocument-pageheader>
|
||||
`;
|
||||
|
||||
@property({
|
||||
@ -75,7 +75,7 @@ export class DePageHeader extends DeesElement {
|
||||
overflow: hidden;
|
||||
top: 130px;
|
||||
left: auto;
|
||||
right: ${unsafeCSS(plugins.shared.rightMargin + 'px')};
|
||||
right: var(--RIGHT-MARGIN);
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
color: #333;
|
||||
@ -86,8 +86,8 @@ export class DePageHeader extends DeesElement {
|
||||
bottom: 10px;
|
||||
height: 25px;
|
||||
left: auto;
|
||||
right: ${unsafeCSS(plugins.shared.rightMargin + 'px')};
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
right: var(--RIGHT-MARGIN);
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
}
|
||||
.topstripe .logo img {
|
||||
position: relative;
|
||||
@ -100,10 +100,16 @@ export class DePageHeader extends DeesElement {
|
||||
return html`
|
||||
<div class="topstripe">
|
||||
<div class="logo">
|
||||
No logo set!
|
||||
${plugins.shared.translation.translate(
|
||||
this.documentSettings.languageCode,
|
||||
"empty.logo"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="topstripe2">${this.letterData?.from?.description || '[no letterData.from.description set]'}</div>
|
||||
<div class="topstripe2">
|
||||
${this.letterData?.from?.description ||
|
||||
"[no letterData.from.description set]"}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
95
ts_web/elements/paymentcode.ts
Normal file
95
ts_web/elements/paymentcode.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import {
|
||||
css,
|
||||
customElement,
|
||||
DeesElement,
|
||||
html,
|
||||
property,
|
||||
query,
|
||||
type TemplateResult,
|
||||
} from "@design.estate/dees-element";
|
||||
import * as plugins from "../plugins.js";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dedocument-paymentcode": DedocumentPaymentCode;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("dedocument-paymentcode")
|
||||
export class DedocumentPaymentCode extends DeesElement {
|
||||
public static styles = [
|
||||
css`
|
||||
:host {
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: inherit !important;
|
||||
height: inherit !important;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@query("canvas")
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
import { html } from '@design.estate/dees-element';
|
||||
import * as plugins from '../plugins.js';
|
||||
import { html } from "@design.estate/dees-element";
|
||||
import * as plugins from "../plugins.js";
|
||||
|
||||
export const demoFunc = () => html`
|
||||
<dedocument-viewer .letterData=${plugins.shared.demoLetter} .documentSettings=${plugins.shared.demoDocumentSettings}></dedocument-viewer>
|
||||
`;
|
||||
<dedocument-viewer
|
||||
.letterData=${plugins.shared.demoLetter}
|
||||
.documentSettings=${plugins.shared.demoDocumentSettings}
|
||||
></dedocument-viewer>
|
||||
`;
|
||||
|
@ -1,15 +1,22 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as plugins from "../plugins.js";
|
||||
|
||||
import { DeesElement, css, cssManager, customElement, html, property } from '@design.estate/dees-element';
|
||||
import { demoFunc } from './viewer.demo.js';
|
||||
import {
|
||||
DeesElement,
|
||||
css,
|
||||
cssManager,
|
||||
customElement,
|
||||
html,
|
||||
property,
|
||||
} from "@design.estate/dees-element";
|
||||
import { demoFunc } from "./viewer.demo.js";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dedocument-viewer': DeDocumentViewer;
|
||||
"dedocument-viewer": DeDocumentViewer;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dedocument-viewer')
|
||||
@customElement("dedocument-viewer")
|
||||
export class DeDocumentViewer extends DeesElement {
|
||||
// DEMO
|
||||
public static demo = demoFunc;
|
||||
@ -34,7 +41,7 @@ export class DeDocumentViewer extends DeesElement {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: ${cssManager.bdTheme('#eeeeeb', '#111')};
|
||||
background: ${cssManager.bdTheme("#eeeeeb", "#111")};
|
||||
}
|
||||
.controls {
|
||||
top: 0px;
|
||||
@ -43,7 +50,7 @@ export class DeDocumentViewer extends DeesElement {
|
||||
position: absolute;
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
background: ${cssManager.bdTheme('#eeeeeb', '#111111ee')};
|
||||
background: ${cssManager.bdTheme("#eeeeeb", "#111111ee")};
|
||||
box-shadow: 0px 2px 8px 0px #000000;
|
||||
}
|
||||
.controlsShadow {
|
||||
@ -77,7 +84,12 @@ export class DeDocumentViewer extends DeesElement {
|
||||
<div class="maincontainer">
|
||||
<div class="viewport">
|
||||
${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``}
|
||||
</div>
|
||||
<div class="controls"></div>
|
||||
@ -85,9 +97,11 @@ export class DeDocumentViewer extends DeesElement {
|
||||
`;
|
||||
};
|
||||
|
||||
public updated = (changedProperties: Map<string | number | symbol, unknown>) => {
|
||||
public updated = (
|
||||
changedProperties: Map<string | number | symbol, unknown>
|
||||
) => {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has('letterData')) {
|
||||
if (changedProperties.has("letterData")) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,39 @@
|
||||
import { css } from '@design.estate/dees-element';
|
||||
import { css } from "@design.estate/dees-element";
|
||||
import * as plugins from "./plugins.js";
|
||||
|
||||
export const dedocumentSharedStyle = css`
|
||||
:host {
|
||||
font-family: 'Exo 2';
|
||||
/* Primitive colors */
|
||||
--color-light: #ffffff;
|
||||
--color-dark: #333333;
|
||||
--color-grey: #dddddd;
|
||||
--color-grey-100: #dddddd;
|
||||
--color-red: #e4002b;
|
||||
|
||||
/* Semantic colors */
|
||||
--color-primary-fg: var(--theme-color-primary-fg, var(--color-dark));
|
||||
--color-primary-bg: var(--theme-color-primary-bg, var(--color-light));
|
||||
--color-accent-fg: var(--theme-color-accent-fg, var(--color-light));
|
||||
--color-accent-bg: var(--theme-color-accent-bg, var(--color-red));
|
||||
|
||||
/* Functional colors */
|
||||
--text-fg-color: var(--color-primary-fg);
|
||||
--text-bg-color: var(--color-primary-bg);
|
||||
--label-fg: var(--color-dark);
|
||||
--label-bg: var(--color-grey);
|
||||
--footer-separator-bg-color: var(--color-accent);
|
||||
--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);
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
Loading…
x
Reference in New Issue
Block a user