No edit summary
Line 21: Line 21:
     }
     }
}
}
</syntaxhighlight>
 


'''Important:''' Your class must be named <code>DocumentScripter</code> and have a <code>Main</code> method with these exact parameters.
'''Important:''' Your class must be named <code>DocumentScripter</code> and have a <code>Main</code> method with these exact parameters.
Line 41: Line 41:
// Clone an existing document (useful when processing multiple records)
// Clone an existing document (useful when processing multiple records)
ZinWord clonedDoc = sourceDocument.Clone(true);
ZinWord clonedDoc = sourceDocument.Clone(true);
</syntaxhighlight>
 


=== Saving Documents ===
=== Saving Documents ===
Line 52: Line 52:
document.SaveDocument("MyFile.pdf");  // Saves as PDF
document.SaveDocument("MyFile.pdf");  // Saves as PDF
document.SaveDocument("MyFile.docx");  // Saves as Word document
document.SaveDocument("MyFile.docx");  // Saves as Word document
</syntaxhighlight>
 


=== Checking Document Content ===
=== Checking Document Content ===
Line 59: Line 59:
// Check if document has any content (text, tables, or images)
// Check if document has any content (text, tables, or images)
bool hasContent = zinBL.DocumentFunctions.DocumentHasContent(document);
bool hasContent = zinBL.DocumentFunctions.DocumentHasContent(document);
</syntaxhighlight>
 


=== Attaching Documents ===
=== Attaching Documents ===
Line 69: Line 69:
bulkDocument.AttachDocument(sourceDocument2);
bulkDocument.AttachDocument(sourceDocument2);
bulkDocument.SaveDocument("Bulk_Output.pdf");
bulkDocument.SaveDocument("Bulk_Output.pdf");
</syntaxhighlight>
 


== Working with Bookmarks ==
== Working with Bookmarks ==
Line 92: Line 92:
// Set image from Picture object
// Set image from Picture object
document.SetBookmarkImage("BookmarkName", pictureItem);
document.SetBookmarkImage("BookmarkName", pictureItem);
</syntaxhighlight>
 


=== Deleting Bookmarks ===
=== Deleting Bookmarks ===
Line 99: Line 99:
// Remove a bookmark and its content
// Remove a bookmark and its content
document.DeleteBookmark("BookmarkName");
document.DeleteBookmark("BookmarkName");
</syntaxhighlight>
 


=== Getting All Bookmarks ===
=== Getting All Bookmarks ===
Line 113: Line 113:
     // ... do something with the bookmark
     // ... do something with the bookmark
}
}
</syntaxhighlight>
 


== Standard Bookmarks ==
== Standard Bookmarks ==
Line 133: Line 133:
     stateAsCity        // Treat state as city (NZ DB) (default: false)
     stateAsCity        // Treat state as city (NZ DB) (default: false)
);
);
</syntaxhighlight>
 


'''Supported Standard Bookmarks:'''
'''Supported Standard Bookmarks:'''
Line 165: Line 165:
     addresseeFirstLineAddress, stateAsCity
     addresseeFirstLineAddress, stateAsCity
);
);
</syntaxhighlight>
 


=== ProcessInvoiceBookmarks ===
=== ProcessInvoiceBookmarks ===
Line 176: Line 176:
     minAddressLines, stateAsCity, "DELIVERY"
     minAddressLines, stateAsCity, "DELIVERY"
);
);
</syntaxhighlight>
 


'''Supported Bookmark:'''
'''Supported Bookmark:'''
Line 191: Line 191:
     addresseeFirstLineAddress, stateAsCity
     addresseeFirstLineAddress, stateAsCity
);
);
</syntaxhighlight>
 


== Working with Parameters ==
== Working with Parameters ==
Line 212: Line 212:
string extension = parameters.GetItemString("OutputExtension");
string extension = parameters.GetItemString("OutputExtension");
string filter = parameters.GetItemString("OutputExtensionFilter");
string filter = parameters.GetItemString("OutputExtensionFilter");
</syntaxhighlight>
 


