@fin.cx/einvoice 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  2. package/dist_ts/00_commitinfo_data.js +9 -0
  3. package/dist_ts/classes.decoder.d.ts +40 -0
  4. package/dist_ts/classes.decoder.js +320 -0
  5. package/dist_ts/classes.encoder.d.ts +51 -0
  6. package/dist_ts/classes.encoder.js +293 -0
  7. package/dist_ts/classes.xinvoice.d.ts +136 -0
  8. package/dist_ts/classes.xinvoice.js +352 -0
  9. package/dist_ts/einvoice.d.ts +217 -0
  10. package/dist_ts/einvoice.js +505 -0
  11. package/dist_ts/errors.d.ts +121 -0
  12. package/dist_ts/errors.js +241 -0
  13. package/dist_ts/formats/base/base.decoder.d.ts +32 -0
  14. package/dist_ts/formats/base/base.decoder.js +52 -0
  15. package/dist_ts/formats/base/base.encoder.d.ts +13 -0
  16. package/dist_ts/formats/base/base.encoder.js +7 -0
  17. package/dist_ts/formats/base/base.validator.d.ts +44 -0
  18. package/dist_ts/formats/base/base.validator.js +39 -0
  19. package/dist_ts/formats/base.decoder.d.ts +19 -0
  20. package/dist_ts/formats/base.decoder.js +130 -0
  21. package/dist_ts/formats/base.validator.d.ts +44 -0
  22. package/dist_ts/formats/base.validator.js +39 -0
  23. package/dist_ts/formats/cii/cii.decoder.d.ts +61 -0
  24. package/dist_ts/formats/cii/cii.decoder.js +111 -0
  25. package/dist_ts/formats/cii/cii.encoder.d.ts +43 -0
  26. package/dist_ts/formats/cii/cii.encoder.js +48 -0
  27. package/dist_ts/formats/cii/cii.types.d.ts +31 -0
  28. package/dist_ts/formats/cii/cii.types.js +40 -0
  29. package/dist_ts/formats/cii/cii.validator.d.ts +56 -0
  30. package/dist_ts/formats/cii/cii.validator.js +146 -0
  31. package/dist_ts/formats/cii/facturx/facturx.decoder.d.ts +43 -0
  32. package/dist_ts/formats/cii/facturx/facturx.decoder.js +207 -0
  33. package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +82 -0
  34. package/dist_ts/formats/cii/facturx/facturx.encoder.js +400 -0
  35. package/dist_ts/formats/cii/facturx/facturx.types.d.ts +10 -0
  36. package/dist_ts/formats/cii/facturx/facturx.types.js +15 -0
  37. package/dist_ts/formats/cii/facturx/facturx.validator.d.ts +32 -0
  38. package/dist_ts/formats/cii/facturx/facturx.validator.js +138 -0
  39. package/dist_ts/formats/cii/zugferd/zugferd.decoder.d.ts +43 -0
  40. package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +218 -0
  41. package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +97 -0
  42. package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +550 -0
  43. package/dist_ts/formats/cii/zugferd/zugferd.types.d.ts +10 -0
  44. package/dist_ts/formats/cii/zugferd/zugferd.types.js +15 -0
  45. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +49 -0
  46. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +229 -0
  47. package/dist_ts/formats/cii/zugferd/zugferd.validator.d.ts +16 -0
  48. package/dist_ts/formats/cii/zugferd/zugferd.validator.js +29 -0
  49. package/dist_ts/formats/decoder.factory.d.ts +15 -0
  50. package/dist_ts/formats/decoder.factory.js +46 -0
  51. package/dist_ts/formats/factories/decoder.factory.d.ts +13 -0
  52. package/dist_ts/formats/factories/decoder.factory.js +56 -0
  53. package/dist_ts/formats/factories/encoder.factory.d.ts +14 -0
  54. package/dist_ts/formats/factories/encoder.factory.js +40 -0
  55. package/dist_ts/formats/factories/validator.factory.d.ts +12 -0
  56. package/dist_ts/formats/factories/validator.factory.js +110 -0
  57. package/dist_ts/formats/factorx.decoder.d.ts +18 -0
  58. package/dist_ts/formats/factorx.decoder.js +178 -0
  59. package/dist_ts/formats/factorx.encoder.d.ts +51 -0
  60. package/dist_ts/formats/factorx.encoder.js +293 -0
  61. package/dist_ts/formats/facturx.decoder.d.ts +18 -0
  62. package/dist_ts/formats/facturx.decoder.js +210 -0
  63. package/dist_ts/formats/facturx.encoder.d.ts +51 -0
  64. package/dist_ts/formats/facturx.encoder.js +293 -0
  65. package/dist_ts/formats/facturx.validator.d.ts +67 -0
  66. package/dist_ts/formats/facturx.validator.js +266 -0
  67. package/dist_ts/formats/pdf/extractors/associated.extractor.d.ts +14 -0
  68. package/dist_ts/formats/pdf/extractors/associated.extractor.js +68 -0
  69. package/dist_ts/formats/pdf/extractors/base.extractor.d.ts +92 -0
  70. package/dist_ts/formats/pdf/extractors/base.extractor.js +319 -0
  71. package/dist_ts/formats/pdf/extractors/index.d.ts +4 -0
  72. package/dist_ts/formats/pdf/extractors/index.js +5 -0
  73. package/dist_ts/formats/pdf/extractors/standard.extractor.d.ts +13 -0
  74. package/dist_ts/formats/pdf/extractors/standard.extractor.js +74 -0
  75. package/dist_ts/formats/pdf/extractors/text.extractor.d.ts +45 -0
  76. package/dist_ts/formats/pdf/extractors/text.extractor.js +151 -0
  77. package/dist_ts/formats/pdf/pdf.embedder.d.ts +69 -0
  78. package/dist_ts/formats/pdf/pdf.embedder.js +183 -0
  79. package/dist_ts/formats/pdf/pdf.extractor.d.ts +49 -0
  80. package/dist_ts/formats/pdf/pdf.extractor.js +98 -0
  81. package/dist_ts/formats/pdf/robust-pdf.extractor.d.ts +40 -0
  82. package/dist_ts/formats/pdf/robust-pdf.extractor.js +324 -0
  83. package/dist_ts/formats/ubl/en16931.ubl.validator.d.ts +18 -0
  84. package/dist_ts/formats/ubl/en16931.ubl.validator.js +169 -0
  85. package/dist_ts/formats/ubl/generic/ubl.encoder.d.ts +151 -0
  86. package/dist_ts/formats/ubl/generic/ubl.encoder.js +892 -0
  87. package/dist_ts/formats/ubl/ubl.decoder.d.ts +61 -0
  88. package/dist_ts/formats/ubl/ubl.decoder.js +95 -0
  89. package/dist_ts/formats/ubl/ubl.encoder.d.ts +38 -0
  90. package/dist_ts/formats/ubl/ubl.encoder.js +46 -0
  91. package/dist_ts/formats/ubl/ubl.types.d.ts +16 -0
  92. package/dist_ts/formats/ubl/ubl.types.js +21 -0
  93. package/dist_ts/formats/ubl/ubl.validator.d.ts +50 -0
  94. package/dist_ts/formats/ubl/ubl.validator.js +112 -0
  95. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.d.ts +34 -0
  96. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +424 -0
  97. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.d.ts +75 -0
  98. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.js +555 -0
  99. package/dist_ts/formats/ubl/xrechnung.validator.d.ts +15 -0
  100. package/dist_ts/formats/ubl/xrechnung.validator.js +107 -0
  101. package/dist_ts/formats/ubl.validator.d.ts +82 -0
  102. package/dist_ts/formats/ubl.validator.js +306 -0
  103. package/dist_ts/formats/utils/format.detector.d.ts +62 -0
  104. package/dist_ts/formats/utils/format.detector.js +249 -0
  105. package/dist_ts/formats/validation/en16931.validator.d.ts +23 -0
  106. package/dist_ts/formats/validation/en16931.validator.js +119 -0
  107. package/dist_ts/formats/validator.factory.d.ts +18 -0
  108. package/dist_ts/formats/validator.factory.js +78 -0
  109. package/dist_ts/formats/xinvoice.decoder.d.ts +28 -0
  110. package/dist_ts/formats/xinvoice.decoder.js +332 -0
  111. package/dist_ts/formats/xinvoice.encoder.d.ts +19 -0
  112. package/dist_ts/formats/xinvoice.encoder.js +282 -0
  113. package/dist_ts/index.d.ts +51 -0
  114. package/dist_ts/index.js +90 -0
  115. package/dist_ts/interfaces/common.d.ts +79 -0
  116. package/dist_ts/interfaces/common.js +24 -0
  117. package/dist_ts/interfaces.d.ts +87 -0
  118. package/dist_ts/interfaces.js +23 -0
  119. package/dist_ts/plugins.d.ts +14 -0
  120. package/dist_ts/plugins.js +31 -0
  121. package/npmextra.json +35 -0
  122. package/package.json +71 -0
  123. package/readme.hints.md +1107 -0
  124. package/readme.howtofixtests.md +38 -0
  125. package/readme.literature.md +1 -0
  126. package/readme.md +998 -0
  127. package/readme.plan.md +481 -0
  128. package/ts/00_commitinfo_data.ts +8 -0
  129. package/ts/einvoice.ts +603 -0
  130. package/ts/errors.ts +341 -0
  131. package/ts/formats/base/base.decoder.ts +68 -0
  132. package/ts/formats/base/base.encoder.ts +14 -0
  133. package/ts/formats/base/base.validator.ts +64 -0
  134. package/ts/formats/cii/cii.decoder.ts +139 -0
  135. package/ts/formats/cii/cii.encoder.ts +64 -0
  136. package/ts/formats/cii/cii.types.ts +44 -0
  137. package/ts/formats/cii/cii.validator.ts +171 -0
  138. package/ts/formats/cii/facturx/facturx.decoder.ts +243 -0
  139. package/ts/formats/cii/facturx/facturx.encoder.ts +483 -0
  140. package/ts/formats/cii/facturx/facturx.types.ts +18 -0
  141. package/ts/formats/cii/facturx/facturx.validator.ts +180 -0
  142. package/ts/formats/cii/zugferd/zugferd.decoder.ts +256 -0
  143. package/ts/formats/cii/zugferd/zugferd.encoder.ts +661 -0
  144. package/ts/formats/cii/zugferd/zugferd.types.ts +18 -0
  145. package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +267 -0
  146. package/ts/formats/cii/zugferd/zugferd.validator.ts +31 -0
  147. package/ts/formats/factories/decoder.factory.ts +62 -0
  148. package/ts/formats/factories/encoder.factory.ts +47 -0
  149. package/ts/formats/factories/validator.factory.ts +134 -0
  150. package/ts/formats/pdf/extractors/associated.extractor.ts +78 -0
  151. package/ts/formats/pdf/extractors/base.extractor.ts +355 -0
  152. package/ts/formats/pdf/extractors/index.ts +4 -0
  153. package/ts/formats/pdf/extractors/standard.extractor.ts +86 -0
  154. package/ts/formats/pdf/extractors/text.extractor.ts +162 -0
  155. package/ts/formats/pdf/pdf.embedder.ts +242 -0
  156. package/ts/formats/pdf/pdf.extractor.ts +141 -0
  157. package/ts/formats/ubl/en16931.ubl.validator.ts +216 -0
  158. package/ts/formats/ubl/generic/ubl.encoder.ts +1041 -0
  159. package/ts/formats/ubl/ubl.decoder.ts +121 -0
  160. package/ts/formats/ubl/ubl.encoder.ts +63 -0
  161. package/ts/formats/ubl/ubl.types.ts +22 -0
  162. package/ts/formats/ubl/ubl.validator.ts +133 -0
  163. package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +471 -0
  164. package/ts/formats/ubl/xrechnung/xrechnung.encoder.ts +619 -0
  165. package/ts/formats/ubl/xrechnung.validator.ts +185 -0
  166. package/ts/formats/utils/format.detector.ts +306 -0
  167. package/ts/formats/validation/en16931.validator.ts +135 -0
  168. package/ts/index.ts +164 -0
  169. package/ts/interfaces/common.ts +90 -0
  170. package/ts/interfaces.ts +98 -0
  171. package/ts/plugins.ts +61 -0
