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
}
} </syntaxhighlight>
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); </syntaxhighlight>
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 </syntaxhighlight>
Checking Document Content
<syntaxhighlight lang="csharp"> // Check if document has any content (text, tables, or images) bool hasContent = zinBL.DocumentFunctions.DocumentHasContent(document); </syntaxhighlight>
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"); </syntaxhighlight>
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); </syntaxhighlight>
Deleting Bookmarks
<syntaxhighlight lang="csharp"> // Remove a bookmark and its content document.DeleteBookmark("BookmarkName"); </syntaxhighlight>
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
} </syntaxhighlight>
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)
); </syntaxhighlight>
Supported Standard Bookmarks:
OrganisationLogo- Your company logoOrganisationName- Your company nameOrganisationAddress- Your postal addressOrganisationPhone- Your phone numberOrganisationEmail- Your email addressOrganisationWebsite- Your website URLOrganisationBankAccount- Bank account name and numberOrganisationBankAccountNumber- Bank account number onlyOrganisationBankAccountName- Bank account name onlyDocumentDate- Current date (formatted as "dd MMMM yyyy")DocumentId- Document ID from parametersGSTNumber- Tax registration numberOtherPartyId- Customer/Supplier IDOtherPartyName- Customer/Supplier nameOtherPartyAddress- Customer/Supplier postal addressOtherPartySalutation- Greeting (from Administrator/Manager contact)ReferenceId- Reference IDOrganisationNameFooter- 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
); </syntaxhighlight>
ProcessInvoiceBookmarks
Handles delivery address for invoices:
<syntaxhighlight lang="csharp"> zinBL.DocumentFunctions.ProcessInvoiceBookmarks(
document, otherParty, parameters, zinBL, transaction, minAddressLines, stateAsCity, "DELIVERY"
); </syntaxhighlight>
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
); </syntaxhighlight>
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"); </syntaxhighlight>
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!"); </syntaxhighlight>
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;
} </syntaxhighlight>
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"); </syntaxhighlight>
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
} </syntaxhighlight>
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();
}); </syntaxhighlight>
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; </syntaxhighlight>
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;
} </syntaxhighlight>
Combining Transaction Lists
<syntaxhighlight lang="csharp"> // Combine and sort multiple transaction lists by date var combined = zinBL.DocumentFunctions.CombineTransactions(listOne, listTwo); </syntaxhighlight>
Date Utilities
<syntaxhighlight lang="csharp"> // Get last day of month DateTime monthEnd = DocumentFunctions.GetEndOfMonth(someDate); // Example: 15/03/2024 returns 31/03/2024 </syntaxhighlight>
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("| Date | Reference | Amount |
| {item.Date:dd/MM/yyyy} | {item.Reference} | {item.Amount:$#,##0.00} |
");
// Insert into document document.SetBookmarkHtml("TableBookmark", htmlTable.ToString()); </syntaxhighlight>
Tips for HTML Tables:
- Use inline styles (external CSS won't work)
- Specify
font-family: "Aptos", sans-serif;for consistency - Use
text-align: rightfor 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);
}
} </syntaxhighlight>
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 </syntaxhighlight>
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;
} </syntaxhighlight>
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);
} </syntaxhighlight>
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);
} </syntaxhighlight>
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();
}); </syntaxhighlight>
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(""); } </syntaxhighlight>
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}"); </syntaxhighlight>
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;
}
} </syntaxhighlight>
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) |
| Out of memory with many documents | Process in batches, dispose of documents after use |