=== Setting Return Parameters ===
=== Setting Return Parameters ===
Line 222: Line 222:
// Return error message if something fails
// Return error message if something fails
returnParameters.SetItemString("ReturnError", "No File Save Location Chosen!");
returnParameters.SetItemString("ReturnError", "No File Save Location Chosen!");
</syntaxhighlight>
 


== File Operations ==
== File Operations ==
Line 246: Line 246:
     return false;
     return false;
}
}
</syntaxhighlight>
 


=== Creating Directories ===
=== Creating Directories ===
Line 259: Line 259:


string saveFileName = Path.Combine(folderPath, "Statement.pdf");
string saveFileName = Path.Combine(folderPath, "Statement.pdf");
</syntaxhighlight>
 


== Working with DataTables ==
== Working with DataTables ==
Line 288: Line 288:
     // Process document for this party
     // Process document for this party
}
}
</syntaxhighlight>
 


== Progress Bar ==
== Progress Bar ==
Line 318: Line 318:
     ProgressBarManager.Finished();
     ProgressBarManager.Finished();
});
});
</syntaxhighlight>
 


== Financial Functions ==
== Financial Functions ==
Line 339: Line 339:
decimal threeMonths = details.ThreeMonthsOverdue;
decimal threeMonths = details.ThreeMonthsOverdue;
decimal total = details.TotalDue;
decimal total = details.TotalDue;
</syntaxhighlight>
 


=== Transaction Data ===
=== Transaction Data ===
Line 364: Line 364:
     decimal subTotal = tran.SubTotal;
     decimal subTotal = tran.SubTotal;
}
}
</syntaxhighlight>
 


=== Combining Transaction Lists ===
=== Combining Transaction Lists ===
Line 371: Line 371:
// Combine and sort multiple transaction lists by date
// Combine and sort multiple transaction lists by date
var combined = zinBL.DocumentFunctions.CombineTransactions(listOne, listTwo);
var combined = zinBL.DocumentFunctions.CombineTransactions(listOne, listTwo);
</syntaxhighlight>
 


=== Date Utilities ===
=== Date Utilities ===
Line 379: Line 379:
DateTime monthEnd = DocumentFunctions.GetEndOfMonth(someDate);
DateTime monthEnd = DocumentFunctions.GetEndOfMonth(someDate);
// Example: 15/03/2024 returns 31/03/2024
// Example: 15/03/2024 returns 31/03/2024
</syntaxhighlight>
 


== Building HTML Tables ==
== Building HTML Tables ==
Line 412: Line 412:
// Insert into document
// Insert into document
document.SetBookmarkHtml("TableBookmark", htmlTable.ToString());
document.SetBookmarkHtml("TableBookmark", htmlTable.ToString());
</syntaxhighlight>
 


'''Tips for HTML Tables:'''
'''Tips for HTML Tables:'''
Line 454: Line 454:
     }
     }
}
}
</syntaxhighlight>
 


== Best Practices ==
== Best Practices ==
Line 479: Line 479:


return true;  // Success
return true;  // Success
</syntaxhighlight>
 


=== Memory Management ===
=== Memory Management ===
Line 495: Line 495:
     clonedDoc = null;
     clonedDoc = null;
}
}
</syntaxhighlight>
 


=== Conditional Processing ===
=== Conditional Processing ===
Line 513: Line 513:
     bulkDocument.AttachDocument(processedDoc);
     bulkDocument.AttachDocument(processedDoc);
}
}
</syntaxhighlight>
 


== Common Patterns ==
== Common Patterns ==
Line 545: Line 545:
     bulkDoc.SaveDocument(saveLocation);
     bulkDoc.SaveDocument(saveLocation);
}
}
</syntaxhighlight>
 


=== Pattern: Processing with Progress ===
=== Pattern: Processing with Progress ===
Line 570: Line 570:
     ProgressBarManager.Finished();
     ProgressBarManager.Finished();
});
});
</syntaxhighlight>
 


=== Pattern: Empty Row Padding ===
=== Pattern: Empty Row Padding ===
Line 585: Line 585:
     htmlTable.AppendLine("</tr>");
     htmlTable.AppendLine("</tr>");
}
}
</syntaxhighlight>
 