@@ -0,0 +1,293 @@
1
+ import * as plugins from '../plugins.js';
2
+ /**
3
+ * A class to convert a given ILetter with invoice data
4
+ * into a Factur-X compliant XML (also compatible with ZUGFeRD and EN16931).
5
+ *
6
+ * Factur-X is the French implementation of the European e-invoicing standard EN16931,
7
+ * which is also implemented in Germany as ZUGFeRD. Both formats are based on
8
+ * UN/CEFACT Cross Industry Invoice (CII) XML schemas.
9
+ */
10
+ export class FacturXEncoder {
11
+ constructor() { }
12
+ /**
13
+ * Alias for createFacturXXml to maintain backward compatibility
14
+ */
15
+ createZugferdXml(letterArg) {
16
+ return this.createFacturXXml(letterArg);
17
+ }
18
+ /**
19
+ * Creates a Factur-X compliant XML based on the provided letter data.
20
+ * This XML is also compliant with ZUGFeRD and EN16931 standards.
21
+ */
22
+ createFacturXXml(letterArg) {
23
+ // 1) Get your "SmartXml" or "xmlbuilder2" instance
24
+ const smartxmlInstance = new plugins.smartxml.SmartXml();
25
+ if (!letterArg?.content?.invoiceData) {
26
+ throw new Error('Letter does not contain invoice data.');
27
+ }
28
+ const invoice = letterArg.content.invoiceData;
29
+ const billedBy = invoice.billedBy;
30
+ const billedTo = invoice.billedTo;
31
+ // 2) Start building the document
32
+ const doc = smartxmlInstance
33
+ .create({ version: '1.0', encoding: 'UTF-8' })
34
+ .ele('rsm:CrossIndustryInvoice', {
35
+ 'xmlns:rsm': 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100',
36
+ 'xmlns:udt': 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100',
37
+ 'xmlns:qdt': 'urn:un:unece:uncefact:data:standard:QualifiedDataType:100',
38
+ 'xmlns:ram': 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'
39
+ });
40
+ // 3) Exchanged Document Context
41
+ const docContext = doc.ele('rsm:ExchangedDocumentContext');
42
+ // Add test indicator
43
+ docContext.ele('ram:TestIndicator')
44
+ .ele('udt:Indicator')
45
+ .txt(this.isDraft(letterArg) ? 'true' : 'false')
46
+ .up()
47
+ .up();
48
+ // Add Factur-X profile information
49
+ // EN16931 profile is compliant with both Factur-X and ZUGFeRD
50
+ docContext.ele('ram:GuidelineSpecifiedDocumentContextParameter')
51
+ .ele('ram:ID')
52
+ .txt('urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:en16931')
53
+ .up()
54
+ .up();
55
+ docContext.up(); // </rsm:ExchangedDocumentContext>
56
+ // 4) Exchanged Document (Invoice Header Info)
57
+ const exchangedDoc = doc.ele('rsm:ExchangedDocument');
58
+ // Invoice ID
59
+ exchangedDoc.ele('ram:ID').txt(invoice.id).up();
60
+ // Document type code
61
+ // 380 = commercial invoice, 381 = credit note
62
+ const documentTypeCode = invoice.type === 'creditnote' ? '381' : '380';
63
+ exchangedDoc.ele('ram:TypeCode').txt(documentTypeCode).up();
64
+ // Issue date
65
+ exchangedDoc
66
+ .ele('ram:IssueDateTime')
67
+ .ele('udt:DateTimeString', { format: '102' })
68
+ // Format 'YYYYMMDD' as per Factur-X specification
69
+ .txt(this.formatDate(letterArg.date))
70
+ .up()
71
+ .up();
72
+ // Document name - Factur-X recommended field
73
+ const documentName = invoice.type === 'creditnote' ? 'CREDIT NOTE' : 'INVOICE';
74
+ exchangedDoc.ele('ram:Name').txt(documentName).up();
75
+ // Optional: Add language indicator (recommended for Factur-X)
76
+ // Use document language if specified, default to 'en'
77
+ const languageCode = letterArg.language?.toUpperCase() || 'EN';
78
+ exchangedDoc
79
+ .ele('ram:IncludedNote')
80
+ .ele('ram:Content').txt('Invoice created with Factur-X compliant software').up()
81
+ .ele('ram:SubjectCode').txt('REG').up() // REG = regulatory information
82
+ .up();
83
+ exchangedDoc.up(); // </rsm:ExchangedDocument>
84
+ // 5) Supply Chain Trade Transaction
85
+ const supplyChainEle = doc.ele('rsm:SupplyChainTradeTransaction');
86
+ // 5.1) Included Supply Chain Trade Line Items
87
+ invoice.items.forEach((item) => {
88
+ const lineItemEle = supplyChainEle.ele('ram:IncludedSupplyChainTradeLineItem');
89
+ lineItemEle.ele('ram:SpecifiedTradeProduct')
90
+ .ele('ram:Name')
91
+ .txt(item.name)
92
+ .up()
93
+ .up(); // </ram:SpecifiedTradeProduct>
94
+ lineItemEle.ele('ram:SpecifiedLineTradeAgreement')
95
+ .ele('ram:GrossPriceProductTradePrice')
96
+ .ele('ram:ChargeAmount')
97
+ .txt(item.unitNetPrice.toFixed(2))
98
+ .up()
99
+ .up()
100
+ .up(); // </ram:SpecifiedLineTradeAgreement>
101
+ lineItemEle.ele('ram:SpecifiedLineTradeDelivery')
102
+ .ele('ram:BilledQuantity')
103
+ .txt(item.unitQuantity.toString())
104
+ .up()
105
+ .up(); // </ram:SpecifiedLineTradeDelivery>
106
+ lineItemEle.ele('ram:SpecifiedLineTradeSettlement')
107
+ .ele('ram:ApplicableTradeTax')
108
+ .ele('ram:RateApplicablePercent')
109
+ .txt(item.vatPercentage.toFixed(2))
110
+ .up()
111
+ .up()
112
+ .ele('ram:SpecifiedTradeSettlementLineMonetarySummation')
113
+ .ele('ram:LineTotalAmount')
114
+ .txt((item.unitQuantity *
115
+ item.unitNetPrice *
116
+ (1 + item.vatPercentage / 100)).toFixed(2))
117
+ .up()
118
+ .up()
119
+ .up(); // </ram:SpecifiedLineTradeSettlement>
120
+ });
121
+ // 5.2) Applicable Header Trade Agreement
122
+ const headerTradeAgreementEle = supplyChainEle.ele('ram:ApplicableHeaderTradeAgreement');
123
+ // Seller
124
+ const sellerPartyEle = headerTradeAgreementEle.ele('ram:SellerTradeParty');
125
+ sellerPartyEle.ele('ram:Name').txt(billedBy.name).up();
126
+ // Example: If it's a company, put company name, etc.
127
+ const sellerAddressEle = sellerPartyEle.ele('ram:PostalTradeAddress');
128
+ sellerAddressEle.ele('ram:PostcodeCode').txt(billedBy.address.postalCode).up();
129
+ sellerAddressEle.ele('ram:LineOne').txt(billedBy.address.streetName).up();
130
+ sellerAddressEle.ele('ram:CityName').txt(billedBy.address.city).up();
131
+ // Typically you'd include 'ram:CountryID' with ISO2 code, e.g. "DE"
132
+ sellerAddressEle.up(); // </ram:PostalTradeAddress>
133
+ sellerPartyEle.up(); // </ram:SellerTradeParty>
134
+ // Buyer
135
+ const buyerPartyEle = headerTradeAgreementEle.ele('ram:BuyerTradeParty');
136
+ buyerPartyEle.ele('ram:Name').txt(billedTo.name).up();
137
+ const buyerAddressEle = buyerPartyEle.ele('ram:PostalTradeAddress');
138
+ buyerAddressEle.ele('ram:PostcodeCode').txt(billedTo.address.postalCode).up();
139
+ buyerAddressEle.ele('ram:LineOne').txt(billedTo.address.streetName).up();
140
+ buyerAddressEle.ele('ram:CityName').txt(billedTo.address.city).up();
141
+ buyerAddressEle.up(); // </ram:PostalTradeAddress>
142
+ buyerPartyEle.up(); // </ram:BuyerTradeParty>
143
+ headerTradeAgreementEle.up(); // </ram:ApplicableHeaderTradeAgreement>
144
+ // 5.3) Applicable Header Trade Delivery
145
+ const headerTradeDeliveryEle = supplyChainEle.ele('ram:ApplicableHeaderTradeDelivery');
146
+ const actualDeliveryEle = headerTradeDeliveryEle.ele('ram:ActualDeliverySupplyChainEvent');
147
+ const occurrenceEle = actualDeliveryEle.ele('ram:OccurrenceDateTime')
148
+ .ele('udt:DateTimeString', { format: '102' });
149
+ const deliveryDate = invoice.deliveryDate || letterArg.date;
150
+ occurrenceEle.txt(this.formatDate(deliveryDate)).up();
151
+ actualDeliveryEle.up(); // </ram:ActualDeliverySupplyChainEvent>
152
+ headerTradeDeliveryEle.up(); // </ram:ApplicableHeaderTradeDelivery>
153
+ // 5.4) Applicable Header Trade Settlement
154
+ const headerTradeSettlementEle = supplyChainEle.ele('ram:ApplicableHeaderTradeSettlement');
155
+ // Tax currency code, doc currency code, etc.
156
+ headerTradeSettlementEle.ele('ram:InvoiceCurrencyCode').txt(invoice.currency).up();
157
+ // Example single tax breakdown
158
+ const tradeTaxEle = headerTradeSettlementEle.ele('ram:ApplicableTradeTax');
159
+ tradeTaxEle.ele('ram:TypeCode').txt('VAT').up();
160
+ tradeTaxEle.ele('ram:CalculatedAmount').txt(this.sumAllVat(invoice).toFixed(2)).up();
161
+ tradeTaxEle
162
+ .ele('ram:RateApplicablePercent')
163
+ .txt(this.extractMainVatRate(invoice.items).toFixed(2))
164
+ .up();
165
+ tradeTaxEle.up(); // </ram:ApplicableTradeTax>
166
+ // Payment Terms
167
+ const paymentTermsEle = headerTradeSettlementEle.ele('ram:SpecifiedTradePaymentTerms');
168
+ // Payment description
169
+ paymentTermsEle.ele('ram:Description').txt(`Payment due in ${invoice.dueInDays} days.`).up();
170
+ // Due date calculation
171
+ const dueDate = new Date(letterArg.date);
172
+ dueDate.setDate(dueDate.getDate() + invoice.dueInDays);
173
+ // Add due date as per Factur-X spec
174
+ paymentTermsEle
175
+ .ele('ram:DueDateDateTime')
176
+ .ele('udt:DateTimeString', { format: '102' })
177
+ .txt(this.formatDate(dueDate.getTime()))
178
+ .up()
179
+ .up();
180
+ // Add payment means if available
181
+ if (invoice.billedBy.sepaConnection) {
182
+ // Add SEPA information as per Factur-X standard
183
+ const paymentMeans = headerTradeSettlementEle.ele('ram:SpecifiedTradeSettlementPaymentMeans');
184
+ paymentMeans.ele('ram:TypeCode').txt('58').up(); // 58 = SEPA credit transfer
185
+ // Payment reference (for bank statement reconciliation)
186
+ paymentMeans.ele('ram:Information').txt(`Reference: ${invoice.id}`).up();
187
+ // Payee account (IBAN)
188
+ if (invoice.billedBy.sepaConnection.iban) {
189
+ const payeeAccount = paymentMeans.ele('ram:PayeePartyCreditorFinancialAccount');
190
+ payeeAccount.ele('ram:IBANID').txt(invoice.billedBy.sepaConnection.iban).up();
191
+ payeeAccount.up();
192
+ }
193
+ // Bank BIC
194
+ if (invoice.billedBy.sepaConnection.bic) {
195
+ const payeeBank = paymentMeans.ele('ram:PayeeSpecifiedCreditorFinancialInstitution');
196
+ payeeBank.ele('ram:BICID').txt(invoice.billedBy.sepaConnection.bic).up();
197
+ payeeBank.up();
198
+ }
199
+ paymentMeans.up();
200
+ }
201
+ paymentTermsEle.up(); // </ram:SpecifiedTradePaymentTerms>
202
+ // Monetary Summation
203
+ const monetarySummationEle = headerTradeSettlementEle.ele('ram:SpecifiedTradeSettlementHeaderMonetarySummation');
204
+ monetarySummationEle
205
+ .ele('ram:LineTotalAmount')
206
+ .txt(this.calcLineTotalNet(invoice).toFixed(2))
207
+ .up();
208
+ monetarySummationEle
209
+ .ele('ram:TaxTotalAmount')
210
+ .txt(this.sumAllVat(invoice).toFixed(2))
211
+ .up();
212
+ monetarySummationEle
213
+ .ele('ram:GrandTotalAmount')
214
+ .txt(this.calcGrandTotal(invoice).toFixed(2))
215
+ .up();
216
+ monetarySummationEle.up(); // </ram:SpecifiedTradeSettlementHeaderMonetarySummation>
217
+ headerTradeSettlementEle.up(); // </ram:ApplicableHeaderTradeSettlement>
218
+ supplyChainEle.up(); // </rsm:SupplyChainTradeTransaction>
219
+ doc.up(); // </rsm:CrossIndustryInvoice>
220
+ // 6) Return the final XML string
221
+ return doc.end({ prettyPrint: true });
222
+ }
223
+ /**
224
+ * Helper: Determine if the letter is in draft or final.
225
+ */
226
+ isDraft(letterArg) {
227
+ return letterArg.versionInfo?.type === 'draft';
228
+ }
229
+ /**
230
+ * Helper: Format date to certain patterns (very minimal example).
231
+ * e.g. 'yyyyMMdd' => '20231231'
232
+ */
233
+ formatDate(timestampMs) {
234
+ const date = new Date(timestampMs);
235
+ const yyyy = date.getFullYear();
236
+ const mm = String(date.getMonth() + 1).padStart(2, '0');
237
+ const dd = String(date.getDate()).padStart(2, '0');
238
+ return `${yyyy}${mm}${dd}`;
239
+ }
240
+ /**
241
+ * Helper: Map your custom 'unitType' to an ISO code or similar.
242
+ */
243
+ mapUnitType(unitType) {
244
+ switch (unitType.toLowerCase()) {
245
+ case 'hour':
246
+ return 'HUR';
247
+ case 'piece':
248
+ return 'C62';
249
+ default:
250
+ return 'C62'; // fallback
251
+ }
252
+ }
253
+ /**
254
+ * Example: Sum all VAT amounts from items.
255
+ */
256
+ sumAllVat(invoice) {
257
+ return invoice.items.reduce((acc, item) => {
258
+ const net = item.unitNetPrice * item.unitQuantity;
259
+ const vat = net * (item.vatPercentage / 100);
260
+ return acc + vat;
261
+ }, 0);
262
+ }
263
+ /**
264
+ * Example: Extract main (or highest) VAT rate from items as representative.
265
+ * In reality, you might list multiple 'ApplicableTradeTax' blocks by group.
266
+ */
267
+ extractMainVatRate(items) {
268
+ let max = 0;
269
+ items.forEach((item) => {
270
+ if (item.vatPercentage > max)
271
+ max = item.vatPercentage;
272
+ });
273
+ return max;
274
+ }
275
+ /**
276
+ * Example: Sum net amounts (without VAT).
277
+ */
278
+ calcLineTotalNet(invoice) {
279
+ return invoice.items.reduce((acc, item) => {
280
+ const net = item.unitNetPrice * item.unitQuantity;
281
+ return acc + net;
282
+ }, 0);
283
+ }
284
+ /**
285
+ * Example: net + VAT = grand total
286
+ */
287
+ calcGrandTotal(invoice) {
288
+ const net = this.calcLineTotalNet(invoice);
289
+ const vat = this.sumAllVat(invoice);
290
+ return net + vat;
291
+ }
292
+ }
293
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdHVyeC5lbmNvZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvZm9ybWF0cy9mYWN0dXJ4LmVuY29kZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFFekM7Ozs7Ozs7R0FPRztBQUNILE1BQU0sT0FBTyxjQUFjO0lBRXpCLGdCQUFlLENBQUM7SUFFaEI7O09BRUc7SUFDSSxnQkFBZ0IsQ0FBQyxTQUEyQztRQUNqRSxPQUFPLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksZ0JBQWdCLENBQUMsU0FBMkM7UUFDakUsbURBQW1EO1FBQ25ELE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBRXpELElBQUksQ0FBQyxTQUFTLEVBQUUsT0FBTyxFQUFFLFdBQVcsRUFBRSxDQUFDO1lBQ3JDLE1BQU0sSUFBSSxLQUFLLENBQUMsdUNBQXVDLENBQUMsQ0FBQztRQUMzRCxDQUFDO1FBRUQsTUFBTSxPQUFPLEdBQXFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDO1FBQ2hGLE1BQU0sUUFBUSxHQUFzQyxPQUFPLENBQUMsUUFBUSxDQUFDO1FBQ3JFLE1BQU0sUUFBUSxHQUFzQyxPQUFPLENBQUMsUUFBUSxDQUFDO1FBRXJFLGlDQUFpQztRQUNqQyxNQUFNLEdBQUcsR0FBRyxnQkFBZ0I7YUFDekIsTUFBTSxDQUFDLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLENBQUM7YUFDN0MsR0FBRyxDQUFDLDBCQUEwQixFQUFFO1lBQy9CLFdBQVcsRUFBRSw4REFBOEQ7WUFDM0UsV0FBVyxFQUFFLDZEQUE2RDtZQUMxRSxXQUFXLEVBQUUsMkRBQTJEO1lBQ3hFLFdBQVcsRUFBRSxvRkFBb0Y7U0FDbEcsQ0FB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
@@ -0,0 +1,67 @@
1
+ import { BaseValidator } from './base.validator.js';
2
+ import { ValidationLevel } from '../interfaces.js';
3
+ import type { ValidationResult } from '../interfaces.js';
4
+ /**
5
+ * Validator for Factur-X/ZUGFeRD invoice format
6
+ * Implements validation rules according to EN16931 and Factur-X specification
7
+ */
8
+ export declare class FacturXValidator extends BaseValidator {
9
+ private static NS_RSMT;
10
+ private static NS_RAM;
11
+ private static NS_UDT;
12
+ private xmlDoc;
13
+ private profile;
14
+ constructor(xml: string);
15
+ /**
16
+ * Validates the Factur-X invoice against the specified level
17
+ * @param level Validation level
18
+ * @returns Validation result
19
+ */
20
+ validate(level?: ValidationLevel): ValidationResult;
21
+ /**
22
+ * Validates XML against schema
23
+ * @returns True if schema validation passed
24
+ */
25
+ protected validateSchema(): boolean;
26
+ /**
27
+ * Validates structure of the XML document
28
+ * @returns True if structure validation passed
29
+ */
30
+ private validateStructure;
31
+ /**
32
+ * Validates business rules
33
+ * @returns True if business rule validation passed
34
+ */
35
+ protected validateBusinessRules(): boolean;
36
+ /**
37
+ * Detects Factur-X profile from the XML
38
+ */
39
+ private detectProfile;
40
+ /**
41
+ * Validates amount calculations in the invoice
42
+ * @returns True if amount validation passed
43
+ */
44
+ private validateAmounts;
45
+ /**
46
+ * Validates mutually exclusive fields
47
+ * @returns True if validation passed
48
+ */
49
+ private validateMutuallyExclusiveFields;
50
+ /**
51
+ * Validates seller VAT identifier requirements
52
+ * @returns True if validation passed
53
+ */
54
+ private validateSellerVatIdentifier;
55
+ /**
56
+ * Helper method to check if a node exists
57
+ * @param xpathExpression XPath to check
58
+ * @returns True if node exists
59
+ */
60
+ private exists;
61
+ /**
62
+ * Helper method to get a number value from XPath
63
+ * @param xpathExpression XPath to get number from
64
+ * @returns Number value or NaN if not found
65
+ */
66
+ private getNumberValue;
67
+ }
@@ -0,0 +1,266 @@
1
+ import { BaseValidator } from './base.validator.js';
2
+ import { ValidationLevel } from '../interfaces.js';
3
+ import * as xpath from 'xpath';
4
+ import { DOMParser } from 'xmldom';
5
+ /**
6
+ * Validator for Factur-X/ZUGFeRD invoice format
7
+ * Implements validation rules according to EN16931 and Factur-X specification
8
+ */
9
+ export class FacturXValidator extends BaseValidator {
10
+ // XML namespaces for Factur-X/ZUGFeRD
11
+ static { this.NS_RSMT = 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100'; }
12
+ static { this.NS_RAM = 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'; }
13
+ static { this.NS_UDT = 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'; }
14
+ constructor(xml) {
15
+ super(xml);
16
+ // XML document for processing
17
+ this.xmlDoc = null;
18
+ // Factur-X profile (BASIC, EN16931, EXTENDED, etc.)
19
+ this.profile = '';
20
+ try {
21
+ // Parse XML document
22
+ this.xmlDoc = new DOMParser().parseFromString(xml, 'application/xml');
23
+ // Determine Factur-X profile
24
+ this.detectProfile();
25
+ }
26
+ catch (error) {
27
+ this.addError('FX-PARSE', `Failed to parse XML: ${error}`, '/');
28
+ }
29
+ }
30
+ /**
31
+ * Validates the Factur-X invoice against the specified level
32
+ * @param level Validation level
33
+ * @returns Validation result
34
+ */
35
+ validate(level = ValidationLevel.SYNTAX) {
36
+ // Reset errors
37
+ this.errors = [];
38
+ // Check if document was parsed successfully
39
+ if (!this.xmlDoc) {
40
+ return {
41
+ valid: false,
42
+ errors: this.errors,
43
+ level: level
44
+ };
45
+ }
46
+ // Perform validation based on level
47
+ let valid = true;
48
+ if (level === ValidationLevel.SYNTAX) {
49
+ valid = this.validateSchema();
50
+ }
51
+ else if (level === ValidationLevel.SEMANTIC) {
52
+ valid = this.validateSchema() && this.validateStructure();
53
+ }
54
+ else if (level === ValidationLevel.BUSINESS) {
55
+ valid = this.validateSchema() &&
56
+ this.validateStructure() &&
57
+ this.validateBusinessRules();
58
+ }
59
+ return {
60
+ valid,
61
+ errors: this.errors,
62
+ level
63
+ };
64
+ }
65
+ /**
66
+ * Validates XML against schema
67
+ * @returns True if schema validation passed
68
+ */
69
+ validateSchema() {
70
+ // Basic schema validation (simplified for now)
71
+ if (!this.xmlDoc)
72
+ return false;
73
+ // Check for root element
74
+ const root = this.xmlDoc.documentElement;
75
+ if (!root || root.nodeName !== 'rsm:CrossIndustryInvoice') {
76
+ this.addError('FX-SCHEMA-1', 'Root element must be rsm:CrossIndustryInvoice', '/');
77
+ return false;
78
+ }
79
+ // Check for required namespaces
80
+ if (!root.lookupNamespaceURI('rsm') || !root.lookupNamespaceURI('ram')) {
81
+ this.addError('FX-SCHEMA-2', 'Required namespaces rsm and ram must be declared', '/');
82
+ return false;
83
+ }
84
+ return true;
85
+ }
86
+ /**
87
+ * Validates structure of the XML document
88
+ * @returns True if structure validation passed
89
+ */
90
+ validateStructure() {
91
+ if (!this.xmlDoc)
92
+ return false;
93
+ let valid = true;
94
+ // Check for required main sections
95
+ const sections = [
96
+ 'rsm:ExchangedDocumentContext',
97
+ 'rsm:ExchangedDocument',
98
+ 'rsm:SupplyChainTradeTransaction'
99
+ ];
100
+ for (const section of sections) {
101
+ if (!this.exists(section)) {
102
+ this.addError('FX-STRUCT-1', `Required section ${section} is missing`, '/rsm:CrossIndustryInvoice');
103
+ valid = false;
104
+ }
105
+ }
106
+ // Check for SupplyChainTradeTransaction sections
107
+ if (this.exists('rsm:SupplyChainTradeTransaction')) {
108
+ const tradeSubsections = [
109
+ 'ram:ApplicableHeaderTradeAgreement',
110
+ 'ram:ApplicableHeaderTradeDelivery',
111
+ 'ram:ApplicableHeaderTradeSettlement'
112
+ ];
113
+ for (const subsection of tradeSubsections) {
114
+ if (!this.exists(`rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeTransaction/${subsection}`)) {
115
+ this.addError('FX-STRUCT-2', `Required subsection ${subsection} is missing`, '/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeTransaction');
116
+ valid = false;
117
+ }
118
+ }
119
+ }
120
+ return valid;
121
+ }
122
+ /**
123
+ * Validates business rules
124
+ * @returns True if business rule validation passed
125
+ */
126
+ validateBusinessRules() {
127
+ if (!this.xmlDoc)
128
+ return false;
129
+ let valid = true;
130
+ // BR-16: Amount due for payment (BT-115) = Invoice total amount with VAT (BT-112) - Paid amount (BT-113)
131
+ valid = this.validateAmounts() && valid;
132
+ // BR-CO-3: Value added tax point date (BT-7) and Value added tax point date code (BT-8) are mutually exclusive
133
+ valid = this.validateMutuallyExclusiveFields() && valid;
134
+ // BR-S-1: An Invoice that contains a line (BG-25) where the Invoiced item VAT category code (BT-151) is "Standard rated"
135
+ // shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32)
136
+ // and/or the Seller tax representative VAT identifier (BT-63).
137
+ valid = this.validateSellerVatIdentifier() && valid;
138
+ return valid;
139
+ }
140
+ /**
141
+ * Detects Factur-X profile from the XML
142
+ */
143
+ detectProfile() {
144
+ if (!this.xmlDoc)
145
+ return;
146
+ // Look for profile identifier
147
+ const profileNode = xpath.select1('string(//rsm:ExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID)', this.xmlDoc);
148
+ if (profileNode) {
149
+ const profileText = profileNode.toString();
150
+ if (profileText.includes('BASIC')) {
151
+ this.profile = 'BASIC';
152
+ }
153
+ else if (profileText.includes('EN16931')) {
154
+ this.profile = 'EN16931';
155
+ }
156
+ else if (profileText.includes('EXTENDED')) {
157
+ this.profile = 'EXTENDED';
158
+ }
159
+ else if (profileText.includes('MINIMUM')) {
160
+ this.profile = 'MINIMUM';
161
+ }
162
+ }
163
+ }
164
+ /**
165
+ * Validates amount calculations in the invoice
166
+ * @returns True if amount validation passed
167
+ */
168
+ validateAmounts() {
169
+ if (!this.xmlDoc)
170
+ return false;
171
+ try {
172
+ // Extract amounts
173
+ const totalAmount = this.getNumberValue('//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:GrandTotalAmount');
174
+ const paidAmount = this.getNumberValue('//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TotalPrepaidAmount') || 0;
175
+ const dueAmount = this.getNumberValue('//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:DuePayableAmount');
176
+ // Calculate expected due amount
177
+ const expectedDueAmount = totalAmount - paidAmount;
178
+ // Compare with a small tolerance for rounding errors
179
+ if (Math.abs(dueAmount - expectedDueAmount) > 0.01) {
180
+ this.addError('BR-16', `Amount due for payment (${dueAmount}) must equal Invoice total amount with VAT (${totalAmount}) - Paid amount (${paidAmount})`, '//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation');
181
+ return false;
182
+ }
183
+ return true;
184
+ }
185
+ catch (error) {
186
+ this.addError('FX-AMOUNT', `Error validating amounts: ${error}`, '/');
187
+ return false;
188
+ }
189
+ }
190
+ /**
191
+ * Validates mutually exclusive fields
192
+ * @returns True if validation passed
193
+ */
194
+ validateMutuallyExclusiveFields() {
195
+ if (!this.xmlDoc)
196
+ return false;
197
+ try {
198
+ // Check for VAT point date and code (BR-CO-3)
199
+ const vatPointDate = this.exists('//ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax/ram:TaxPointDate');
200
+ const vatPointDateCode = this.exists('//ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax/ram:DueDateTypeCode');
201
+ if (vatPointDate && vatPointDateCode) {
202
+ this.addError('BR-CO-3', 'Value added tax point date and Value added tax point date code are mutually exclusive', '//ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax');
203
+ return false;
204
+ }
205
+ return true;
206
+ }
207
+ catch (error) {
208
+ this.addError('FX-MUTUAL', `Error validating mutually exclusive fields: ${error}`, '/');
209
+ return false;
210
+ }
211
+ }
212
+ /**
213
+ * Validates seller VAT identifier requirements
214
+ * @returns True if validation passed
215
+ */
216
+ validateSellerVatIdentifier() {
217
+ if (!this.xmlDoc)
218
+ return false;
219
+ try {
220
+ // Check if there are any standard rated line items
221
+ const standardRatedItems = this.exists('//ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:CategoryCode[text()="S"]');
222
+ if (standardRatedItems) {
223
+ // Check for seller VAT identifier
224
+ const sellerVatId = this.exists('//ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]');
225
+ const sellerTaxId = this.exists('//ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="FC"]');
226
+ const sellerTaxRepId = this.exists('//ram:ApplicableHeaderTradeAgreement/ram:SellerTaxRepresentativeTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]');
227
+ if (!sellerVatId && !sellerTaxId && !sellerTaxRepId) {
228
+ this.addError('BR-S-1', 'An Invoice with standard rated items must contain the Seller VAT Identifier, Tax registration identifier or Tax representative VAT identifier', '//ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty');
229
+ return false;
230
+ }
231
+ }
232
+ return true;
233
+ }
234
+ catch (error) {
235
+ this.addError('FX-VAT', `Error validating seller VAT identifier: ${error}`, '/');
236
+ return false;
237
+ }
238
+ }
239
+ /**
240
+ * Helper method to check if a node exists
241
+ * @param xpathExpression XPath to check
242
+ * @returns True if node exists
243
+ */
244
+ exists(xpathExpression) {
245
+ if (!this.xmlDoc)
246
+ return false;
247
+ const nodes = xpath.select(xpathExpression, this.xmlDoc);
248
+ // Handle different return types from xpath.select()
249
+ if (Array.isArray(nodes)) {
250
+ return nodes.length > 0;
251
+ }
252
+ return nodes ? true : false;
253
+ }
254
+ /**
255
+ * Helper method to get a number value from XPath
256
+ * @param xpathExpression XPath to get number from
257
+ * @returns Number value or NaN if not found
258
+ */
259
+ getNumberValue(xpathExpression) {
260
+ if (!this.xmlDoc)
261
+ return NaN;
262
+ const node = xpath.select1(`string(${xpathExpression})`, this.xmlDoc);
263
+ return node ? parseFloat(node.toString()) : NaN;
264
+ }
265
+ }
266
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdHVyeC52YWxpZGF0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9mb3JtYXRzL2ZhY3R1cngudmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUNwRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFFbkQsT0FBTyxLQUFLLEtBQUssTUFBTSxPQUFPLENBQUM7QUFDL0IsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUVuQzs7O0dBR0c7QUFDSCxNQUFNLE9BQU8sZ0JBQWlCLFNBQVEsYUFBYTtJQUNqRCxzQ0FBc0M7YUFDdkIsWUFBTyxHQUFHLDhEQUE4RCxBQUFqRSxDQUFrRTthQUN6RSxXQUFNLEdBQUcsb0ZBQW9GLEFBQXZGLENBQXdGO2FBQzlGLFdBQU0sR0FBRyw2REFBNkQsQUFBaEUsQ0FBaUU7SUFRdEYsWUFBWSxHQUFXO1FBQ3JCLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQVBiLDhCQUE4QjtRQUN0QixXQUFNLEdBQW9CLElBQUksQ0FBQztRQUV2QyxvREFBb0Q7UUFDNUMsWUFBTyxHQUFXLEVBQUUsQ0FBQztRQUszQixJQUFJLENBQUM7WUFDSCxxQkFBcUI7WUFDckIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLFNBQVMsRUFBRSxDQUFDLGVBQWUsQ0FBQyxHQUFHLEVBQUUsaUJBQWlCLENBQUMsQ0FBQztZQUV0RSw2QkFBNkI7WUFDN0IsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO1FBQ3ZCLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLEVBQUUsd0JBQXdCLEtBQUssRUFBRSxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQ2xFLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNJLFFBQVEsQ0FBQyxRQUF5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