Messaging: OAuth Sending

Jobs: Sichtbeleg Anpassungen, Seitenwechsel, Freiräume und Taxpos
This commit is contained in:
Developer01
2026-06-30 10:29:00 +02:00
parent 7377a9176e
commit b6d3e61488
7 changed files with 2716 additions and 543 deletions

View File

@@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices
<Assembly: AssemblyCompany("Digital Data")>
<Assembly: AssemblyProduct("Modules.Jobs")>
<Assembly: AssemblyCopyright("Copyright © 2026")>
<Assembly: AssemblyTrademark("3.7.0")>
<Assembly: AssemblyTrademark("3.8.0")>
<Assembly: ComVisible(False)>
@@ -30,5 +30,5 @@ Imports System.Runtime.InteropServices
' Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern
' übernehmen, indem Sie "*" eingeben:
<Assembly: AssemblyVersion("3.7.0.0")>
<Assembly: AssemblyFileVersion("3.7.0.0")>
<Assembly: AssemblyVersion("3.8.0.0")>
<Assembly: AssemblyFileVersion("3.8.0.0")>

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -31,7 +31,10 @@ Namespace Mail
Public Function Connect(pServer As String, pPort As Integer, pUser As String, pPassword As String, pAuthType As String, pOptions As MailSession.MailSessionOptions) As MailSession.SessionInfo
Return MailSession.ConnectToServerWithBasicAuth(pServer, pPort, pUser, pPassword, pAuthType, pOptions)
End Function
Public Function ConnectToO365(pUser As String, pClientId As String, pTenantId As String, pClientSecret As String) As MailSession.SessionInfo
Dim oOptions = New MailSession.MailSessionOptions With {.EnableTls1_2 = True}
Return MailSession.ConnectToServerWithO365OAuth(pUser, pClientId, pTenantId, pClientSecret, oOptions)
End Function
Public Function Disconnect() As Boolean
Return MailSession.DisconnectFromServer()
End Function

View File

@@ -1,5 +1,4 @@
Imports System.IdentityModel.Tokens
Imports System.Net.Security
Imports System.Net.Security
Imports DigitalData.Modules.Base
Imports DigitalData.Modules.Logging
Imports Limilabs.Client
@@ -91,11 +90,11 @@ Namespace Mail
Public Function ConnectToServerWithO365OAuth(pUser As String, pClientId As String, pTenantId As String, pClientSecret As String, pOptions As MailSessionOptions) As SessionInfo
' Choose server/port based on the client type
Dim server As String = If(TypeOf Client Is Smtp, "smtp.office365.com", OAuth2.O365_SERVER)
Dim oServer As String = If(TypeOf Client Is Smtp, "smtp-mail.outlook.com", OAuth2.O365_SERVER_IMAP)
Dim port As Integer = If(TypeOf Client Is Imap, 993, If(TypeOf Client Is Smtp, 587, 993))
Dim oSession = New SessionInfo With {
.Server = server,
.Server = oServer,
.Port = port,
.ClientId = pClientId,
.ClientSecret = pClientSecret,

View File

@@ -10,7 +10,8 @@ Namespace Mail
Private ReadOnly _clientId As String
Private ReadOnly _clientSecret As String
Public Const O365_SERVER As String = "outlook.office365.com"
Public Const O365_SERVER_IMAP As String = "outlook.office365.com"
Public Const O365_SERVER_SMTP As String = "smtp-mail.outlook.com"
Public Const O365_SCOPE As String = "https://outlook.office365.com/.default"
Public Const O365_AUTHORITY_PREFIX As String = "https://login.microsoftonline.com/"

View File

@@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices
' übernehmen, indem Sie "*" eingeben:
' <Assembly: AssemblyVersion("1.0.*")>
<Assembly: AssemblyVersion("2.1.0.0")>
<Assembly: AssemblyFileVersion("2.1.0.0")>
<Assembly: AssemblyVersion("2.2.0.0")>
<Assembly: AssemblyFileVersion("2.2.0.0")>