== Debugging ==
== Debugging ==
Line 598: Line 598:
// Show debug information
// Show debug information
MessageBox.Show($"Debug: {variableName}");
MessageBox.Show($"Debug: {variableName}");
</syntaxhighlight>
 


== Complete Example ==
== Complete Example ==
Line 706: Line 706:
     }
     }
}
}
</syntaxhighlight>
 


== Troubleshooting ==
== Troubleshooting ==

Revision as of 03:39, 10 November 2025

Document Scripting

Document Scripting allows you to create custom documents in ZinformAccounts by writing C# code that processes templates and data. This guide covers the core functions and patterns you'll use.

Getting Started

Every document script follows this basic structure:

<syntaxhighlight lang="csharp"> public class DocumentScripter {

   public static BusinessLayerMain zinBL;
   public static Document document;
   
   public bool Main(BusinessLayerMain bl, object documentTemplate, object? otherPartyDataTable, 
                    ZinParameters parameters, ZinParameters returnParameters, User? user)
   {
       zinBL = bl;
       // Your code here
       return true; // or false if error
   }

}


Important: Your class must be named DocumentScripter and have a Main method with these exact parameters.

Working with ZinWord Documents

Creating and Loading Documents

<syntaxhighlight lang="csharp"> // Create a new document ZinWord document = new ZinWord(zinBL);

// Load from file document.LoadDocument(documentFileName);

// Load from stream document.LoadStream(memoryStream);

// Clone an existing document (useful when processing multiple records) ZinWord clonedDoc = sourceDocument.Clone(true);


Saving Documents

<syntaxhighlight lang="csharp"> // Save to file - format determined by extension document.SaveDocument(saveFileName);

// Examples: document.SaveDocument("MyFile.pdf"); // Saves as PDF document.SaveDocument("MyFile.docx"); // Saves as Word document


Checking Document Content

<syntaxhighlight lang="csharp"> // Check if document has any content (text, tables, or images) bool hasContent = zinBL.DocumentFunctions.DocumentHasContent(document);


Attaching Documents

<syntaxhighlight lang="csharp"> // Combine multiple documents into one (useful for bulk processing) ZinWord bulkDocument = new ZinWord(zinBL); bulkDocument.AttachDocument(sourceDocument1); bulkDocument.AttachDocument(sourceDocument2); bulkDocument.SaveDocument("Bulk_Output.pdf");


Working with Bookmarks

Bookmarks are placeholders in your Word templates that you replace with actual data.

Setting Bookmark Values

<syntaxhighlight lang="csharp"> // Set text value document.SetBookmarkString("BookmarkName", "Value");

// Set HTML content (for formatted text, tables, etc.) document.SetBookmarkHtml("BookmarkName", htmlString);

// Set hyperlink document.SetBookmarkHyperLink("BookmarkName", hyperLinkItem);

// Set image from file document.SetBookmarkImageFromFile("BookmarkName", pictureFileName);

// Set image from Picture object document.SetBookmarkImage("BookmarkName", pictureItem);


Deleting Bookmarks

<syntaxhighlight lang="csharp"> // Remove a bookmark and its content document.DeleteBookmark("BookmarkName");


Getting All Bookmarks

<syntaxhighlight lang="csharp"> // Retrieve all bookmarks in the document var bookmarks = document.GetBookmarks();

foreach (var bookmark in bookmarks) {

   // Process each bookmark
   string name = bookmark.Name;
   // ... do something with the bookmark

}


Standard Bookmarks

ZinformAccounts provides automatic processing for common bookmarks:

ProcessStandardBookmarks

<syntaxhighlight lang="csharp"> zinBL.DocumentFunctions.ProcessStandardBookmarks(

   document,           // ZinWord document to process
   otherParty,         // ZOtherParty object (customer/supplier)
   parameters,         // ZinParameters with document settings
   zinBL,             // Business layer instance
   logoWidth,         // Logo width in pixels (e.g., 193)
   logoHeight,        // Logo height in pixels (e.g., 70)
   minAddressLines,   // Minimum address lines (default: 4)
   addresseeFirstLineAddress,  // Use first line as addressee (default: false)
   stateAsCity        // Treat state as city (NZ DB) (default: false)

);


