Merge pull request 'feat: enhance translation and invoice layout' (#3) from feat/enhance-translation into master
Reviewed-on: #3
This commit is contained in:
		
							
								
								
									
										28
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								package.json
									
									
									
									
									
								
							| @@ -21,27 +21,29 @@ | ||||
|   "author": "Lossless GmbH", | ||||
|   "license": "MIT", | ||||
|   "dependencies": { | ||||
|     "@design.estate/dees-domtools": "^2.0.65", | ||||
|     "@design.estate/dees-element": "^2.0.39", | ||||
|     "@design.estate/dees-wcctools": "^1.0.90", | ||||
|     "@design.estate/dees-catalog": "^1.10.10", | ||||
|     "@design.estate/dees-domtools": "^2.3.3", | ||||
|     "@design.estate/dees-element": "^2.1.2", | ||||
|     "@design.estate/dees-wcctools": "^1.1.1", | ||||
|     "@git.zone/tsrun": "^1.3.3", | ||||
|     "@push.rocks/smartfile": "^11.0.21", | ||||
|     "@push.rocks/smartfile": "^11.2.5", | ||||
|     "@push.rocks/smartjson": "^5.0.20", | ||||
|     "@push.rocks/smartpath": "^5.0.18", | ||||
|     "@push.rocks/smartpdf": "^3.1.8", | ||||
|     "@push.rocks/smarttime": "^4.0.8", | ||||
|     "@tsclass/tsclass": "^4.1.2", | ||||
|     "@types/node": "^22.10.1", | ||||
|     "@push.rocks/smartpdf": "^3.2.2", | ||||
|     "@push.rocks/smarttime": "^4.1.1", | ||||
|     "@tsclass/tsclass": "^9.2.0", | ||||
|     "@types/node": "^24.1.0", | ||||
|     "@types/qrcode": "^1.5.5", | ||||
|     "puppeteer": "^24.15.0", | ||||
|     "qrcode": "^1.5.4" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@git.zone/tsbuild": "^2.2.0", | ||||
|     "@git.zone/tsbundle": "^2.1.0", | ||||
|     "@git.zone/tstest": "^1.0.90", | ||||
|     "@git.zone/tswatch": "^2.0.34", | ||||
|     "@git.zone/tsbuild": "^2.6.4", | ||||
|     "@git.zone/tsbundle": "^2.5.1", | ||||
|     "@git.zone/tstest": "^2.3.2", | ||||
|     "@git.zone/tswatch": "^2.1.2", | ||||
|     "@push.rocks/projectinfo": "^5.0.2", | ||||
|     "@push.rocks/tapbundle": "^5.5.3" | ||||
|     "@push.rocks/tapbundle": "^6.0.3" | ||||
|   }, | ||||
|   "files": [ | ||||
|     "ts/**/*", | ||||
|   | ||||
							
								
								
									
										6449
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6449
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										206
									
								
								test/test.ts
									
									
									
									
									
								
							
							
						
						
									
										206
									
								
								test/test.ts
									
									
									
									
									
								
							| @@ -1,119 +1,107 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
| import * as paths from './paths.js'; | ||||
| import * as interfaces from '../ts_shared/interfaces/index.js'; | ||||
| import { expect, tap } from '@push.rocks/tapbundle'; | ||||
| import * as deesDocumentServer from '../ts/index.js'; | ||||
| import * as plugins from "./plugins.js"; | ||||
| import * as paths from "./paths.js"; | ||||
| import * as interfaces from "../ts_shared/interfaces/index.js"; | ||||
| import { expect, tap } from "@push.rocks/tapbundle"; | ||||
| import * as deesDocumentServer from "../ts/index.js"; | ||||
|  | ||||
| let testPdfServiceInstance: deesDocumentServer.PdfService; | ||||
| const testLetterData: plugins.tsclass.business.ILetter = { | ||||
|   accentColor: null, | ||||
|   type: 'invoice', | ||||
| const testLetterData: plugins.tsclass.finance.TInvoice = { | ||||
|   type: "invoice", | ||||
|   invoiceType: "debitnote", | ||||
|   date: null, | ||||
|   needsCoverSheet: true, | ||||
|   objectActions: [], | ||||
|   pdf: null, | ||||
|   content: { | ||||
|     invoiceData: { | ||||
|       id: 'XX-CLIENT-48765', | ||||
|       reverseCharge: true, | ||||
|       dueInDays: 30, | ||||
|       currency: 'EUR', | ||||
|       notes: [], | ||||
|       type: 'debitnote', | ||||
|       billedBy: { | ||||
|         address: null, | ||||
|         description: null, | ||||
|         name: 'Some Service GmbH', | ||||
|         type: null, | ||||
|         customerNumber: null, | ||||
|         email: null, | ||||
|         facebookUrl: null, | ||||
|         fax: null, | ||||
|         legalEntity: null, | ||||
|         sepaConnection: { | ||||
|           bic: 'BPOTBEB1', | ||||
|           iban: 'BE72000000001616', | ||||
|         }, | ||||
|       }, | ||||
|       billedTo: null, | ||||
|       status: null, | ||||
|       deliveryDate: new Date().getTime(), | ||||
|       periodOfPerformance: null, | ||||
|       printResult: null, | ||||
|       items: [ | ||||
|         { | ||||
|           name: 'Website Creation', | ||||
|           unitQuantity: 1, | ||||
|           unitNetPrice: 1200, | ||||
|           unitType: 'item', | ||||
|           vatPercentage: 0, | ||||
|           position: 1, | ||||
|         }, | ||||
|       ], | ||||
|   id: "XX-CLIENT-48765", | ||||
|   invoiceId: "XX-CLIENT-48765", | ||||
|   reverseCharge: true, | ||||
|   dueInDays: 30, | ||||
|   currency: "EUR", | ||||
|   notes: [], | ||||
|   status: null, | ||||
|   deliveryDate: new Date().getTime(), | ||||
|   periodOfPerformance: null, | ||||
|   printResult: null, | ||||
|   items: [ | ||||
|     { | ||||
|       name: "Website Creation", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 1200, | ||||
|       unitType: "item", | ||||
|       vatPercentage: 0, | ||||
|       position: 1, | ||||
|     }, | ||||
|     contractData: { | ||||
|       contractDate: Date.now(), | ||||
|       id: 'LL-CONTRACT-48765', | ||||
|     }, | ||||
|     textData: [], | ||||
|     timesheetData: '', | ||||
|   }, | ||||
|   ], | ||||
|   from: { | ||||
|     name: 'PdfService Test Company', | ||||
|     type: 'company', | ||||
|     description: 'doing pdf stuff', | ||||
|     name: "PdfService Test Company", | ||||
|     type: "company", | ||||
|     status: "active", | ||||
|     foundedDate: { day: 1, month: 1, year: 2025 }, | ||||
|     description: "doing pdf stuff", | ||||
|     address: { | ||||
|       streetName: 'Awesome Street', | ||||
|       houseNumber: '5', | ||||
|       city: 'Bremen', | ||||
|       country: 'Germany', | ||||
|       postalCode: '28359', | ||||
|       streetName: "Awesome Street", | ||||
|       houseNumber: "5", | ||||
|       city: "Bremen", | ||||
|       country: "Germany", | ||||
|       postalCode: "28359", | ||||
|     }, | ||||
|     sepaConnection: { | ||||
|       bic: 'BPOTBEB1', | ||||
|       iban: 'BE72000000001616', | ||||
|       bic: "BPOTBEB1", | ||||
|       iban: "BE72000000001616", | ||||
|     }, | ||||
|     registrationDetails: { | ||||
|       vatId: "", | ||||
|       registrationName: "", | ||||
|       registrationId: "", | ||||
|     }, | ||||
|   }, | ||||
|   to: { | ||||
|     name: 'Awesome To Company', | ||||
|     type: 'company', | ||||
|     description: 'a company that does stuff', | ||||
|     name: "Awesome To Company", | ||||
|     type: "company", | ||||
|     status: "active", | ||||
|     foundedDate: { day: 1, month: 1, year: 2025 }, | ||||
|     description: "a company that does stuff", | ||||
|     address: { | ||||
|       streetName: 'Awesome Street', | ||||
|       houseNumber: '5', | ||||
|       city: 'Bremen', | ||||
|       country: 'Germany', | ||||
|       postalCode: '28359', | ||||
|       streetName: "Awesome Street", | ||||
|       houseNumber: "5", | ||||
|       city: "Bremen", | ||||
|       country: "Germany", | ||||
|       postalCode: "28359", | ||||
|     }, | ||||
|     registrationDetails: { | ||||
|       vatId: "", | ||||
|       registrationName: "", | ||||
|       registrationId: "", | ||||
|     }, | ||||
|   }, | ||||
|   incidenceId: null, | ||||
|   language: null, | ||||
|   legalContact: null, | ||||
|   logoUrl: null, | ||||
|   pdfAttachments: null, | ||||
|   subject: 'Invoice XX-CLIENT-48765', | ||||
|   subject: "Invoice XX-CLIENT-48765", | ||||
|   versionInfo: { | ||||
|     type: 'final', | ||||
|     version: '1.0.0', | ||||
|     type: "final", | ||||
|     version: "1.0.0", | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| tap.test('should create a document from an invoice', async () => { | ||||
| tap.test("should create a document from an invoice", async () => { | ||||
|   testPdfServiceInstance = new deesDocumentServer.PdfService({}); | ||||
|   await testPdfServiceInstance.start(); | ||||
|   expect(testPdfServiceInstance).toBeInstanceOf(deesDocumentServer.PdfService); | ||||
| }); | ||||
|  | ||||
| tap.test('should create an invoice', async () => { | ||||
| tap.test("should create an invoice", async () => { | ||||
|   let counter = 0; | ||||
|   const saveResult = async (optionsArg: { | ||||
|     letterData: plugins.tsclass.business.ILetter; | ||||
|     letterData: plugins.tsclass.finance.TInvoice; | ||||
|     documentSettings: interfaces.IDocumentSettings; | ||||
|   }) => { | ||||
|     const pdfResult = await testPdfServiceInstance.createPdfFromLetterObject(optionsArg); | ||||
|     const pdfResult = await testPdfServiceInstance.createPdfFromLetterObject( | ||||
|       optionsArg | ||||
|     ); | ||||
|     await plugins.smartfile.memory.toFs( | ||||
|       Buffer.from(pdfResult.buffer), | ||||
|       plugins.path.join(paths.nogitDir, `test-${counter++}.pdf`), | ||||
|       plugins.path.join(paths.nogitDir, `test-${counter++}.pdf`) | ||||
|     ); | ||||
|   }; | ||||
|   await saveResult({ | ||||
| @@ -124,106 +112,106 @@ tap.test('should create an invoice', async () => { | ||||
|     letterData: { | ||||
|       ...testLetterData, | ||||
|       versionInfo: { | ||||
|         type: 'draft', | ||||
|         version: '1.0.0', | ||||
|         type: "draft", | ||||
|         version: "1.0.0", | ||||
|       }, | ||||
|     }, | ||||
|     documentSettings: {}, | ||||
|   }); | ||||
|   (testLetterData.content.invoiceData.items = [ | ||||
|   (testLetterData.items = [ | ||||
|     { | ||||
|       name: 'Website Creation', | ||||
|       name: "Website Creation", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 1200, | ||||
|       unitType: 'item', | ||||
|       unitType: "item", | ||||
|       vatPercentage: 0, | ||||
|       position: 1, | ||||
|     }, | ||||
|     { | ||||
|       name: 'Hosting', | ||||
|       name: "Hosting", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 1200, | ||||
|       unitType: 'item', | ||||
|       unitType: "item", | ||||
|       vatPercentage: 19, | ||||
|       position: 2, | ||||
|     }, | ||||
|     { | ||||
|       name: 'Overnight Shipping', | ||||
|       name: "Overnight Shipping", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 1200, | ||||
|       unitType: 'item', | ||||
|       unitType: "item", | ||||
|       vatPercentage: 24, | ||||
|       position: 3, | ||||
|     }, | ||||
|     { | ||||
|       name: 'Website Creation', | ||||
|       name: "Website Creation", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 1200, | ||||
|       unitType: 'item', | ||||
|       unitType: "item", | ||||
|       vatPercentage: 0, | ||||
|       position: 4, | ||||
|     }, | ||||
|     { | ||||
|       name: 'Hosting', | ||||
|       name: "Hosting", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 1200, | ||||
|       unitType: 'item', | ||||
|       unitType: "item", | ||||
|       vatPercentage: 19, | ||||
|       position: 5, | ||||
|     }, | ||||
|     { | ||||
|       name: 'Overnight Shipping', | ||||
|       name: "Overnight Shipping", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 1200, | ||||
|       unitType: 'item', | ||||
|       unitType: "item", | ||||
|       vatPercentage: 24, | ||||
|       position: 6, | ||||
|     }, | ||||
|     { | ||||
|       name: 'Website Creation', | ||||
|       name: "Website Creation", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 1200, | ||||
|       unitType: 'item', | ||||
|       unitType: "item", | ||||
|       vatPercentage: 0, | ||||
|       position: 7, | ||||
|     }, | ||||
|     { | ||||
|       name: 'Hosting', | ||||
|       name: "Hosting", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 1200, | ||||
|       unitType: 'item', | ||||
|       unitType: "item", | ||||
|       vatPercentage: 19, | ||||
|       position: 8, | ||||
|     }, | ||||
|     { | ||||
|       name: 'Overnight Shipping', | ||||
|       name: "Overnight Shipping", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 1200, | ||||
|       unitType: 'item', | ||||
|       unitType: "item", | ||||
|       vatPercentage: 24, | ||||
|       position: 9, | ||||
|     }, | ||||
|     { | ||||
|       name: 'Website Creation', | ||||
|       name: "Website Creation", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 1200, | ||||
|       unitType: 'item', | ||||
|       unitType: "item", | ||||
|       vatPercentage: 0, | ||||
|       position: 10, | ||||
|     }, | ||||
|     { | ||||
|       name: 'Hosting', | ||||
|       name: "Hosting", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 1200, | ||||
|       unitType: 'item', | ||||
|       unitType: "item", | ||||
|       vatPercentage: 19, | ||||
|       position: 11, | ||||
|     }, | ||||
|     { | ||||
|       name: 'Overnight Shipping', | ||||
|       name: "Overnight Shipping", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 1200, | ||||
|       unitType: 'item', | ||||
|       unitType: "item", | ||||
|       vatPercentage: 24, | ||||
|       position: 12, | ||||
|     }, | ||||
| @@ -234,7 +222,7 @@ tap.test('should create an invoice', async () => { | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| tap.test('should stop the service', async () => { | ||||
| tap.test("should stop the service", async () => { | ||||
|   await testPdfServiceInstance.stop(); | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
| import * as helpers from './helpers.js'; | ||||
| import * as plugins from "./plugins.js"; | ||||
| import * as helpers from "./helpers.js"; | ||||
|  | ||||
| export interface IPdfServiceConstructorOptions {} | ||||
|  | ||||
| @@ -8,7 +8,9 @@ export interface IPdfServiceConstructorOptions {} | ||||
|  */ | ||||
| export class PdfService { | ||||
|   // STATIC | ||||
|   public static async createAndStart(optionsArg: IPdfServiceConstructorOptions) { | ||||
|   public static async createAndStart( | ||||
|     optionsArg: IPdfServiceConstructorOptions | ||||
|   ) { | ||||
|     const pdfService = new PdfService(optionsArg); | ||||
|     await pdfService.start(); | ||||
|     return pdfService; | ||||
| @@ -42,17 +44,23 @@ export class PdfService { | ||||
|    * creates an letter | ||||
|    */ | ||||
|   public async createPdfFromLetterObject(optionsArg: { | ||||
|     letterData: plugins.tsclass.business.ILetter; | ||||
|     letterData: plugins.tsclass.business.TLetter; | ||||
|     documentSettings: plugins.shared.interfaces.IDocumentSettings; | ||||
|   }) { | ||||
|   }): Promise<plugins.smartpdf.IPdf> { | ||||
|     const html = ` | ||||
|       <script type="module"> | ||||
|         ${await helpers.getBundleAsString()} | ||||
|       </script> | ||||
|       <dedocument-dedocument printMode documentSettings="${plugins.smartjson.stringifyBase64(optionsArg.documentSettings)}" letterData="${plugins.smartjson.stringifyBase64(optionsArg.letterData)}"></dedocument-dedocument> | ||||
|       <dedocument-dedocument printMode documentSettings="${plugins.smartjson.stringifyBase64( | ||||
|         optionsArg.documentSettings | ||||
|       )}" letterData="${plugins.smartjson.stringifyBase64( | ||||
|       optionsArg.letterData | ||||
|     )}"></dedocument-dedocument> | ||||
|     `; | ||||
|     // console.log(html); | ||||
|     const pdfResult = await this.smartpdfInstance.getA4PdfResultForHtmlString(html); | ||||
|     const pdfResult = await this.smartpdfInstance.getA4PdfResultForHtmlString( | ||||
|       html | ||||
|     ); | ||||
|     return pdfResult; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,219 +1,248 @@ | ||||
| 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', | ||||
| const fromContact: plugins.tsclass.business.TContact = { | ||||
|   name: "Awesome From Company", | ||||
|   type: "company", | ||||
|   status: "active", | ||||
|   foundedDate: { day: 1, month: 1, year: 2025 }, | ||||
|   description: "a company that does stuff", | ||||
|   address: { | ||||
|     streetName: 'Awesome Street', | ||||
|     houseNumber: '5', | ||||
|     city: 'Bremen', | ||||
|     country: 'Germany', | ||||
|     postalCode: '28359', | ||||
|     streetName: "Awesome Street", | ||||
|     houseNumber: "5", | ||||
|     city: "Bremen", | ||||
|     country: "Germany", | ||||
|     postalCode: "28359", | ||||
|   }, | ||||
|   vatId: 'DE12345678', | ||||
|   sepaConnection: { | ||||
|     bic: 'BPOTBEB1', | ||||
|     iban: 'BE01234567891616' | ||||
|     bic: "BPOTBEB1", | ||||
|     iban: "BE01234567891616", | ||||
|   }, | ||||
|   email: "hello@awesome.company", | ||||
|   phone: "+49 421 1234567", | ||||
|   fax: "+49 421 1234568", | ||||
|   registrationDetails: { | ||||
|     registrationId: "HRB 35230 HB", | ||||
|     registrationName: "Amtsgericht Bremen", | ||||
|     vatId: "DE12345678", | ||||
|   }, | ||||
|   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', | ||||
| const toContact: plugins.tsclass.business.TContact = { | ||||
|   name: "Awesome To GmbH", | ||||
|   type: "company", | ||||
|   status: "active", | ||||
|   foundedDate: { day: 1, month: 1, year: 2025 }, | ||||
|   customerNumber: "LL-CLIENT-123", | ||||
|   description: "a company that does stuff", | ||||
|   address: { | ||||
|     streetName: 'Awesome Street', | ||||
|     houseNumber: '5', | ||||
|     city: 'Bremen', | ||||
|     country: 'Germany', | ||||
|     postalCode: '28359' | ||||
|     streetName: "Awesome Street", | ||||
|     houseNumber: "5", | ||||
|     city: "Bremen", | ||||
|     country: "Germany", | ||||
|     postalCode: "28359", | ||||
|   }, | ||||
|   vatId: 'BE12345678', | ||||
| } | ||||
|   registrationDetails: { | ||||
|     registrationId: "HRB 35230 HB", | ||||
|     registrationName: "Amtsgericht Bremen", | ||||
|     vatId: "DE12345678", | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export const demoLetter: plugins.tsclass.business.ILetter = { | ||||
| export const demoLetter: plugins.tsclass.finance.TInvoice = { | ||||
|   type: "accounting-doc", | ||||
|   accountingDocType: "invoice", | ||||
|   accountingDocId: "LL-INV-48765", | ||||
|   accountingDocStatus: "draft", | ||||
|   id: "LL-INV-48765", | ||||
|   versionInfo: { | ||||
|     type: 'draft', | ||||
|     version: '1.0.0', | ||||
|     version: "1.0.0", | ||||
|     type: "draft", | ||||
|   }, | ||||
|   accentColor: null, | ||||
|   content: { | ||||
|     textData: null, | ||||
|     timesheetData: null, | ||||
|     contractData: { | ||||
|       contractDate: Date.now(), | ||||
|       id: 'someid' | ||||
|     }, | ||||
|     invoiceData: { | ||||
|       id: 'LL-INV-48765', | ||||
|       reverseCharge: true, | ||||
|       dueInDays: 30, | ||||
|       billedBy: fromContact, | ||||
|       billedTo: toContact, | ||||
|       status: null, | ||||
|       deliveryDate: new Date().getTime(), | ||||
|       periodOfPerformance: null, | ||||
|       printResult: null, | ||||
|       currency: 'EUR', | ||||
|       notes: [], | ||||
|       type: 'debitnote', | ||||
|       items: [ | ||||
|         { | ||||
|           name: 'Item with 19% VAT', | ||||
|           unitQuantity: 2, | ||||
|           unitNetPrice: 100, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 19, | ||||
|           position: 0, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 7% VAT', | ||||
|           unitQuantity: 4, | ||||
|           unitNetPrice: 100, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 7, | ||||
|           position: 1, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 7% VAT', | ||||
|           unitQuantity: 3, | ||||
|           unitNetPrice: 230, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 7, | ||||
|           position: 2, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 21% VAT', | ||||
|           unitQuantity: 1, | ||||
|           unitNetPrice: 230, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 21, | ||||
|           position: 3, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 0% VAT', | ||||
|           unitQuantity: 6, | ||||
|           unitNetPrice: 230, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 0, | ||||
|           position: 4, | ||||
|         },{ | ||||
|           name: 'Item with 19% VAT', | ||||
|           unitQuantity: 8, | ||||
|           unitNetPrice: 100, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 19, | ||||
|           position: 5, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 7% VAT', | ||||
|           unitQuantity: 9, | ||||
|           unitNetPrice: 100, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 7, | ||||
|           position: 6, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 7% VAT', | ||||
|           unitQuantity: 4, | ||||
|           unitNetPrice: 230, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 7, | ||||
|           position: 8, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 21% VAT', | ||||
|           unitQuantity: 3, | ||||
|           unitNetPrice: 230, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 21, | ||||
|           position: 9, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 0% VAT', | ||||
|           unitQuantity: 1, | ||||
|           unitNetPrice: 230, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 0, | ||||
|           position: 10, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 0% VAT', | ||||
|           unitQuantity: 1, | ||||
|           unitNetPrice: 230, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 0, | ||||
|           position: 10, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 0% VAT', | ||||
|           unitQuantity: 1, | ||||
|           unitNetPrice: 230, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 0, | ||||
|           position: 10, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 0% VAT', | ||||
|           unitQuantity: 1, | ||||
|           unitNetPrice: 230, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 0, | ||||
|           position: 10, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 0% VAT', | ||||
|           unitQuantity: 1, | ||||
|           unitNetPrice: 230, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 0, | ||||
|           position: 10, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 0% VAT', | ||||
|           unitQuantity: 1, | ||||
|           unitNetPrice: 230, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 0, | ||||
|           position: 10, | ||||
|         }, | ||||
|         { | ||||
|           name: 'Item with 0% VAT', | ||||
|           unitQuantity: 1, | ||||
|           unitNetPrice: 230, | ||||
|           unitType: 'hours', | ||||
|           vatPercentage: 0, | ||||
|           position: 10, | ||||
|         }, | ||||
|       ], | ||||
|     } | ||||
|   }, | ||||
|    | ||||
|   language: "de", | ||||
|   date: Date.now(), | ||||
|   type: 'invoice', | ||||
|   needsCoverSheet: false, | ||||
|   objectActions: [], | ||||
|   pdf: null, | ||||
|   incidenceId: "LL-INV-48765", | ||||
|   subject: "LL-INV-48765", | ||||
|   reverseCharge: true, | ||||
|   dueInDays: 30, | ||||
|   from: fromContact, | ||||
|   to: toContact, | ||||
|   incidenceId: null, | ||||
|   language: null, | ||||
|   legalContact: null, | ||||
|   logoUrl: null, | ||||
|   pdfAttachments: null, | ||||
|   subject: 'Invoice: LL-INV-48765', | ||||
| } | ||||
|   status: null, | ||||
|   deliveryDate: new Date().getTime(), | ||||
|   periodOfPerformance: { | ||||
|     from: +new Date().setDate(new Date().getDate() - 7), | ||||
|     to: +new Date(), | ||||
|   }, | ||||
|   printResult: null, | ||||
|   currency: "EUR", | ||||
|   notes: [], | ||||
|   items: [ | ||||
|     { | ||||
|       name: "Item with 19% VAT", | ||||
|       unitQuantity: 2, | ||||
|       unitNetPrice: 100, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 19, | ||||
|       position: 0, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 7% VAT", | ||||
|       unitQuantity: 4, | ||||
|       unitNetPrice: 100, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 7, | ||||
|       position: 1, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 7% VAT", | ||||
|       unitQuantity: 3, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 7, | ||||
|       position: 2, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 21% VAT", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 21, | ||||
|       position: 3, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 0% VAT", | ||||
|       unitQuantity: 6, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 0, | ||||
|       position: 4, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 19% VAT", | ||||
|       unitQuantity: 8, | ||||
|       unitNetPrice: 100, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 19, | ||||
|       position: 5, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 7% VAT", | ||||
|       unitQuantity: 9, | ||||
|       unitNetPrice: 100, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 7, | ||||
|       position: 6, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 7% VAT", | ||||
|       unitQuantity: 4, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 7, | ||||
|       position: 8, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 21% VAT", | ||||
|       unitQuantity: 3, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 21, | ||||
|       position: 9, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 0% VAT", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 0, | ||||
|       position: 10, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 0% VAT", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 0, | ||||
|       position: 11, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 0% VAT", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 0, | ||||
|       position: 12, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 0% VAT", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 0, | ||||
|       position: 13, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 0% VAT", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 0, | ||||
|       position: 14, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 0% VAT", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 0, | ||||
|       position: 15, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 0% VAT", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 0, | ||||
|       position: 16, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 0% VAT", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 0, | ||||
|       position: 17, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 0% VAT", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 0, | ||||
|       position: 18, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 0% VAT", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 0, | ||||
|       position: 19, | ||||
|     }, | ||||
|     { | ||||
|       name: "Item with 0% VAT", | ||||
|       unitQuantity: 1, | ||||
|       unitNetPrice: 230, | ||||
|       unitType: "hours", | ||||
|       vatPercentage: 0, | ||||
|       position: 20, | ||||
|     }, | ||||
|   ], | ||||
| }; | ||||
|  | ||||
| export const demoDocumentSettings: interfaces.IDocumentSettings = { | ||||
|   enableTopDraftText: true, | ||||
|   enableDefaultHeader: true, | ||||
|   enableDefaultFooter: true, | ||||
|   languageCode: 'DE', | ||||
| }; | ||||
|   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,200 @@ | ||||
| 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', | ||||
| }; | ||||
|  | ||||
| // Define Spanish translations | ||||
| export const ES_translations: TTranslationImplementation = { | ||||
|   address: 'Dirección', | ||||
|   bankConnection: 'Conexión bancaria', | ||||
|   contactInfo: 'Información de contacto', | ||||
|   description: 'Descripción', | ||||
|   invoice: 'Factura', | ||||
|   itemPos: 'Pos.', | ||||
|   quantity: 'Cantidad', | ||||
|   registrationInfo: 'Información de registro', | ||||
|   reverseVatNote: 'El IVA se aplica por inversión del sujeto pasivo y debe ser pagado por el cliente.', | ||||
|   totalNetPrice: 'Precio total neto', | ||||
|   unitNetPrice: 'Precio unitario neto', | ||||
|   unitType: 'Tipo de unidad', | ||||
|   yourCustomerId: 'Su número de cliente:', | ||||
|   yourVatId: 'Su ID de IVA:', | ||||
|   continuesOnPage: 'Continúa en la página', | ||||
|   finalPageStatement: 'Esta es la última página de este documento.', | ||||
|   page: 'Página', | ||||
|   vatShort: 'IVA', | ||||
| }; | ||||
|  | ||||
| // Define French translations | ||||
| export const FR_translations: TTranslationImplementation = { | ||||
|   address: 'Adresse', | ||||
|   bankConnection: 'Coordonnées bancaires', | ||||
|   contactInfo: 'Informations de contact', | ||||
|   description: 'Description', | ||||
|   invoice: 'Facture', | ||||
|   itemPos: 'Position', | ||||
|   quantity: 'Quantité', | ||||
|   registrationInfo: "Informations d'enregistrement", | ||||
|   reverseVatNote: | ||||
|     "La TVA s'applique selon le mécanisme d'autoliquidation et est à payer par le client.", | ||||
|   totalNetPrice: 'Prix net total', | ||||
|   unitNetPrice: 'Prix unitaire net', | ||||
|   unitType: "Type d'unité", | ||||
|   yourCustomerId: 'Votre numéro de client :', | ||||
|   yourVatId: 'Votre numéro de TVA :', | ||||
|   continuesOnPage: 'Continue sur la page', | ||||
|   finalPageStatement: 'Ceci est la dernière page de ce document.', | ||||
|   page: 'Page', | ||||
|   vatShort: 'TVA', | ||||
| }; | ||||
|  | ||||
| // Define Italian translations | ||||
| export const IT_translations: TTranslationImplementation = { | ||||
|   address: 'Indirizzo', | ||||
|   bankConnection: 'Coordinate bancarie', | ||||
|   contactInfo: 'Informazioni di contatto', | ||||
|   description: 'Descrizione', | ||||
|   invoice: 'Fattura', | ||||
|   itemPos: 'Pos.', | ||||
|   quantity: 'Quantità', | ||||
|   registrationInfo: 'Informazioni di registrazione', | ||||
|   reverseVatNote: "L'IVA è applicata con inversione contabile ed è a carico del cliente.", | ||||
|   totalNetPrice: 'Prezzo netto totale', | ||||
|   unitNetPrice: 'Prezzo netto unitario', | ||||
|   unitType: 'Tipo di unità', | ||||
|   yourCustomerId: 'Il tuo numero cliente:', | ||||
|   yourVatId: 'Il tuo numero di partita IVA:', | ||||
|   continuesOnPage: 'Continua alla pagina', | ||||
|   finalPageStatement: 'Questa è l\'ultima pagina di questo documento.', | ||||
|   page: 'Pagina', | ||||
|   vatShort: 'IVA', | ||||
| 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", | ||||
| }; | ||||
|  | ||||
| // 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, | ||||
| }; | ||||
|  | ||||
| // 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> | ||||
| @@ -42,7 +41,7 @@ export class DeContentInvoice extends DeesElement { | ||||
|     type: Object, | ||||
|     reflect: true, | ||||
|   }) | ||||
|   public letterData: plugins.tsclass.business.ILetter; | ||||
|   public letterData: plugins.tsclass.finance.TInvoice; | ||||
|  | ||||
|   @property({ | ||||
|     type: Object, | ||||
| @@ -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; | ||||
|  | ||||
| @@ -87,7 +178,7 @@ export class DeContentInvoice extends DeesElement { | ||||
|       return totalNet; | ||||
|     } | ||||
|  | ||||
|     for (const item of this.letterData.content.invoiceData.items) { | ||||
|     for (const item of this.letterData.items) { | ||||
|       totalNet += item.unitNetPrice * item.unitQuantity; | ||||
|     } | ||||
|     return totalNet; | ||||
| @@ -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.TInvoice["items"]; | ||||
|       vatAmountSum: number; | ||||
|     }[] = []; | ||||
|  | ||||
| @@ -118,17 +209,22 @@ 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); | ||||
|     for (const item of this.letterData.items) { | ||||
|       taxAmounts.includes(item.vatPercentage) | ||||
|         ? null | ||||
|         : taxAmounts.push(item.vatPercentage); | ||||
|     } | ||||
|  | ||||
|     for (const taxAmount of taxAmounts) { | ||||
|       const matchingItems = this.letterData.content.invoiceData.items.filter( | ||||
|       const matchingItems = this.letterData.items.filter( | ||||
|         (itemArg) => itemArg.vatPercentage === taxAmount | ||||
|       ); | ||||
|       let sum = 0; | ||||
|       for (const matchingItem of matchingItems) { | ||||
|         sum += matchingItem.unitNetPrice * matchingItem.unitQuantity * (taxAmount / 100); | ||||
|         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,194 @@ export class DeContentInvoice extends DeesElement { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) { | ||||
|   public translateKey(key: TranslationKey): string { | ||||
|     return plugins.shared.translation.translate( | ||||
|       this.documentSettings.languageCode, | ||||
|       key | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   public async firstUpdated( | ||||
|     _changedProperties: Map<string | number | symbol, unknown> | ||||
|   ) { | ||||
|     super.firstUpdated(_changedProperties); | ||||
|     this.attachInvoiceDom(); | ||||
|   } | ||||
|  | ||||
|   private renderPaymentTerms(): TemplateResult { | ||||
|     return html`<div class="infoBox"> | ||||
|       <div> | ||||
|         <div> | ||||
|           <div class="label"> | ||||
|             ${this.translateKey("invoice@@payment.terms")} | ||||
|           </div> | ||||
|           <span> | ||||
|             ${this.translateKey("invoice@@payment.terms.direct")} | ||||
|             ${new Intl.DateTimeFormat(this.documentSettings.languageCode, { | ||||
|               dateStyle: this.documentSettings.dateStyle, | ||||
|             }).format( | ||||
|               new Date(this.letterData.date).setDate( | ||||
|                 new Date(this.letterData.date).getDate() + | ||||
|                   this.letterData?.dueInDays | ||||
|               ) | ||||
|             )} | ||||
|           </span> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div>`; | ||||
|   } | ||||
|  | ||||
|   private renderPaymentInfo(): TemplateResult { | ||||
|     const bic = this.letterData?.from.sepaConnection.bic; | ||||
|     const name = this.letterData?.from.name; | ||||
|     const iban = this.letterData?.from.sepaConnection.iban; | ||||
|     const currency = this.letterData?.currency; | ||||
|     const totalGross = this.getTotalGross(); | ||||
|     const reference = this.letterData?.id; | ||||
|  | ||||
|     return html`<div class="infoBox"> | ||||
|       <div> | ||||
|         <div> | ||||
|           <div class="label">${this.translateKey("invoice@@payment.qr")}</div> | ||||
|           <span> ${this.translateKey("invoice@@payment.qr.description")} </span> | ||||
|         </div> | ||||
|         <dedocument-paymentcode | ||||
|           bic="${bic}" | ||||
|           name="${name}" | ||||
|           iban="${iban}" | ||||
|           currency="${currency}" | ||||
|           totalGross="${totalGross}" | ||||
|           reference="${reference}" | ||||
|         /> | ||||
|       </div> | ||||
|     </div>`; | ||||
|   } | ||||
|  | ||||
|   private renderReferencedContract(): TemplateResult { | ||||
|     return null; | ||||
|     // return this.documentSettings.enableInvoiceContractRefSection && | ||||
|     //   this.invoiceData?.content?.contractData?.contractDate | ||||
|     //   ? html` | ||||
|     //       <div class="infoBox"> | ||||
|     //         <div class="label"> | ||||
|     //           ${this.translateKey("invoice@@referencedContract")} | ||||
|     //         </div> | ||||
|     //         ${this.translateKey("invoice@@referencedContract.text")} | ||||
|     //         ${new Intl.DateTimeFormat(this.documentSettings.languageCode, { | ||||
|     //           dateStyle: this.documentSettings.dateStyle, | ||||
|     //         }).format( | ||||
|     //           new Date(this.invoiceData?.content.contractData.contractDate) | ||||
|     //         )}. | ||||
|     //       </div> | ||||
|     //     ` | ||||
|     //   : null; | ||||
|   } | ||||
|  | ||||
|   public async attachInvoiceDom() { | ||||
|     const contentNodes = await this.getContentNodes(); | ||||
|     render( | ||||
|       html` | ||||
|         <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?.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.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> | ||||
|             ` | ||||
|  | ||||
|         ${this.letterData?.reverseCharge | ||||
|           ? 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,27 +65,28 @@ 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 letterData: plugins.tsclass.business.ILetter; | ||||
|   public letterData: plugins.tsclass.business.TLetter; | ||||
|  | ||||
|   @property({ | ||||
|     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 latestRenderedLetterData: plugins.tsclass.business.ILetter = null; | ||||
|   public latestDocumentSettings: plugins.shared.interfaces.IDocumentSettings = | ||||
|     null; | ||||
|   public latestRenderedLetterData: plugins.tsclass.business.TLetter = null; | ||||
|   public cleanupStore: any[] = []; | ||||
|  | ||||
|  | ||||
|   public async renderDocument() { | ||||
|  | ||||
|     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); | ||||
|  | ||||
| @@ -184,7 +158,8 @@ export class DeDocument extends DeesElement { | ||||
|     // lets append the content | ||||
|     const content: DeContentInvoice = new DeContentInvoice(); | ||||
|     cleanUpStoreCurrentRender.push(content); | ||||
|     content.letterData = this.letterData; | ||||
|     content.letterData = this | ||||
|       .letterData as unknown as plugins.tsclass.finance.TInvoice; | ||||
|     content.documentSettings = this.documentSettings; | ||||
|     document.body.appendChild(content); | ||||
|  | ||||
| @@ -208,7 +183,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 +199,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 +223,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 +263,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,31 +5,33 @@ 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; | ||||
|   public letterData: plugins.tsclass.finance.TInvoice; | ||||
|  | ||||
|   @property({ | ||||
|     type: Number, | ||||
| @@ -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,93 @@ export class DeLetterHeader extends DeesElement { | ||||
|     `, | ||||
|   ]; | ||||
|  | ||||
|   private renderDeliveryDate(from: Date, to: Date): TemplateResult { | ||||
|     if (this.letterData.accountingDocType !== "invoice") return null; | ||||
|     const dateFormat = new Intl.DateTimeFormat( | ||||
|       this.documentSettings.languageCode, | ||||
|       { dateStyle: this.documentSettings.dateStyle } | ||||
|     ); | ||||
|  | ||||
|     let formattedFrom = from ? dateFormat.format(from) : null; | ||||
|     let formattedTo = to ? dateFormat.format(to) : null; | ||||
|  | ||||
|     const isSameDay = formattedFrom === formattedTo; | ||||
|  | ||||
|     if (isSameDay) { | ||||
|       return html`<div class="label"> | ||||
|           ${plugins.shared.translation.translate( | ||||
|             this.documentSettings.languageCode, | ||||
|             "letterhead@@periodOfPerformance.day" | ||||
|           )} | ||||
|         </div> | ||||
|         <span> ${formattedFrom} </span>`; | ||||
|     } else { | ||||
|       return html`<div class="label"> | ||||
|           ${plugins.shared.translation.translate( | ||||
|             this.documentSettings.languageCode, | ||||
|             "letterhead@@periodOfPerformance.range" | ||||
|           )} | ||||
|         </div> | ||||
|         <span> ${formattedFrom} - ${formattedTo}</span>`; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public render(): TemplateResult { | ||||
|     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.registrationDetails.vatId || "not provided"} | ||||
|  | ||||
|         <!-- TODO: Make use of components --> | ||||
|         ${this.letterData.accountingDocType === "invoice" | ||||
|           ? html` <div class="label"> | ||||
|                 ${plugins.shared.translation.translate( | ||||
|                   this.documentSettings.languageCode, | ||||
|                   "letterhead@@invoice.number" | ||||
|                 )} | ||||
|               </div> | ||||
|               ${this.letterData.id || "not registered"}` | ||||
|           : null} | ||||
|         ${this.renderDeliveryDate( | ||||
|           new Date(this.letterData.periodOfPerformance?.from), | ||||
|           new Date(this.letterData.periodOfPerformance?.to) | ||||
|         )} | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
|   | ||||
| @@ -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, | ||||
| @@ -53,7 +53,7 @@ export class DePage extends DeesElement { | ||||
|   @property({ | ||||
|     type: Object, | ||||
|   }) | ||||
|   public letterData: tsclass.business.ILetter = null; | ||||
|   public letterData: tsclass.business.TLetter = null; | ||||
|  | ||||
|   @property({ | ||||
|     type: Boolean, | ||||
| @@ -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,27 +8,27 @@ 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({ | ||||
|     type: Number, | ||||
|   }) | ||||
|   public letterData: plugins.tsclass.business.ILetter; | ||||
|   public letterData: plugins.tsclass.business.TLetter; | ||||
|  | ||||
|   @property({ | ||||
|     type: Number, | ||||
| @@ -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,30 +5,29 @@ 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({ | ||||
|     type: Object, | ||||
|   }) | ||||
|   letterData: plugins.tsclass.business.ILetter; | ||||
|   letterData: plugins.tsclass.business.TLetter; | ||||
|  | ||||
|   @property({ | ||||
|     type: Object, | ||||
| @@ -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,113 @@ 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> | ||||
|         ${this.letterData.from.registrationDetails | ||||
|           ? html` <div> | ||||
|               <strong | ||||
|                 >${plugins.shared.translation.translate( | ||||
|                   this.documentSettings.languageCode, | ||||
|                   "footer@@registration.label" | ||||
|                 )}:</strong | ||||
|               ><br /> | ||||
|               ${this.letterData.from.registrationDetails.registrationName}<br /> | ||||
|               <i>reg-#:</i> ${this.letterData.from.registrationDetails | ||||
|                 .registrationId}<br /> | ||||
|               <i>vat-id:</i> ${this.letterData.from.registrationDetails.vatId} | ||||
|             </div>` | ||||
|           : null} | ||||
|         <div> | ||||
|           <strong>${plugins.shared.translation.translate(this.documentSettings.languageCode, 'registrationInfo', 'Registration Info')}:</strong><br /> | ||||
|           Amtsgericht Bremen<br /> | ||||
|           <i>reg-#:</i> HRB 35230 HB<br /> | ||||
|           <i>vat-id:</i> ${this.letterData.from.vatId} | ||||
|           <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, '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, | ||||
|               "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> | ||||
|           <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 /> | ||||
|         <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,28 +7,28 @@ 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({ | ||||
|     type: Object, | ||||
|   }) | ||||
|   public letterData: plugins.tsclass.business.ILetter = null; | ||||
|   public letterData: plugins.tsclass.business.TLetter = null; | ||||
|  | ||||
|   @property({ | ||||
|     type: Object, | ||||
| @@ -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; | ||||
| @@ -19,7 +26,7 @@ export class DeDocumentViewer extends DeesElement { | ||||
|     type: Object, | ||||
|     reflect: true, | ||||
|   }) | ||||
|   public letterData: plugins.tsclass.business.ILetter = null; | ||||
|   public letterData: plugins.tsclass.business.TLetter = null; | ||||
|  | ||||
|   @property({ | ||||
|     type: Object, | ||||
| @@ -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,5 +1,5 @@ | ||||
| import * as plugins from '../plugins.js'; | ||||
| import { html } from '@design.estate/dees-element'; | ||||
| import * as plugins from "../plugins.js"; | ||||
| import { html } from "@design.estate/dees-element"; | ||||
|  | ||||
| export const page1 = () => html` | ||||
|   <style> | ||||
| @@ -7,11 +7,15 @@ export const page1 = () => html` | ||||
|       margin: 16px; | ||||
|     } | ||||
|   </style> | ||||
|   <dedocument-dedocument .printMode=${false} letterData=${plugins.smartjson.stringifyBase64({ | ||||
|   <dedocument-dedocument | ||||
|     .printMode=${false} | ||||
|     letterData=${plugins.smartjson.stringifyBase64({ | ||||
|       ...plugins.shared.demoLetter, | ||||
|       from: { | ||||
|         ...plugins.shared.demoLetter.from, | ||||
|         description: 'a string set via stringified JSON' | ||||
|       } | ||||
|     } as plugins.tsclass.business.ILetter)}> </dedocument-dedocument> | ||||
|         description: "a string set via stringified JSON", | ||||
|       }, | ||||
|     } as plugins.tsclass.finance.TInvoice)} | ||||
|   > | ||||
|   </dedocument-dedocument> | ||||
| `; | ||||
|   | ||||
| @@ -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); | ||||
|   } | ||||
| `; | ||||
| `; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user