Messaging: OAuth Sending
Jobs: Sichtbeleg Anpassungen, Seitenwechsel, Freiräume und Taxpos
This commit is contained in:
@@ -24,7 +24,7 @@ Public Class XRechnungViewDocument
|
||||
Private Const MARGIN_LEFT As Integer = 10
|
||||
Private Const MARGIN_TOP As Integer = 15
|
||||
Private Const LINE_WIDTH As Integer = 200
|
||||
Private Const PAGE_HEIGHT_LIMIT As Integer = 270
|
||||
Private Const PAGE_HEIGHT_LIMIT As Integer = 275
|
||||
Private Const FOOTER_Y As Integer = 280
|
||||
Private Const FOOTER_TEXT_Y As Integer = 285
|
||||
Private Const LINE_HEIGHT As Integer = 5
|
||||
@@ -39,6 +39,14 @@ Public Class XRechnungViewDocument
|
||||
Private Const COL_POS_SUM As Integer = 181
|
||||
Private Const COL_VALUE_X As Integer = 70
|
||||
|
||||
' Spalten für TAXPOS-Tabelle (Steuerbeträge):
|
||||
' BT 116 <TAB> 2.116,36 € (BASEAMOUNT-Zeile)
|
||||
' BT 119 <TAB> 7.00 % <TAB> 148,14 € <TAB> VAT (RATE-Zeile)
|
||||
Private Const COL_TAXPOS_LABEL As Integer = MARGIN_LEFT ' "BT 116" / "BT 119"
|
||||
Private Const COL_TAXPOS_VALUE1 As Integer = 50 ' Basisbetrag ODER Steuersatz (%)
|
||||
Private Const COL_TAXPOS_AMOUNT As Integer = 100 ' Steuerbetrag (nur RATE-Zeile)
|
||||
Private Const COL_TAXPOS_TYPE As Integer = 150 ' VAT-Kennzeichen (nur RATE-Zeile)
|
||||
|
||||
' Text-Größen
|
||||
Private Const TEXT_SIZE_TITLE As Integer = 18
|
||||
Private Const TEXT_SIZE_NORMAL As Integer = 10
|
||||
@@ -174,6 +182,9 @@ Public Class XRechnungViewDocument
|
||||
DrawHeader(context.PDF)
|
||||
DrawFooter(context.PDF, context.CreatedString)
|
||||
context.YPosition = MARGIN_TOP + 30 ' Nach Header
|
||||
' WICHTIG: CurrentPositionPage wird hier NICHT gesetzt.
|
||||
' Es gehört zum Positions-Anker und wird nur in HandleArticleTextFollowUp
|
||||
' und HandlePositionAmountFollowUp gesetzt – nicht beim Seitenbruch selbst.
|
||||
End Sub
|
||||
|
||||
Private Sub DrawHeader(pdf As GdPicturePDF)
|
||||
@@ -412,6 +423,7 @@ Public Class XRechnungViewDocument
|
||||
context.YDynamic = 0
|
||||
context.YPosition += LINE_HEIGHT ' Neue Zeile für erste Position
|
||||
context.CurrentPositionStartY = context.YPosition
|
||||
context.CurrentPositionPage = context.PDF.GetPageCount()
|
||||
context.PDF.DrawText(fontResName, COL_POS_NUMBER, context.YPosition, "1") ' Erste Position ist immer 1
|
||||
context.PDF.DrawText(fontResName, COL_POS_AMOUNT, context.YPosition, itemData.Value)
|
||||
context.PositionCount = 1 ' Zähler auf 1 setzen statt increment
|
||||
@@ -447,15 +459,42 @@ Public Class XRechnungViewDocument
|
||||
End Sub
|
||||
|
||||
Private Sub HandleTaxPosAreaSwitch(context As PdfRenderContext, itemData As InvoiceItemData)
|
||||
If itemData.SpecName = "INVOICE_TAXPOS_RATE" Then
|
||||
context.PositionCount = 1
|
||||
context.TaxPosText = $"{itemData.Value} %: " ' ← In Context speichern!
|
||||
context.IsFirstTaxPosDisplay = True ' ← Flag setzen!
|
||||
' Die TAXPOS-Area kann pro Steuersatz-Gruppe mit BASEAMOUNT ODER RATE beginnen:
|
||||
' Gruppe: [BASEAMOUNT] RATE AMOUNT TYPE (BASEAMOUNT optional, ggf. nicht vorhanden)
|
||||
' Jede Gruppe ergibt GENAU EINE Zeile, sobald TYPE (VAT) eintrifft - außer der
|
||||
' optionalen BASEAMOUNT-Zeile, die SOFORT und SEPARAT gerendert wird.
|
||||
' WICHTIG: Dies ist die allererste Zeile der Area - kein zusätzlicher Zeilenvorschub.
|
||||
context.IsFirstTaxPosDisplay = True
|
||||
|
||||
If itemData.SpecName = "INVOICE_TAXPOS_BASEAMOUNT" Then
|
||||
RenderTaxposBaseAmountRow(context, itemData)
|
||||
itemData.Display = False
|
||||
_logger.Debug($"TAXPOS RATE in AreaSwitch accumulated: [{context.TaxPosText}]")
|
||||
ElseIf itemData.SpecName = "INVOICE_TAXPOS_RATE" Then
|
||||
context.TaxPosRate = itemData.Value
|
||||
context.TaxPosRateCaption = itemData.Caption
|
||||
itemData.Display = False
|
||||
_logger.Debug($"TAXPOS RATE in AreaSwitch (group start, no BASEAMOUNT) accumulated: [{context.TaxPosRate}]")
|
||||
End If
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' Rendert die Basisbetrag-Zeile sofort in eigener Zeile: "BT 116 <TAB> 2.116,36 €"
|
||||
''' Diese Zeile ist unabhängig von der nachfolgenden RATE/AMOUNT/TYPE-Zeile.
|
||||
''' </summary>
|
||||
Private Sub RenderTaxposBaseAmountRow(context As PdfRenderContext, itemData As InvoiceItemData)
|
||||
' Erste Zeile der TAXPOS-Area braucht keinen zusätzlichen Zeilenvorschub
|
||||
' (Area-Header hat YPosition bereits korrekt positioniert); alle weiteren Zeilen schon.
|
||||
If Not context.IsFirstTaxPosDisplay Then
|
||||
context.YPosition += LINE_HEIGHT
|
||||
End If
|
||||
context.IsFirstTaxPosDisplay = False
|
||||
|
||||
Dim baseAmountFormatted As String = FormatCurrency(itemData.Value, context.CurrencySymbol)
|
||||
context.PDF.DrawText(fontResName, COL_TAXPOS_LABEL, context.YPosition, itemData.Caption)
|
||||
context.PDF.DrawText(fontResName, COL_TAXPOS_VALUE1, context.YPosition, baseAmountFormatted)
|
||||
_logger.Debug($"TAXPOS BASEAMOUNT row rendered: [{itemData.Caption}] [{baseAmountFormatted}] at Y={context.YPosition}")
|
||||
End Sub
|
||||
|
||||
#End Region
|
||||
|
||||
|
||||
@@ -506,7 +545,6 @@ Public Class XRechnungViewDocument
|
||||
ElseIf itemData.SpecName = "INVOICE_POSITION_UNIT_TYPE" Then
|
||||
HandleUnitTypeFollowUp(context, itemData)
|
||||
ElseIf {"POSITION_ALLOWANCE_REASON", "RECEIPT_ALLOWANCE_REASON"}.Contains(itemData.SpecName) Then
|
||||
' ALLOWANCE_REASON direkt in Spalte schreiben
|
||||
context.PDF.DrawText(fontResName, COL_POS_REASON, context.YPosition, itemData.Value)
|
||||
itemData.Display = False
|
||||
ElseIf {"INVOICE_POSITION_ARTICLE", "INVOICE_POSITION_ARTICLE_DESCRIPTION"}.Contains(itemData.SpecName) Then
|
||||
@@ -514,37 +552,63 @@ Public Class XRechnungViewDocument
|
||||
ElseIf itemData.SpecName = "INVOICE_POSITION_NOTE" Then
|
||||
HandlePositionNoteFollowUp(context, itemData)
|
||||
ElseIf {"INVOICE_TAXPOS_TAX_RATE", "INVOICE_TAXPOS_RATE"}.Contains(itemData.SpecName) Then
|
||||
' ← NUR für POSITION: Tax Rate
|
||||
HandleTaxRateFollowUp(context, itemData)
|
||||
ElseIf {"RECEIPT_ALLOWANCE_VAT_RATE", "POSITION_ALLOWANCE_VAT_RATE"}.Contains(itemData.SpecName) Then
|
||||
' ← NEU: Für ALLOWANCE: VAT Rate (nicht CALCULATION_PERCENT!)
|
||||
HandleTaxRateFollowUp(context, itemData)
|
||||
ElseIf itemData.SpecName = "INVOICE_POSITION_TAX_AMOUNT" Then
|
||||
HandlePositionTaxAmountFollowUp(context, itemData)
|
||||
ElseIf {"RECEIPT_ALLOWANCE_VAT_CODE", "POSITION_ALLOWANCE_VAT_CODE"}.Contains(itemData.SpecName) Then
|
||||
' VAT_CODE wird nicht angezeigt (nur Metadata)
|
||||
itemData.Display = False
|
||||
ElseIf {"RECEIPT_ALLOWANCE_CALCULATION_PERCENT", "POSITION_ALLOWANCE_CALCULATION_PERCENT"}.Contains(itemData.SpecName) Then
|
||||
' ← NEU: CALCULATION_PERCENT wird nicht angezeigt (nur Metadata)
|
||||
itemData.Display = False
|
||||
ElseIf itemData.SpecName = "RECEIPT_ALLOWANCE_CHARGE_INDICATOR" Then
|
||||
' CHARGE_INDICATOR im Follow-Up (zweite Allowance) wird nicht angezeigt
|
||||
itemData.Display = False
|
||||
End If
|
||||
End Sub
|
||||
|
||||
Private Sub HandlePositionAmountFollowUp(context As PdfRenderContext, itemData As InvoiceItemData, descriptionFollowup As Boolean)
|
||||
context.PositionCount += 1
|
||||
|
||||
' *** BUGFIX: Unnötige Leerzeile zwischen Positionen ***
|
||||
'
|
||||
' HandleArticleTextFollowUp synct am Ende jeder Position:
|
||||
' context.YPosition = Math.Max(context.YPosition, context.YDynamic)
|
||||
' Nach einer mehrzeiligen DESCRIPTION (z.B. 2 Teile) zeigt YDynamic bereits auf die
|
||||
' NÄCHSTE freie Zeile (RenderMultiLineText erhöht YDynamic nach jedem Teil). D.h.
|
||||
' YPosition steht zu Beginn dieser Methode oft schon korrekt auf der nächsten freien
|
||||
' Zeile - ein zusätzliches "+= LINE_HEIGHT" weiter unten würde dann eine echte
|
||||
' Leerzeile zwischen letzter DESCRIPTION-Zeile und der neuen Position erzeugen.
|
||||
'
|
||||
' Erkennungsmerkmal: YDynamic > 0 UND YDynamic = YPosition bedeutet, die vorherige
|
||||
' Position hat bereits über RenderMultiLineText/HandleArticleTextFollowUp die
|
||||
' YPosition auf die korrekte nächste freie Zeile gesetzt - dann KEIN weiterer Vorschub.
|
||||
Dim yPositionAlreadyAdvanced As Boolean = (context.YDynamic > 0) AndAlso (context.YDynamic = context.YPosition)
|
||||
|
||||
If Not descriptionFollowup Then
|
||||
context.YPlus = 0
|
||||
context.YDynamic = 0
|
||||
End If
|
||||
|
||||
' WICHTIG: Neue Zeile für jede neue Position!
|
||||
context.YPosition += LINE_HEIGHT
|
||||
|
||||
' ← NEU: Start-Y der aktuellen Position speichern
|
||||
If Not yPositionAlreadyAdvanced Then
|
||||
context.YPosition += LINE_HEIGHT
|
||||
End If
|
||||
context.CurrentPositionStartY = context.YPosition
|
||||
context.CurrentPositionPage = context.PDF.GetPageCount()
|
||||
|
||||
' ✓ OPTIMIERT: Von 11mm auf 6mm
|
||||
' WARUM 6mm?
|
||||
' - Position#-Zeile: 5mm (wird SOFORT gerendert)
|
||||
' - Tax/Amount: 0mm Extra (werden auf DERSELBEN Y-Position gerendert via CurrentPositionStartY)
|
||||
' - Minimaler Puffer: 1mm (für Textbox-Rendering-Toleranzen)
|
||||
Dim requiredSpace As Integer = 6
|
||||
|
||||
If (context.YPosition + requiredSpace) >= PAGE_HEIGHT_LIMIT Then
|
||||
CreateNewPage(context)
|
||||
context.YDynamic = context.YPosition
|
||||
context.CurrentPositionStartY = context.YPosition
|
||||
context.CurrentPositionPage = context.PDF.GetPageCount() ' neue Seite ist der Anker
|
||||
_logger.Debug($"HandlePositionAmountFollowUp: Page break! New YPosition={context.YPosition}, CurrentPositionStartY={context.CurrentPositionStartY}, Page={context.CurrentPositionPage}")
|
||||
End If
|
||||
|
||||
context.PDF.DrawText(fontResName, COL_POS_NUMBER, context.YPosition, context.PositionCount.ToString())
|
||||
|
||||
@@ -587,9 +651,41 @@ Public Class XRechnungViewDocument
|
||||
xPos = COL_POS_REASON
|
||||
End If
|
||||
|
||||
RenderMultiLineText(context, itemData.Value, xPos, MAX_TEXT_LENGTH_POSITION)
|
||||
' *** BUGFIX: CurrentPositionStartY auf die ERSTE Zeile von INVOICE_POSITION_ARTICLE verankern ***
|
||||
'
|
||||
' Tax (COL_POS_TAX) und Amount (COL_POS_SUM) müssen in der ERSTEN Zeile des Artikeltexts
|
||||
' erscheinen - dort wo der Artikelname und damit auch der Steuersatz/Betrag laut Layout
|
||||
' hingehören. Bei kurzen (einzeiligen) Artikeln ist "erste Zeile" = "letzte Zeile", daher
|
||||
' lieferte die alte Berechnung (YDynamic - LINE_HEIGHT, NACH dem Rendern) bislang dasselbe
|
||||
' Ergebnis. Bei LANGEN, mehrzeiligen Artikeltexten (z.B. 15 Zeilen Prüfbeschreibung, die
|
||||
' selbst über einen Seitenbruch laufen) zeigte "YDynamic - LINE_HEIGHT NACH dem Rendern"
|
||||
' auf die LETZTE Zeile des Artikeltexts (ggf. auf einer späteren Seite) - das war falsch.
|
||||
'
|
||||
' Die neuen ByRef-Parameter firstAnchorY/firstAnchorPage liefern stattdessen exakt den
|
||||
' Y-Wert und die Seite des ALLERERSTEN tatsächlich gezeichneten Textteils, erfasst
|
||||
' INNERHALB von RenderMultiLineText direkt vor dem ersten DrawText-Aufruf - das ist in
|
||||
' allen Fällen korrekt, auch wenn der Seitenbruch noch VOR dem ersten Zeichnen eintritt
|
||||
' (Szenario B: Artikel beginnt komplett auf neuer Seite).
|
||||
'
|
||||
' Szenario A – kein Seitenbruch, kurzer Artikel: erste Zeile=240/Seite1 → Anker=240/1 (wie bisher) ✓
|
||||
' Szenario B – FIRST-part-Break (Artikel beginnt auf neuer Seite): erste Zeile=45/Seite2 → Anker=45/2 (wie bisher) ✓
|
||||
' Szenario C – CONTINUATION-Break in DESCRIPTION (Artikel einzeilig, bleibt auf alter Seite):
|
||||
' erste Zeile=265/Seite1 → Anker=265/1 (wie bisher, unverändert) ✓
|
||||
' Szenario D – NEU: langer mehrzeiliger ARTIKEL, der selbst über mehrere Seiten läuft:
|
||||
' erste Zeile=220/Seite1 → Anker=220/1 (KORRIGIERT, vorher fälschlich letzte Zeile/spätere Seite)
|
||||
If itemData.SpecName = "INVOICE_POSITION_ARTICLE" Then
|
||||
Dim firstAnchorY As Integer = -1
|
||||
Dim firstAnchorPage As Integer = -1
|
||||
RenderMultiLineText(context, itemData.Value, xPos, MAX_TEXT_LENGTH_POSITION, firstAnchorY, firstAnchorPage)
|
||||
|
||||
' ← NEU: YPosition auf den höchsten erreichten Wert setzen
|
||||
context.CurrentPositionStartY = firstAnchorY
|
||||
context.CurrentPositionPage = firstAnchorPage
|
||||
_logger.Debug($"HandleArticleTextFollowUp: CurrentPositionStartY anchored to FIRST ARTICLE row Y={context.CurrentPositionStartY} on page {context.CurrentPositionPage}")
|
||||
Else
|
||||
RenderMultiLineText(context, itemData.Value, xPos, MAX_TEXT_LENGTH_POSITION)
|
||||
End If
|
||||
|
||||
' YPosition immer synchronisieren
|
||||
context.YPosition = Math.Max(context.YPosition, context.YDynamic)
|
||||
|
||||
itemData.Display = False
|
||||
@@ -602,28 +698,74 @@ Public Class XRechnungViewDocument
|
||||
|
||||
RenderMultiLineText(context, itemData.Value, COL_POS_TEXT, MAX_TEXT_LENGTH_NOTE)
|
||||
|
||||
' ← NEU: YPosition synchronisieren
|
||||
' ✓ KORRIGIERT: YPosition synchronisieren
|
||||
context.YPosition = Math.Max(context.YPosition, context.YDynamic)
|
||||
|
||||
itemData.Display = False
|
||||
End Sub
|
||||
|
||||
Private Sub HandleTaxRateFollowUp(context As PdfRenderContext, itemData As InvoiceItemData)
|
||||
' ← VERWENDE die Start-Y-Position der aktuellen Position!
|
||||
Dim yPos As Integer = context.CurrentPositionStartY
|
||||
_logger.Debug($"Handling Tax Rate Follow-Up: Value=[{itemData.Value}] at YPos={yPos}")
|
||||
Dim targetPage As Integer = context.CurrentPositionPage
|
||||
Dim activePage As Integer = context.PDF.GetPageCount()
|
||||
|
||||
_logger.Debug($"Handling Tax Rate Follow-Up: Value=[{itemData.Value}] at YPos={yPos}, targetPage={targetPage}, activePage={activePage}")
|
||||
|
||||
If yPos < MARGIN_TOP + 30 Then
|
||||
_logger.Warn($"TaxRate: yPos={yPos} ungültig. Verwende YPosition={context.YPosition}.")
|
||||
yPos = context.YPosition
|
||||
targetPage = activePage
|
||||
End If
|
||||
|
||||
If targetPage <> activePage Then
|
||||
context.PDF.SelectPage(targetPage)
|
||||
_logger.Debug($"TaxRate: SelectPage({targetPage}) to draw at Y={yPos}, then back to page {activePage}")
|
||||
End If
|
||||
|
||||
' SetTextSize explizit setzen: GdPicturePDF hält Font-Zustand global;
|
||||
' nach SelectPage ist er undefiniert und muss vor jedem DrawText normalisiert werden.
|
||||
context.PDF.SetTextSize(TEXT_SIZE_NORMAL)
|
||||
context.PDF.DrawText(fontResName, COL_POS_TAX, yPos, $"{itemData.Value} %")
|
||||
|
||||
If targetPage <> activePage Then
|
||||
context.PDF.SelectPage(activePage)
|
||||
context.PDF.SetTextSize(TEXT_SIZE_NORMAL) ' Font-Zustand der aktiven Seite wiederherstellen
|
||||
End If
|
||||
|
||||
itemData.Display = False
|
||||
End Sub
|
||||
|
||||
Private Sub HandlePositionTaxAmountFollowUp(context As PdfRenderContext, itemData As InvoiceItemData)
|
||||
Dim yPos As Integer = context.CurrentPositionStartY
|
||||
Dim targetPage As Integer = context.CurrentPositionPage
|
||||
Dim activePage As Integer = context.PDF.GetPageCount()
|
||||
|
||||
If yPos < MARGIN_TOP + 30 Then
|
||||
_logger.Warn($"TaxAmount: yPos={yPos} ungültig. Verwende YPosition={context.YPosition}.")
|
||||
yPos = context.YPosition
|
||||
targetPage = activePage
|
||||
End If
|
||||
|
||||
Dim yPosAdjusted As Double = yPos - 3.5
|
||||
Dim taxTerm As String = FormatCurrency(itemData.Value, context.CurrencySymbol)
|
||||
_logger.Debug($"Handling Position Tax Amount Follow-Up: Value=[{itemData.Value}] Formatted=[{taxTerm}] at YPos={yPos}, Adjusted YPos={yPosAdjusted}")
|
||||
_logger.Debug($"Handling Position Tax Amount Follow-Up: Value=[{itemData.Value}] Formatted=[{taxTerm}] at YPos={yPos}, Adjusted={yPosAdjusted}, targetPage={targetPage}, activePage={activePage}")
|
||||
|
||||
If targetPage <> activePage Then
|
||||
context.PDF.SelectPage(targetPage)
|
||||
_logger.Debug($"TaxAmount: SelectPage({targetPage}) to draw at Y={yPosAdjusted}, then back to page {activePage}")
|
||||
End If
|
||||
|
||||
' SetTextSize explizit setzen – gleicher Grund wie in HandleTaxRateFollowUp.
|
||||
context.PDF.SetTextSize(TEXT_SIZE_NORMAL)
|
||||
context.PDF.DrawTextBox(fontResName, 177, yPosAdjusted, 198, YCoo_TextBoxPlus5(yPosAdjusted),
|
||||
TextAlignment.TextAlignmentFar, TextAlignment.TextAlignmentNear,
|
||||
taxTerm)
|
||||
|
||||
If targetPage <> activePage Then
|
||||
context.PDF.SelectPage(activePage)
|
||||
context.PDF.SetTextSize(TEXT_SIZE_NORMAL) ' Font-Zustand der aktiven Seite wiederherstellen
|
||||
End If
|
||||
|
||||
itemData.Display = False
|
||||
End Sub
|
||||
|
||||
@@ -634,23 +776,47 @@ Public Class XRechnungViewDocument
|
||||
End Sub
|
||||
|
||||
Private Sub HandleTaxPosFollowUp(context As PdfRenderContext, itemData As InvoiceItemData)
|
||||
' TAXPOS Items werden zu einem String kombiniert
|
||||
If itemData.SpecName = "INVOICE_TAXPOS_RATE" Then
|
||||
If itemData.SpecName = "INVOICE_TAXPOS_BASEAMOUNT" Then
|
||||
' BASEAMOUNT kann auch als Follow-Up (zweite und weitere Steuersatz-Gruppen)
|
||||
' auftreten, nicht nur im allerersten Area-Switch. Eigene Zeile, sofort gerendert.
|
||||
RenderTaxposBaseAmountRow(context, itemData)
|
||||
itemData.Display = False
|
||||
ElseIf itemData.SpecName = "INVOICE_TAXPOS_RATE" Then
|
||||
context.PositionCount += 1
|
||||
context.TaxPosText = $"{itemData.Value} %: " ' Speichern statt direkt setzen
|
||||
context.TaxPosRate = itemData.Value
|
||||
context.TaxPosRateCaption = itemData.Caption
|
||||
itemData.Display = False
|
||||
_logger.Debug($"TAXPOS RATE accumulated: [{context.TaxPosText}]")
|
||||
_logger.Debug($"TAXPOS RATE accumulated (new group): [{context.TaxPosRate}]")
|
||||
ElseIf itemData.SpecName = "INVOICE_TAXPOS_AMOUNT" Then
|
||||
Dim amount As String = FormatCurrency(itemData.Value, context.CurrencySymbol)
|
||||
context.TaxPosText &= amount ' Anhängen
|
||||
context.TaxPosAmount = FormatCurrency(itemData.Value, context.CurrencySymbol)
|
||||
itemData.Display = False
|
||||
_logger.Debug($"TAXPOS AMOUNT accumulated: [{context.TaxPosText}]")
|
||||
_logger.Debug($"TAXPOS AMOUNT accumulated: [{context.TaxPosAmount}]")
|
||||
ElseIf itemData.SpecName = "INVOICE_TAXPOS_TYPE" Then
|
||||
context.TaxPosText &= $" {itemData.Value}" ' Anhängen
|
||||
itemData.Value = context.TaxPosText ' JETZT den kombinierten String setzen
|
||||
itemData.Display = True ' Und anzeigen!
|
||||
context.TaxPosText = "" ' Reset für nächste TAXPOS
|
||||
_logger.Debug($"TAXPOS TYPE final: [{itemData.Value}], Display=True")
|
||||
' Vollständige Gruppe (RATE + AMOUNT + TYPE) ist da → eine Zeile rendern:
|
||||
' "BT 119 <TAB> 7.00 % <TAB> 148,14 € <TAB> VAT"
|
||||
If Not String.IsNullOrEmpty(context.TaxPosRate) Then
|
||||
' Nur Zeilenvorschub wenn dies NICHT die allererste Zeile der Area ist
|
||||
' (z.B. wenn die Gruppe direkt mit RATE begann, ohne BASEAMOUNT davor).
|
||||
If Not context.IsFirstTaxPosDisplay Then
|
||||
context.YPosition += LINE_HEIGHT
|
||||
End If
|
||||
context.IsFirstTaxPosDisplay = False
|
||||
|
||||
context.PDF.DrawText(fontResName, COL_TAXPOS_LABEL, context.YPosition, context.TaxPosRateCaption)
|
||||
context.PDF.DrawText(fontResName, COL_TAXPOS_VALUE1, context.YPosition, $"{context.TaxPosRate} %")
|
||||
context.PDF.DrawText(fontResName, COL_TAXPOS_AMOUNT, context.YPosition, context.TaxPosAmount)
|
||||
context.PDF.DrawText(fontResName, COL_TAXPOS_TYPE, context.YPosition, itemData.Value)
|
||||
|
||||
_logger.Debug($"TAXPOS row rendered: [{context.TaxPosRateCaption}] [{context.TaxPosRate} %] [{context.TaxPosAmount}] [{itemData.Value}] at Y={context.YPosition}")
|
||||
|
||||
' Reset für nächste TAXPOS-Gruppe
|
||||
context.TaxPosRate = ""
|
||||
context.TaxPosRateCaption = ""
|
||||
context.TaxPosAmount = ""
|
||||
Else
|
||||
_logger.Debug($"TAXPOS TYPE DUPLICATE/incomplete ignored: [{itemData.Value}]")
|
||||
End If
|
||||
itemData.Display = False
|
||||
ElseIf itemData.Value.Contains("EXEMPTION") Then
|
||||
_logger.Debug($"We got an Exemption: {itemData.Value}")
|
||||
End If
|
||||
@@ -731,28 +897,58 @@ Public Class XRechnungViewDocument
|
||||
End If
|
||||
End Sub
|
||||
|
||||
Private Sub RenderMultiLineText(context As PdfRenderContext, text As String, xPos As Integer, maxLength As Integer)
|
||||
' ERST nach echten Zeilenumbrüchen aufteilen
|
||||
Private Sub RenderMultiLineText(context As PdfRenderContext, text As String, xPos As Integer, maxLength As Integer,
|
||||
Optional ByRef firstRenderedY As Integer = -1,
|
||||
Optional ByRef firstRenderedPage As Integer = -1)
|
||||
Dim partsNL As List(Of String) = StringFunctions.SplitTextByNewLine(text)
|
||||
Dim isFirstPart As Boolean = True
|
||||
|
||||
For Each linePart As String In partsNL
|
||||
' DANN jede Zeile bei maxLength umbrechen
|
||||
Dim parts As List(Of String) = StringFunctions.SplitText_Length(linePart, maxLength)
|
||||
|
||||
For Each part As String In parts
|
||||
' ← NEU: VOR jedem Rendering prüfen, ob neue Seite nötig!
|
||||
If context.YDynamic >= PAGE_HEIGHT_LIMIT Then
|
||||
' ✓ PROBLEM GEFUNDEN: requiredSpace=10 ist zu viel!
|
||||
' Bei YDynamic=230 prüft es: 230 + 10 = 240 >= 270 → TRUE → Seitenumbruch
|
||||
' ABER: Die Zeile selbst benötigt nur 5mm! 230 + 5 = 235 wäre OK!
|
||||
|
||||
Dim requiredSpace As Integer = 6 ' ← REDUZIERT: Nur 1mm Puffer (Zeile=5mm + 1mm)
|
||||
|
||||
If (context.YDynamic + requiredSpace) >= PAGE_HEIGHT_LIMIT Then
|
||||
' Neue Seite erstellen
|
||||
CreateNewPage(context)
|
||||
' YDynamic auf neue Startposition setzen (nach Header)
|
||||
context.YDynamic = context.YPosition
|
||||
_logger.Debug($"RenderMultiLineText: Page break! New YDynamic: {context.YDynamic}")
|
||||
|
||||
' WICHTIG: CurrentPositionStartY wird hier NICHT geändert.
|
||||
' Der korrekte Anker ist die Zeile von INVOICE_POSITION_ARTICLE,
|
||||
' gesetzt in HandleArticleTextFollowUp VOR dem RenderMultiLineText-Aufruf.
|
||||
' - CONTINUATION-Break: Artikel auf alter Seite → CurrentPositionStartY
|
||||
' bleibt auf der alten Seite (Tax/Amount landen korrekt neben Artikel).
|
||||
' - FIRST-part-Break im ARTICLE-Feld: HandleArticleTextFollowUp hat
|
||||
' CurrentPositionStartY = context.YDynamic gesetzt, BEVOR CreateNewPage
|
||||
' aufgerufen wurde → nach dem Seitenbruch zeigt YDynamic auf die neue
|
||||
' Seite und der Anker ist bereits korrekt (45).
|
||||
If isFirstPart Then
|
||||
_logger.Debug($"RenderMultiLineText: Page break at FIRST part! YDynamic={context.YDynamic}, CurrentPositionStartY unchanged={context.CurrentPositionStartY}")
|
||||
Else
|
||||
_logger.Debug($"RenderMultiLineText: Page break at continuation! YDynamic={context.YDynamic}, CurrentPositionStartY unchanged={context.CurrentPositionStartY}")
|
||||
End If
|
||||
End If
|
||||
|
||||
' *** BUGFIX (lange mehrzeilige ARTICLE-Texte über Seitenbruch hinweg) ***
|
||||
' Erfasst Y/Seite des ALLERERSTEN tatsächlich gezeichneten Textteils - das ist
|
||||
' immer die korrekte Ankerzeile, unabhängig davon, ob danach noch weitere Zeilen
|
||||
' folgen oder ein Seitenbruch mitten im Text auftritt. Wird nur befüllt, wenn der
|
||||
' Aufrufer (HandleArticleTextFollowUp) die Parameter explizit übergibt.
|
||||
If firstRenderedY = -1 Then
|
||||
firstRenderedY = context.YDynamic
|
||||
firstRenderedPage = context.PDF.GetPageCount()
|
||||
End If
|
||||
|
||||
_logger.Debug($"RenderMultiLineText: Rendering part: [{part}] at Y position: {context.YDynamic}")
|
||||
context.PDF.DrawText(fontResName, xPos, context.YDynamic, part)
|
||||
context.YDynamic += LINE_HEIGHT
|
||||
context.YPlus += LINE_HEIGHT
|
||||
isFirstPart = False
|
||||
Next
|
||||
Next
|
||||
End Sub
|
||||
@@ -821,9 +1017,15 @@ Public Class XRechnungViewDocument
|
||||
Public Property YPlus As Integer
|
||||
Public Property CreateTextBox As Boolean
|
||||
Public Property CreatedString As String
|
||||
Public Property TaxPosText As String
|
||||
Public Property TaxPosText As String ' Legacy, nicht mehr aktiv genutzt - bleibt für Kompatibilität
|
||||
Public Property TaxPosRate As String
|
||||
Public Property TaxPosRateCaption As String
|
||||
Public Property TaxPosAmount As String
|
||||
Public Property IsFirstTaxPosDisplay As Boolean
|
||||
Public Property CurrentPositionStartY As Integer
|
||||
' Seitennummer auf der CurrentPositionStartY gültig ist.
|
||||
' Wird beim Setzen von CurrentPositionStartY immer mitgesetzt.
|
||||
Public Property CurrentPositionPage As Integer
|
||||
|
||||
Public Sub New(pdf As GdPicturePDF, createdString As String)
|
||||
Me.PDF = pdf
|
||||
@@ -837,8 +1039,12 @@ Public Class XRechnungViewDocument
|
||||
YPlus = 0
|
||||
CreateTextBox = False
|
||||
TaxPosText = ""
|
||||
TaxPosRate = ""
|
||||
TaxPosRateCaption = ""
|
||||
TaxPosAmount = ""
|
||||
IsFirstTaxPosDisplay = False
|
||||
CurrentPositionStartY = 0
|
||||
CurrentPositionPage = 1
|
||||
End Sub
|
||||
End Class
|
||||
|
||||
|
||||
Reference in New Issue
Block a user