Supported Standard Bookmarks:

  • OrganisationLogo - Your company logo
  • OrganisationName - Your company name
  • OrganisationAddress - Your postal address
  • OrganisationPhone - Your phone number
  • OrganisationEmail - Your email address
  • OrganisationWebsite - Your website URL
  • OrganisationBankAccount - Bank account name and number
  • OrganisationBankAccountNumber - Bank account number only
  • OrganisationBankAccountName - Bank account name only
  • DocumentDate - Current date (formatted as "dd MMMM yyyy")
  • DocumentId - Document ID from parameters
  • GSTNumber - Tax registration number
  • OtherPartyId - Customer/Supplier ID
  • OtherPartyName - Customer/Supplier name
  • OtherPartyAddress - Customer/Supplier postal address
  • OtherPartySalutation - Greeting (from Administrator/Manager contact)
  • ReferenceId - Reference ID
  • OrganisationNameFooter - Company name for footer

ProcessExtraInvoiceBookmarks

For invoice-specific bookmarks (duplicated bookmarks with "2" suffix):

<syntaxhighlight lang="csharp"> zinBL.DocumentFunctions.ProcessExtraInvoiceBookmarks(

   document, otherParty, parameters, zinBL, 
   logoWidth, logoHeight, minAddressLines, 
   addresseeFirstLineAddress, stateAsCity

);


ProcessInvoiceBookmarks

Handles delivery address for invoices:

<syntaxhighlight lang="csharp"> zinBL.DocumentFunctions.ProcessInvoiceBookmarks(

   document, otherParty, parameters, zinBL, transaction,
   minAddressLines, stateAsCity, "DELIVERY"

);


Supported Bookmark:

  • DeliverToDetails - Delivery address (deleted if no delivery address exists)

ProcessExtraStatementBookmarks

For statement-specific bookmarks (duplicated bookmarks with "2" suffix):

<syntaxhighlight lang="csharp"> zinBL.DocumentFunctions.ProcessExtraStatementBookmarks(

   document, otherParty, parameters, zinBL, 
   logoWidth, logoHeight, minAddressLines, 
   addresseeFirstLineAddress, stateAsCity

);


Working with Parameters

Parameters pass data between the UI and your script.

Getting Parameter Values

<syntaxhighlight lang="csharp"> // Get string value string documentId = parameters.GetItemString("DocumentId");

// Get datetime value DateTime asAtDate = (DateTime)parameters.GetItemDateTime("DateAsAt");

// Get boolean value bool individual = parameters.GetItemBoolean("IndividualDocuments");

// Get output extension string extension = parameters.GetItemString("OutputExtension"); string filter = parameters.GetItemString("OutputExtensionFilter");


Setting Return Parameters

<syntaxhighlight lang="csharp"> // Return the save location to the caller returnParameters.SetItemString("SaveLocation", saveLocation);

// Return error message if something fails returnParameters.SetItemString("ReturnError", "No File Save Location Chosen!");


File Operations

Save File Dialog

<syntaxhighlight lang="csharp"> string saveLocation = FileFunctions.OpenSaveFileDialog(

   document.DocumentOutputFolder,           // Default folder
   String.Format("Statement_{0}", DateTime.Now.ToString("yyyyMMdd_hhmmss")), // Default filename
   parameters.GetItemString("OutputExtension"),       // Default extension (e.g., "pdf")
   parameters.GetItemString("OutputExtensionFilter")  // File type filter

);

if (saveLocation != null && saveLocation.Length > 0) {

   // User selected a location

} else {

   // User cancelled - handle error
   returnParameters.SetItemString("ReturnError", "No File Save Location Chosen!");
   return false;

}


Creating Directories

<syntaxhighlight lang="csharp"> string folderPath = Path.Combine(baseFolderPath, $"OtherParty_{otherPartyId}");

if (!Directory.Exists(folderPath)) {

   Directory.CreateDirectory(folderPath);

}

string saveFileName = Path.Combine(folderPath, "Statement.pdf");


