Document Scripting: Difference between revisions
No edit summary Tag: Reverted |
No edit summary Tag: Manual revert |
||
| Line 8: | Line 8: | ||
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 | |||
} | |||
} | |||
| Line 31: | Line 31: | ||
// Create a new document | // Create a new document | ||
ZinWord document = new ZinWord(zinBL); | |||
// Load from file | // Load from file | ||
document.LoadDocument(documentFileName); | |||
// Load from stream | // Load from stream | ||
document.LoadStream(memoryStream); | |||
// Clone an existing document (useful when processing multiple records) | // Clone an existing document (useful when processing multiple records) | ||
| Line 47: | Line 47: | ||
// Save to file - format determined by extension | // Save to file - format determined by extension | ||
document.SaveDocument(saveFileName); | |||
// Examples: | // Examples: | ||
document.SaveDocument("MyFile.pdf"); // Saves as PDF | |||
document.SaveDocument("MyFile.docx"); // Saves as Word document | |||
| Line 58: | Line 58: | ||
// 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); | |||
| Line 65: | Line 65: | ||
// Combine multiple documents into one (useful for bulk processing) | // 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"); | |||
| Line 79: | Line 79: | ||
// Set text value | // Set text value | ||
document.SetBookmarkString("BookmarkName", "Value"); | |||
// Set HTML content (for formatted text, tables, etc.) | // Set HTML content (for formatted text, tables, etc.) | ||
document.SetBookmarkHtml("BookmarkName", htmlString); | |||
// Set hyperlink | // Set hyperlink | ||
document.SetBookmarkHyperLink("BookmarkName", hyperLinkItem); | |||
// Set image from file | // Set image from file | ||
document.SetBookmarkImageFromFile("BookmarkName", pictureFileName); | |||
// Set image from Picture object | // Set image from Picture object | ||
document.SetBookmarkImage("BookmarkName", pictureItem); | |||
| Line 98: | Line 98: | ||
// Remove a bookmark and its content | // Remove a bookmark and its content | ||
document.DeleteBookmark("BookmarkName"); | |||
| Line 105: | Line 105: | ||
// Retrieve all bookmarks in the document | // 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 == | == Standard Bookmarks == | ||
ZinformAccounts provides automatic processing for common bookmarks: | |||
=== ProcessStandardBookmarks === | === ProcessStandardBookmarks === | ||
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) | |||
); | |||
| Line 160: | Line 160: | ||
zinBL.DocumentFunctions.ProcessExtraInvoiceBookmarks( | |||
document, otherParty, parameters, zinBL, | |||
logoWidth, logoHeight, minAddressLines, | |||
addresseeFirstLineAddress, stateAsCity | |||
); | |||
| Line 172: | Line 172: | ||
zinBL.DocumentFunctions.ProcessInvoiceBookmarks( | |||
document, otherParty, parameters, zinBL, transaction, | |||
minAddressLines, stateAsCity, "DELIVERY" | |||
); | |||
| Line 186: | Line 186: | ||
zinBL.DocumentFunctions.ProcessExtraStatementBookmarks( | |||
document, otherParty, parameters, zinBL, | |||
logoWidth, logoHeight, minAddressLines, | |||
addresseeFirstLineAddress, stateAsCity | |||
); | |||
| Line 201: | Line 201: | ||
// Get string value | // Get string value | ||
string documentId = parameters.GetItemString("DocumentId"); | |||
// Get datetime value | // Get datetime value | ||
DateTime asAtDate = (DateTime)parameters.GetItemDateTime("DateAsAt"); | |||
// Get boolean value | // Get boolean value | ||
bool individual = parameters.GetItemBoolean("IndividualDocuments"); | |||
// Get output extension | // Get output extension | ||
string extension = parameters.GetItemString("OutputExtension"); | |||
string filter = parameters.GetItemString("OutputExtensionFilter"); | |||
| Line 218: | Line 218: | ||
// Return the save location to the caller | // Return the save location to the caller | ||
returnParameters.SetItemString("SaveLocation", saveLocation); | |||
// Return error message if something fails | // Return error message if something fails | ||
returnParameters.SetItemString("ReturnError", "No File Save Location Chosen!"); | |||
| Line 229: | Line 229: | ||
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; | |||
} | |||
| Line 251: | Line 251: | ||
string folderPath = Path.Combine(baseFolderPath, $"OtherParty_{otherPartyId}"); | |||
if (!Directory.Exists(folderPath)) | |||
{ | |||
Directory.CreateDirectory(folderPath); | |||
} | |||
string saveFileName = Path.Combine(folderPath, "Statement.pdf"); | |||
| Line 267: | Line 267: | ||
// Validate the DataTable parameter | // 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 | // 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 | |||
} | |||
| Line 295: | Line 295: | ||
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(); | |||
}); | |||
| Line 326: | Line 326: | ||
// Get statement details (aged balances) | // Get statement details (aged balances) | ||
ZinStatementDetails details = zinBL.DocumentFunctions.GetStatementDetails( | |||
zinBL, | |||
otherParty, | |||
statementDate, | |||
DocumentTypes.ARStatement // or DocumentTypes.APRemittance | |||
); | |||
// Access aged balances | // Access aged balances | ||
decimal current = details.CurrentBalance; | |||
decimal oneMonth = details.OneMonthOverdue; | |||
decimal twoMonths = details.TwoMonthsOverdue; | |||
decimal threeMonths = details.ThreeMonthsOverdue; | |||
decimal total = details.TotalDue; | |||
| Line 345: | Line 345: | ||
// Get monthly running transactions (preferred method) | // Get monthly running transactions (preferred method) | ||
var (transactions, statementDetails) = zinBL.DocumentFunctions.GetMonthlyRunningTransactions2( | |||
statementDate, | |||
otherPartyId, | |||
zinBL | |||
); | |||
// transactions is List<StatementTransaction2> | // transactions is List<StatementTransaction2> | ||
| Line 355: | Line 355: | ||
// Process transactions | // 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; | |||
} | |||
| Line 370: | Line 370: | ||
// Combine and sort multiple transaction lists by date | // Combine and sort multiple transaction lists by date | ||
var combined = zinBL.DocumentFunctions.CombineTransactions(listOne, listTwo); | |||
| Line 377: | Line 377: | ||
// Get last day of month | // Get last day of month | ||
DateTime monthEnd = DocumentFunctions.GetEndOfMonth(someDate); | |||
// Example: 15/03/2024 returns 31/03/2024 | // Example: 15/03/2024 returns 31/03/2024 | ||
| Line 386: | Line 386: | ||
var htmlTable = new StringBuilder(); | |||
htmlTable.AppendLine("<table style='width: 100%; border-collapse: collapse;'>"); | |||
htmlTable.AppendLine("<thead>"); | |||
htmlTable.AppendLine("<tr>"); | |||
htmlTable.AppendLine("<td style='padding: 5px; font-family: \"Aptos\", sans-serif;'><b>Date</b></td>"); | |||
htmlTable.AppendLine("<td style='padding: 5px; font-family: \"Aptos\", sans-serif;'><b>Reference</b></td>"); | |||
htmlTable.AppendLine("<td style='padding: 5px; text-align: right; font-family: \"Aptos\", sans-serif;'><b>Amount</b></td>"); | |||
htmlTable.AppendLine("</tr>"); | |||
htmlTable.AppendLine("</thead>"); | |||
htmlTable.AppendLine("<tbody>"); | |||
foreach (var item in dataList) | |||
{ | |||
htmlTable.AppendLine("<tr>"); | |||
htmlTable.AppendLine($"<td style='padding: 2px; font-family: \"Aptos\", sans-serif;'>{item.Date:dd/MM/yyyy}</td>"); | |||
htmlTable.AppendLine($"<td style='padding: 2px; font-family: \"Aptos\", sans-serif;'>{item.Reference}</td>"); | |||
htmlTable.AppendLine($"<td style='padding: 2px; text-align: right; font-family: \"Aptos\", sans-serif;'>{item.Amount:$#,##0.00}</td>"); | |||
htmlTable.AppendLine("</tr>"); | |||
} | |||
htmlTable.AppendLine("</tbody>"); | |||
htmlTable.AppendLine("</table>"); | |||
// Insert into document | // Insert into document | ||
document.SetBookmarkHtml("TableBookmark", htmlTable.ToString()); | |||
| Line 427: | Line 427: | ||
// Get email addresses for specific contacts | // 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); | |||
} | |||
} | |||
| Line 461: | Line 461: | ||
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 | |||
| Line 486: | Line 486: | ||
foreach (DataRow row in dataTable.Rows) | |||
{ | |||
var clonedDoc = sourceDoc.Clone(true); | |||
// Process document... | |||
// If not saving this document, explicitly dispose | |||
clonedDoc = null; | |||
} | |||
| Line 501: | Line 501: | ||
// Only add to bulk document if not emailed | // 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); | |||
} | |||
| Line 520: | Line 520: | ||
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 | // Save bulk document if not individual | ||
if (!individual && zinBL.DocumentFunctions.DocumentHasContent(bulkDoc)) | |||
{ | |||
bulkDoc.SaveDocument(saveLocation); | |||
} | |||
| Line 550: | Line 550: | ||
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(); | |||
}); | |||
| Line 575: | Line 575: | ||
int minLines = 13; | |||
int currentLines = dataList.Count; | |||
int emptyLines = Math.Max(0, minLines - currentLines); | |||
for (int i = 0; i < emptyLines; i++) | |||
{ | |||
htmlTable.AppendLine("<tr style='height: 12px;'>"); | |||
htmlTable.AppendLine("<td></td><td></td><td></td>"); | |||
htmlTable.AppendLine("</tr>"); | |||
} | |||
| Line 591: | Line 591: | ||
// Add this using statement at top of your script: | // Add this using statement at top of your script: | ||
using ZinformAccounts.Debugger; | |||
// Launch debugger at any point | // Launch debugger at any point | ||
DebuggerExtensions.LaunchNow("Starting Main method"); | |||
// Show debug information | // Show debug information | ||
MessageBox.Show($"Debug: {variableName}"); | |||
| Line 605: | Line 605: | ||
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("<table style='width: 100%;'>"); | |||
foreach (var tran in transactions) | |||
{ | |||
html.AppendLine("<tr>"); | |||
html.AppendLine($"<td>{tran.Date:dd/MM/yyyy}</td>"); | |||
html.AppendLine($"<td>{tran.Reference}</td>"); | |||
html.AppendLine($"<td style='text-align: right;'>{tran.Value:$#,##0.00}</td>"); | |||
html.AppendLine("</tr>"); | |||
} | |||
html.AppendLine("</table>"); | |||
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; | |||
} | |||
} | |||
Latest revision as of 03:49, 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:
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
// 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
// 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
// Check if document has any content (text, tables, or images) bool hasContent = zinBL.DocumentFunctions.DocumentHasContent(document);
Attaching Documents
// 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
// 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
// Remove a bookmark and its content document.DeleteBookmark("BookmarkName");
Getting All Bookmarks
// 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
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 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):
zinBL.DocumentFunctions.ProcessExtraInvoiceBookmarks(
document, otherParty, parameters, zinBL, logoWidth, logoHeight, minAddressLines, addresseeFirstLineAddress, stateAsCity
);
ProcessInvoiceBookmarks
Handles delivery address for invoices:
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):
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
// 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
// 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
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
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):
// 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:
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
// 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
// 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
// Combine and sort multiple transaction lists by date var combined = zinBL.DocumentFunctions.CombineTransactions(listOne, listTwo);
Date Utilities
// 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:
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());
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
// 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
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:
foreach (DataRow row in dataTable.Rows)
{
var clonedDoc = sourceDoc.Clone(true); // Process document... // If not saving this document, explicitly dispose clonedDoc = null;
}
Conditional Processing
// 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
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
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
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
// 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:
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) |