Document Scripting: Difference between revisions
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
= | = 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 <code>DocumentScripter</code> and have a <code>Main</code> 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); | document.LoadDocument(documentFileName); | ||
// Load from stream | |||
document.LoadStream( | 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); | |||
document.SaveDocument( | // 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"); | document.SetBookmarkString("BookmarkName", "Value"); | ||
===== | // Set HTML content (for formatted text, tables, etc.) | ||
document. | 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:''' | |||
* <code>OrganisationLogo</code> - Your company logo | |||
* <code>OrganisationName</code> - Your company name | |||
* <code>OrganisationAddress</code> - Your postal address | |||
* <code>OrganisationPhone</code> - Your phone number | |||
* <code>OrganisationEmail</code> - Your email address | |||
* <code>OrganisationWebsite</code> - Your website URL | |||
* <code>OrganisationBankAccount</code> - Bank account name and number | |||
* <code>OrganisationBankAccountNumber</code> - Bank account number only | |||
* <code>OrganisationBankAccountName</code> - Bank account name only | |||
* <code>DocumentDate</code> - Current date (formatted as "dd MMMM yyyy") | |||
* <code>DocumentId</code> - Document ID from parameters | |||
* <code>GSTNumber</code> - Tax registration number | |||
* <code>OtherPartyId</code> - Customer/Supplier ID | |||
* <code>OtherPartyName</code> - Customer/Supplier name | |||
* <code>OtherPartyAddress</code> - Customer/Supplier postal address | |||
* <code>OtherPartySalutation</code> - Greeting (from Administrator/Manager contact) | |||
* <code>ReferenceId</code> - Reference ID | |||
* <code>OrganisationNameFooter</code> - 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:''' | |||
* <code>DeliverToDetails</code> - 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("<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 | |||
document.SetBookmarkHtml("TableBookmark", htmlTable.ToString()); | |||
</syntaxhighlight> | |||
'''Tips for HTML Tables:''' | |||
* Use inline styles (external CSS won't work) | |||
* Specify <code>font-family: "Aptos", sans-serif;</code> for consistency | |||
* Use <code>text-align: right</code> for numbers | |||
* Add padding for readability: <code>padding: 5px;</code> | |||
* Format currency: <code>{value:$#,##0.00}</code> | |||
== 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"> | ||
document | // Only add to bulk document if not emailed | ||
bool wasEmailed = false; | |||
if (document.EmailIfPossible && emailAddresses.Length > 0) | |||
document. | { | ||
// Send email | |||
wasEmailed = true; | |||
} | |||
document. | 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("<tr style='height: 12px;'>"); | |||
htmlTable.AppendLine("<td></td><td></td><td></td>"); | |||
htmlTable.AppendLine("</tr>"); | |||
} | |||
</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("<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; | |||
} | |||
} | |||
</syntaxhighlight> | |||
== Troubleshooting == | |||
{| class="wikitable" | |||
! 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 <code>ProgressBarManager.Finished()</code> | |||
|- | |||
| 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 | |||
|} | |||
==== | == Additional Resources == | ||
* [[Transaction_Types|Transaction Types Reference]] | |||
* [[Document_Templates|Creating Document Templates]] | |||
* [[Bookmark_Reference|Complete Bookmark Reference]] | |||
[[Category:Scripting]] | |||
[[Category:Documents]] | |||
Revision as of 00:14, 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
}
} </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 |