Working with DataTables

When processing multiple records (e.g., statements for multiple customers):

<syntaxhighlight lang="csharp"> // Validate the DataTable parameter DataTable otherPartyDataTable; if (otherPartyDataTable != null && otherPartyDataTable is DataTable) {

   otherPartyDataTable = (DataTable)otherPartyDataTable;

} else {

   throw new InvalidOperationException("otherPartyDataTable is not a valid DataTable");

}

// Loop through rows foreach (DataRow row in otherPartyDataTable.Rows) {

   // Get values from the row
   string otherPartyId = (string)row["OtherPartyId"];
   
   // Load the full OtherParty object
   var otherParty = zinBL.OtherPartyFunctions.GetOtherPartyById(otherPartyId);
   
   // Process document for this party

}


Progress Bar

Show progress when processing multiple records:

<syntaxhighlight lang="csharp"> ProgressBarManager.Start("Processing Statements", true, totalCount, progressBar => {

   int currentCount = 1;
   
   foreach (DataRow row in dataTable.Rows)
   {
       // Check for cancellation
       if (progressBar.CancellationPending || ProgressBarManager.IsCancelled())
       {
           break;
       }
       
       // Update progress
       progressBar.ReportProgress(currentCount, $"Processing: {customerName}");
       
       // Do work here...
       
       currentCount++;
   }
   
   // Mark as finished
   ProgressBarManager.Finished();

});


Financial Functions

Statement Data

<syntaxhighlight lang="csharp"> // Get statement details (aged balances) ZinStatementDetails details = zinBL.DocumentFunctions.GetStatementDetails(

   zinBL, 
   otherParty, 
   statementDate, 
   DocumentTypes.ARStatement  // or DocumentTypes.APRemittance

);

// Access aged balances decimal current = details.CurrentBalance; decimal oneMonth = details.OneMonthOverdue; decimal twoMonths = details.TwoMonthsOverdue; decimal threeMonths = details.ThreeMonthsOverdue; decimal total = details.TotalDue;


Transaction Data

<syntaxhighlight lang="csharp"> // Get monthly running transactions (preferred method) var (transactions, statementDetails) = zinBL.DocumentFunctions.GetMonthlyRunningTransactions2(

   statementDate, 
   otherPartyId, 
   zinBL

);

// transactions is List<StatementTransaction2> // statementDetails is ZinStatementDetails with aged balances

// Process transactions foreach (var tran in transactions) {

   DateTime date = tran.Date;
   int type = tran.Type;
   string reference = tran.Reference;
   decimal value = tran.Value;
   decimal runningBalance = tran.RunningBalance;
   decimal subTotal = tran.SubTotal;

}


Combining Transaction Lists

<syntaxhighlight lang="csharp"> // Combine and sort multiple transaction lists by date var combined = zinBL.DocumentFunctions.CombineTransactions(listOne, listTwo);


Date Utilities

<syntaxhighlight lang="csharp"> // Get last day of month DateTime monthEnd = DocumentFunctions.GetEndOfMonth(someDate); // Example: 15/03/2024 returns 31/03/2024


Building HTML Tables

For inserting formatted tables into bookmarks:

<syntaxhighlight lang="csharp"> var htmlTable = new StringBuilder();

htmlTable.AppendLine("

"); htmlTable.AppendLine("<thead>"); htmlTable.AppendLine(""); htmlTable.AppendLine(""); htmlTable.AppendLine(""); htmlTable.AppendLine(""); htmlTable.AppendLine(""); htmlTable.AppendLine("</thead>"); htmlTable.AppendLine("<tbody>"); foreach (var item in dataList) { htmlTable.AppendLine(""); htmlTable.AppendLine($""); htmlTable.AppendLine($""); htmlTable.AppendLine($""); htmlTable.AppendLine(""); } htmlTable.AppendLine("</tbody>"); htmlTable.AppendLine("
DateReferenceAmount
{item.Date:dd/MM/yyyy}{item.Reference}{item.Amount:$#,##0.00}

");

// Insert into document document.SetBookmarkHtml("TableBookmark", htmlTable.ToString());


Tips for HTML Tables:

  • Use inline styles (external CSS won't work)
  • Specify font-family: "Aptos", sans-serif; for consistency
  • Use text-align: right for numbers
  • Add padding for readability: padding: 5px;
  • Format currency: {value:$#,##0.00}

Email Integration

Sending Documents via Email

<syntaxhighlight lang="csharp"> // Get email addresses for specific contacts var emailAddresses = otherParty.GetEmailContactsAsEmailCompatibleStringById(document.EmailContactIds);

if (emailAddresses.Length > 0) {

   // Replace placeholders in email body
   string emailBody = document.EmailBody.Replace("[OtherPartyName]", otherParty.Name);
   
   // Send email with attachment
   var result = zinBL.Emailing.SendEmailWithImage(
       emailAddresses,              // To addresses
       "",                          // CC addresses
       "",                          // BCC addresses
       document.EmailSubject,       // Subject
       emailBody,                   // HTML body
       zinBL,                       // Business layer
       attachmentFilePath,          // Path to attachment
       false,                       // Is HTML
       70L,                         // Image quality
       444,                         // Max image width
       "unsubscribe@yourdomain.com" // Unsubscribe email
   );
   
   if (!result.success)
   {
       MessageBox.Show(result.errorMessage);
   }

}


Best Practices

Error Handling

<syntaxhighlight lang="csharp"> try {

   // Your code here
   
   if (somethingWentWrong)
   {
       returnParameters.SetItemString("ReturnError", "Clear error message here");
       return false;
   }

} catch (Exception ex) {

   MessageBox.Show($"An error occurred: {ex.Message}\n{ex.StackTrace}", "Error");
   returnParameters.SetItemString("ReturnError", ex.Message);
   return false;

}

return true; // Success


Memory Management

When processing many documents, clean up:

<syntaxhighlight lang="csharp"> foreach (DataRow row in dataTable.Rows) {

   var clonedDoc = sourceDoc.Clone(true);
   
   // Process document...
   
   // If not saving this document, explicitly dispose
   clonedDoc = null;

}


Conditional Processing

<syntaxhighlight lang="csharp"> // Only add to bulk document if not emailed bool wasEmailed = false;

if (document.EmailIfPossible && emailAddresses.Length > 0) {

   // Send email
   wasEmailed = true;

}

if (!wasEmailed) {

   bulkDocument.AttachDocument(processedDoc);

}


Common Patterns

Pattern: Individual vs Bulk Documents

<syntaxhighlight lang="csharp"> bool individual = parameters.GetItemBoolean("IndividualDocuments"); ZinWord bulkDoc = new ZinWord(zinBL);

foreach (var otherParty in otherParties) {

   var doc = ProcessDocument(otherParty);
   
   if (individual)
   {
       // Save individual file
       string fileName = $"{folderPath}/Document_{otherParty.OtherPartyId}.pdf";
       doc.SaveDocument(fileName);
   }
   else
   {
       // Add to bulk document
       bulkDoc.AttachDocument(doc);
   }

}

// Save bulk document if not individual if (!individual && zinBL.DocumentFunctions.DocumentHasContent(bulkDoc)) {

   bulkDoc.SaveDocument(saveLocation);

}


Pattern: Processing with Progress

<syntaxhighlight lang="csharp"> ProgressBarManager.Start("Processing Documents", true, dataTable.Rows.Count, progressBar => {

   int currentCount = 1;
   
   foreach (DataRow row in dataTable.Rows)
   {
       if (progressBar.CancellationPending || ProgressBarManager.IsCancelled())
       {
           break;
       }
       
       progressBar.ReportProgress(currentCount, $"Processing: {currentItem}");
       
       // Do work...
       
       currentCount++;
   }
   
   ProgressBarManager.Finished();

});


Pattern: Empty Row Padding

<syntaxhighlight lang="csharp"> int minLines = 13; int currentLines = dataList.Count; int emptyLines = Math.Max(0, minLines - currentLines);

for (int i = 0; i < emptyLines; i++) {

htmlTable.AppendLine(""); htmlTable.AppendLine(""); htmlTable.AppendLine(""); }

Debugging

<syntaxhighlight lang="csharp"> // Add this using statement at top of your script: using ZinformAccounts.Debugger;

// Launch debugger at any point DebuggerExtensions.LaunchNow("Starting Main method");

// Show debug information MessageBox.Show($"Debug: {variableName}");


Complete Example

Here's a simplified but complete statement script:

<syntaxhighlight lang="csharp"> using System; using System.Text; using System.Windows; using System.Data; using ZinBusinessLayer; using ZinBusinessLayer.HelperClasses; using ZinBusinessLayer.Enums; using ZinBusinessLayer.Word;

public class DocumentScripter {

   public static BusinessLayerMain zinBL;
   public static Document document;
   public bool Main(BusinessLayerMain bl, object documentTemplate, object? otherPartyDataTable, 
                    ZinParameters parameters, ZinParameters returnParameters, User? user)
   {
       zinBL = bl;
       
       if (!(documentTemplate is ZinWord sourceDoc))
       {
           returnParameters.SetItemString("ReturnError", "Invalid document template");
           return false;
       }
       
       document = zinBL.DocumentFunctions.GetDocument(parameters.GetItemString("DocumentId"));
       DateTime statementDate = (DateTime)parameters.GetItemDateTime("DateAsAt");
       
       string saveLocation = FileFunctions.OpenSaveFileDialog(
           document.DocumentOutputFolder,
           $"Statement_{DateTime.Now:yyyyMMdd_hhmmss}",
           parameters.GetItemString("OutputExtension"),
           parameters.GetItemString("OutputExtensionFilter")
       );
       
       if (string.IsNullOrEmpty(saveLocation))
       {
           returnParameters.SetItemString("ReturnError", "No save location chosen");
           return false;
       }
       
       DataTable dataTable = (DataTable)otherPartyDataTable;
       ZinWord bulkDoc = new ZinWord(zinBL);
       
       ProgressBarManager.Start("Processing Statements", true, dataTable.Rows.Count, progressBar =>
       {
           int count = 1;
           
           foreach (DataRow row in dataTable.Rows)
           {
               if (progressBar.CancellationPending) break;
               
               string otherPartyId = (string)row["OtherPartyId"];
               var otherParty = zinBL.OtherPartyFunctions.GetOtherPartyById(otherPartyId);
               
               progressBar.ReportProgress(count, $"Processing: {otherParty.Name}");
               
               var doc = sourceDoc.Clone(true);
               
               // Process bookmarks
               zinBL.DocumentFunctions.ProcessStandardBookmarks(
                   doc, otherParty, parameters, zinBL, 193, 70, 5
               );
               
               // Get statement data
               var (transactions, details) = zinBL.DocumentFunctions.GetMonthlyRunningTransactions2(
                   statementDate, otherPartyId, zinBL
               );
               
               // Build transaction table
               var html = new StringBuilder();

html.AppendLine("

"); foreach (var tran in transactions) { html.AppendLine(""); html.AppendLine($""); html.AppendLine($""); html.AppendLine($""); html.AppendLine(""); } html.AppendLine("
{tran.Date:dd/MM/yyyy}{tran.Reference}{tran.Value:$#,##0.00}

");

               doc.SetBookmarkHtml("Transactions", html.ToString());
               doc.SetBookmarkString("TotalDue", details.TotalDue.ToString("#,##0.00"));
               
               bulkDoc.AttachDocument(doc);
               count++;
           }
           
           ProgressBarManager.Finished();
       });
       
       if (zinBL.DocumentFunctions.DocumentHasContent(bulkDoc))
       {
           bulkDoc.SaveDocument(saveLocation);
       }
       
       returnParameters.SetItemString("SaveLocation", saveLocation);
       return true;
   }

}


Troubleshooting

Problem Solution
Bookmark not found error Check spelling - bookmarks are case-sensitive
HTML not rendering properly Use inline styles only, no external CSS
Progress bar not showing Ensure you call ProgressBarManager.Finished()
Document won't save Check file path and ensure extension matches format
Changes not appearing Make sure you're modifying the correct document instance (not the template)