No edit summary
Tag: Reverted
No edit summary
Tag: Manual revert
 
Line 8: Line 8:




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




Line 31: Line 31:


// Create a new document
// Create a new document
ZinWord document = new ZinWord(zinBL);
ZinWord document = new ZinWord(zinBL);


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


// Load from stream
// Load from stream
document.LoadStream(memoryStream);
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);
document.SaveDocument(saveFileName);


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




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);
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);
ZinWord bulkDocument = new ZinWord(zinBL);
bulkDocument.AttachDocument(sourceDocument1);
bulkDocument.AttachDocument(sourceDocument1);
bulkDocument.AttachDocument(sourceDocument2);
bulkDocument.AttachDocument(sourceDocument2);
bulkDocument.SaveDocument("Bulk_Output.pdf");
bulkDocument.SaveDocument("Bulk_Output.pdf");




Line 79: Line 79:


// Set text value
// Set text value
document.SetBookmarkString("BookmarkName", "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);
document.SetBookmarkHtml("BookmarkName", htmlString);


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


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


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




Line 98: Line 98:


// Remove a bookmark and its content
// Remove a bookmark and its content
document.DeleteBookmark("BookmarkName");
document.DeleteBookmark("BookmarkName");




Line 105: Line 105:


// Retrieve all bookmarks in the document
// Retrieve all bookmarks in the document
var bookmarks = document.GetBookmarks();
var bookmarks = document.GetBookmarks();


foreach (var bookmark in bookmarks)
foreach (var bookmark in bookmarks)
{
{
// Process each bookmark
    // Process each bookmark
string name = bookmark.Name;
    string name = bookmark.Name;
// ... do something with the bookmark
    // ... do something with the bookmark
}
}




== Standard Bookmarks ==
== Standard Bookmarks ==


ZinformAccounts provides automatic processing for common bookmarks:
ZinformAccounts provides automatic processing for common bookmarks:


=== ProcessStandardBookmarks ===
=== ProcessStandardBookmarks ===




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




Line 160: Line 160:




zinBL.DocumentFunctions.ProcessExtraInvoiceBookmarks(
zinBL.DocumentFunctions.ProcessExtraInvoiceBookmarks(
document, otherParty, parameters, zinBL,  
    document, otherParty, parameters, zinBL,  
logoWidth, logoHeight, minAddressLines,  
    logoWidth, logoHeight, minAddressLines,  
addresseeFirstLineAddress, stateAsCity
    addresseeFirstLineAddress, stateAsCity
);
);




Line 172: Line 172:




zinBL.DocumentFunctions.ProcessInvoiceBookmarks(
zinBL.DocumentFunctions.ProcessInvoiceBookmarks(
document, otherParty, parameters, zinBL, transaction,
    document, otherParty, parameters, zinBL, transaction,
minAddressLines, stateAsCity, "DELIVERY"
    minAddressLines, stateAsCity, "DELIVERY"
);
);




Line 186: Line 186:




zinBL.DocumentFunctions.ProcessExtraStatementBookmarks(
zinBL.DocumentFunctions.ProcessExtraStatementBookmarks(
document, otherParty, parameters, zinBL,  
    document, otherParty, parameters, zinBL,  
logoWidth, logoHeight, minAddressLines,  
    logoWidth, logoHeight, minAddressLines,  
addresseeFirstLineAddress, stateAsCity
    addresseeFirstLineAddress, stateAsCity
);
);




Line 201: Line 201:


// Get string value
// Get string value
string documentId = parameters.GetItemString("DocumentId");
string documentId = parameters.GetItemString("DocumentId");


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


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


// Get output extension
// Get output extension
string extension = parameters.GetItemString("OutputExtension");
string extension = parameters.GetItemString("OutputExtension");
string filter = parameters.GetItemString("OutputExtensionFilter");
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);
returnParameters.SetItemString("SaveLocation", saveLocation);


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




Line 229: Line 229:




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


if (saveLocation != null && saveLocation.Length > 0)
if (saveLocation != null && saveLocation.Length > 0)
{
{
// User selected a location
    // User selected a location
}
}
else
else
{
{
// User cancelled - handle error
    // User cancelled - handle error
returnParameters.SetItemString("ReturnError", "No File Save Location Chosen!");
    returnParameters.SetItemString("ReturnError", "No File Save Location Chosen!");
return false;
    return false;
}
}




Line 251: Line 251:




string folderPath = Path.Combine(baseFolderPath, $"OtherParty_{otherPartyId}");
string folderPath = Path.Combine(baseFolderPath, $"OtherParty_{otherPartyId}");


if (!Directory.Exists(folderPath))
if (!Directory.Exists(folderPath))
{
{
Directory.CreateDirectory(folderPath);
    Directory.CreateDirectory(folderPath);
}
}


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




Line 267: Line 267:


// Validate the DataTable parameter
// Validate the DataTable parameter
DataTable otherPartyDataTable;
DataTable otherPartyDataTable;
if (otherPartyDataTable != null && otherPartyDataTable is DataTable)
if (otherPartyDataTable != null && otherPartyDataTable is DataTable)
{
{
otherPartyDataTable = (DataTable)otherPartyDataTable;
    otherPartyDataTable = (DataTable)otherPartyDataTable;
}
}
else
else
{
{
throw new InvalidOperationException("otherPartyDataTable is not a valid DataTable");
    throw new InvalidOperationException("otherPartyDataTable is not a valid DataTable");
}
}


// Loop through rows
// Loop through rows
foreach (DataRow row in otherPartyDataTable.Rows)
foreach (DataRow row in otherPartyDataTable.Rows)
{
{
// Get values from the row
    // Get values from the row
string otherPartyId = (string)row["OtherPartyId"];
    string otherPartyId = (string)row["OtherPartyId"];
   
// Load the full OtherParty object
    // Load the full OtherParty object
var otherParty = zinBL.OtherPartyFunctions.GetOtherPartyById(otherPartyId);
    var otherParty = zinBL.OtherPartyFunctions.GetOtherPartyById(otherPartyId);
   
// Process document for this party
    // Process document for this party
}
}




Line 295: Line 295:




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




Line 326: Line 326:


// Get statement details (aged balances)
// Get statement details (aged balances)
ZinStatementDetails details = zinBL.DocumentFunctions.GetStatementDetails(
ZinStatementDetails details = zinBL.DocumentFunctions.GetStatementDetails(
zinBL,  
    zinBL,  
otherParty,  
    otherParty,  
statementDate,  
    statementDate,  
DocumentTypes.ARStatement  // or DocumentTypes.APRemittance
    DocumentTypes.ARStatement  // or DocumentTypes.APRemittance
);
);


// Access aged balances
// Access aged balances
decimal current = details.CurrentBalance;
decimal current = details.CurrentBalance;
decimal oneMonth = details.OneMonthOverdue;
decimal oneMonth = details.OneMonthOverdue;
decimal twoMonths = details.TwoMonthsOverdue;
decimal twoMonths = details.TwoMonthsOverdue;
decimal threeMonths = details.ThreeMonthsOverdue;
decimal threeMonths = details.ThreeMonthsOverdue;
decimal total = details.TotalDue;
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(
var (transactions, statementDetails) = zinBL.DocumentFunctions.GetMonthlyRunningTransactions2(
statementDate,  
    statementDate,  
otherPartyId,  
    otherPartyId,  
zinBL
    zinBL
);
);


// transactions is List<StatementTransaction2>
// transactions is List<StatementTransaction2>
Line 355: Line 355:


// Process transactions
// Process transactions
foreach (var tran in transactions)
foreach (var tran in transactions)
{
{
DateTime date = tran.Date;
    DateTime date = tran.Date;
int type = tran.Type;
    int type = tran.Type;
string reference = tran.Reference;
    string reference = tran.Reference;
decimal value = tran.Value;
    decimal value = tran.Value;
decimal runningBalance = tran.RunningBalance;
    decimal runningBalance = tran.RunningBalance;
decimal subTotal = tran.SubTotal;
    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);
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);
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();
var htmlTable = new StringBuilder();


htmlTable.AppendLine("<table style='width: 100%; border-collapse: collapse;'>");
htmlTable.AppendLine("<table style='width: 100%; border-collapse: collapse;'>");
htmlTable.AppendLine("<thead>");
htmlTable.AppendLine("<thead>");
htmlTable.AppendLine("<tr>");
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>Date</b></td>");
htmlTable.AppendLine("<td style='padding: 5px; font-family: \"Aptos\", sans-serif;'><b>Reference</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("<td style='padding: 5px; text-align: right; font-family: \"Aptos\", sans-serif;'><b>Amount</b></td>");
htmlTable.AppendLine("</tr>");
htmlTable.AppendLine("</tr>");
htmlTable.AppendLine("</thead>");
htmlTable.AppendLine("</thead>");
htmlTable.AppendLine("<tbody>");
htmlTable.AppendLine("<tbody>");


foreach (var item in dataList)
foreach (var item in dataList)
{
{
htmlTable.AppendLine("<tr>");
    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.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; 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($"<td style='padding: 2px; text-align: right; font-family: \"Aptos\", sans-serif;'>{item.Amount:$#,##0.00}</td>");
htmlTable.AppendLine("</tr>");
    htmlTable.AppendLine("</tr>");
}
}


htmlTable.AppendLine("</tbody>");
htmlTable.AppendLine("</tbody>");
htmlTable.AppendLine("</table>");
htmlTable.AppendLine("</table>");


// Insert into document
// Insert into document
document.SetBookmarkHtml("TableBookmark", htmlTable.ToString());
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);
var emailAddresses = otherParty.GetEmailContactsAsEmailCompatibleStringById(document.EmailContactIds);


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




Line 461: Line 461:




try
try
{
{
// Your code here
    // Your code here
   
if (somethingWentWrong)
    if (somethingWentWrong)
{
    {
returnParameters.SetItemString("ReturnError", "Clear error message here");
        returnParameters.SetItemString("ReturnError", "Clear error message here");
return false;
        return false;
}
    }
}
}
catch (Exception ex)
catch (Exception ex)
{
{
MessageBox.Show($"An error occurred: {ex.Message}\n{ex.StackTrace}", "Error");
    MessageBox.Show($"An error occurred: {ex.Message}\n{ex.StackTrace}", "Error");
returnParameters.SetItemString("ReturnError", ex.Message);
    returnParameters.SetItemString("ReturnError", ex.Message);
return false;
    return false;
}
}


return true;  // Success
return true;  // Success




Line 486: Line 486:




foreach (DataRow row in dataTable.Rows)
foreach (DataRow row in dataTable.Rows)
{
{
var clonedDoc = sourceDoc.Clone(true);
    var clonedDoc = sourceDoc.Clone(true);
   
// Process document...
    // Process document...
   
// If not saving this document, explicitly dispose
    // If not saving this document, explicitly dispose
clonedDoc = null;
    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;
bool wasEmailed = false;


if ( document.EmailIfPossible && emailAddresses.Length > 0)
if (document.EmailIfPossible && emailAddresses.Length > 0)
{
{
// Send email
    // Send email
wasEmailed = true;
    wasEmailed = true;
}
}


if (!wasEmailed)
if (!wasEmailed)
{
{
bulkDocument.AttachDocument(processedDoc);
    bulkDocument.AttachDocument(processedDoc);
}
}




Line 520: Line 520:




bool individual = parameters.GetItemBoolean("IndividualDocuments");
bool individual = parameters.GetItemBoolean("IndividualDocuments");
ZinWord bulkDoc = new ZinWord(zinBL);
ZinWord bulkDoc = new ZinWord(zinBL);


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


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




Line 550: Line 550:




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




Line 575: Line 575:




int minLines = 13;
int minLines = 13;
int currentLines = dataList.Count;
int currentLines = dataList.Count;
int emptyLines = Math.Max(0, minLines - currentLines);
int emptyLines = Math.Max(0, minLines - currentLines);


for (int i = 0; i < emptyLines; i++)
for (int i = 0; i < emptyLines; i++)
{
{
htmlTable.AppendLine("<tr style='height: 12px;'>");
    htmlTable.AppendLine("<tr style='height: 12px;'>");
htmlTable.AppendLine("<td></td><td></td><td></td>");
    htmlTable.AppendLine("<td></td><td></td><td></td>");
htmlTable.AppendLine("</tr>");
    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;
using ZinformAccounts.Debugger;


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


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




Line 605: Line 605:




using System;
using System;
using System.Text;
using System.Text;
using System.Windows;
using System.Windows;
using System.Data;
using System.Data;
using ZinBusinessLayer;
using ZinBusinessLayer;
using ZinBusinessLayer.HelperClasses;
using ZinBusinessLayer.HelperClasses;
using ZinBusinessLayer.Enums;
using ZinBusinessLayer.Enums;
using ZinBusinessLayer.Word;
using ZinBusinessLayer.Word;


public class DocumentScripter
public class DocumentScripter
{
{
public static BusinessLayerMain zinBL;
    public static BusinessLayerMain zinBL;
public static Document document;
    public static Document document;


public bool Main(BusinessLayerMain bl, object documentTemplate, object? otherPartyDataTable,  
    public bool Main(BusinessLayerMain bl, object documentTemplate, object? otherPartyDataTable,  
ZinParameters parameters, ZinParameters returnParameters, User? user)
                    ZinParameters parameters, ZinParameters returnParameters, User? user)
{
    {
zinBL = bl;
        zinBL = bl;
       
if (!(documentTemplate is ZinWord sourceDoc))
        if (!(documentTemplate is ZinWord sourceDoc))
{
        {
returnParameters.SetItemString("ReturnError", "Invalid document template");
            returnParameters.SetItemString("ReturnError", "Invalid document template");
return false;
            return false;
}
        }
       
document = zinBL.DocumentFunctions.GetDocument(parameters.GetItemString("DocumentId"));
        document = zinBL.DocumentFunctions.GetDocument(parameters.GetItemString("DocumentId"));
DateTime statementDate = (DateTime)parameters.GetItemDateTime("DateAsAt");
        DateTime statementDate = (DateTime)parameters.GetItemDateTime("DateAsAt");
       
string saveLocation = FileFunctions.OpenSaveFileDialog(
        string saveLocation = FileFunctions.OpenSaveFileDialog(
document.DocumentOutputFolder,
            document.DocumentOutputFolder,
$"Statement_{DateTime.Now:yyyyMMdd_hhmmss}",
            $"Statement_{DateTime.Now:yyyyMMdd_hhmmss}",
parameters.GetItemString("OutputExtension"),
            parameters.GetItemString("OutputExtension"),
parameters.GetItemString("OutputExtensionFilter")
            parameters.GetItemString("OutputExtensionFilter")
);
        );
       
if (string.IsNullOrEmpty(saveLocation))
        if (string.IsNullOrEmpty(saveLocation))
{
        {
returnParameters.SetItemString("ReturnError", "No save location chosen");
            returnParameters.SetItemString("ReturnError", "No save location chosen");
return false;
            return false;
}
        }
       
DataTable dataTable = (DataTable)otherPartyDataTable;
        DataTable dataTable = (DataTable)otherPartyDataTable;
ZinWord bulkDoc = new ZinWord(zinBL);
        ZinWord bulkDoc = new ZinWord(zinBL);
       
ProgressBarManager.Start("Processing Statements", true, dataTable.Rows.Count, progressBar =>
        ProgressBarManager.Start("Processing Statements", true, dataTable.Rows.Count, progressBar =>
{
        {
int count = 1;
            int count = 1;
           
foreach (DataRow row in dataTable.Rows)
            foreach (DataRow row in dataTable.Rows)
{
            {
if (progressBar.CancellationPending) break;
                if (progressBar.CancellationPending) break;
               
string otherPartyId = (string)row["OtherPartyId"];
                string otherPartyId = (string)row["OtherPartyId"];
var otherParty = zinBL.OtherPartyFunctions.GetOtherPartyById(otherPartyId);
                var otherParty = zinBL.OtherPartyFunctions.GetOtherPartyById(otherPartyId);
               
progressBar.ReportProgress(count, $"Processing: {otherParty.Name}");
                progressBar.ReportProgress(count, $"Processing: {otherParty.Name}");
               
var doc = sourceDoc.Clone(true);
                var doc = sourceDoc.Clone(true);
               
// Process bookmarks
                // Process bookmarks
zinBL.DocumentFunctions.ProcessStandardBookmarks(
                zinBL.DocumentFunctions.ProcessStandardBookmarks(
doc, otherParty, parameters, zinBL, 193, 70, 5
                    doc, otherParty, parameters, zinBL, 193, 70, 5
);
                );
               
// Get statement data
                // Get statement data
var (transactions, details) = zinBL.DocumentFunctions.GetMonthlyRunningTransactions2(
                var (transactions, details) = zinBL.DocumentFunctions.GetMonthlyRunningTransactions2(
statementDate, otherPartyId, zinBL
                    statementDate, otherPartyId, zinBL
);
                );
               
// Build transaction table
                // Build transaction table
var html = new StringBuilder();
                var html = new StringBuilder();
html.AppendLine("<table style='width: 100%;'>");
                html.AppendLine("<table style='width: 100%;'>");
foreach (var tran in transactions)
                foreach (var tran in transactions)
{
                {
html.AppendLine("<tr>");
                    html.AppendLine("<tr>");
html.AppendLine($"<td>{tran.Date:dd/MM/yyyy}</td>");
                    html.AppendLine($"<td>{tran.Date:dd/MM/yyyy}</td>");
html.AppendLine($"<td>{tran.Reference}</td>");
                    html.AppendLine($"<td>{tran.Reference}</td>");
html.AppendLine($"<td style='text-align: right;'>{tran.Value:$#,##0.00}</td>");
                    html.AppendLine($"<td style='text-align: right;'>{tran.Value:$#,##0.00}</td>");
html.AppendLine("</tr>");
                    html.AppendLine("</tr>");
}
                }
html.AppendLine("</table>");
                html.AppendLine("</table>");
               
doc.SetBookmarkHtml("Transactions", html.ToString());
                doc.SetBookmarkHtml("Transactions", html.ToString());
doc.SetBookmarkString("TotalDue", details.TotalDue.ToString("#,##0.00"));
                doc.SetBookmarkString("TotalDue", details.TotalDue.ToString("#,##0.00"));
               
bulkDoc.AttachDocument(doc);
                bulkDoc.AttachDocument(doc);
count++;
                count++;
}
            }
           
ProgressBarManager.Finished();
            ProgressBarManager.Finished();
});
        });
       
if (zinBL.DocumentFunctions.DocumentHasContent(bulkDoc))
        if (zinBL.DocumentFunctions.DocumentHasContent(bulkDoc))
{
        {
bulkDoc.SaveDocument(saveLocation);
            bulkDoc.SaveDocument(saveLocation);
}
        }
       
returnParameters.SetItemString("SaveLocation", saveLocation);
        returnParameters.SetItemString("SaveLocation", saveLocation);
return true;
        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 logo
  • OrganisationName - Your company name
  • OrganisationAddress - Your postal address
  • OrganisationPhone - Your phone number
  • OrganisationEmail - Your email address
  • OrganisationWebsite - Your website URL
  • OrganisationBankAccount - Bank account name and number
  • OrganisationBankAccountNumber - Bank account number only
  • OrganisationBankAccountName - Bank account name only
  • DocumentDate - Current date (formatted as "dd MMMM yyyy")
  • DocumentId - Document ID from parameters
  • GSTNumber - Tax registration number
  • OtherPartyId - Customer/Supplier ID
  • OtherPartyName - Customer/Supplier name
  • OtherPartyAddress - Customer/Supplier postal address
  • OtherPartySalutation - Greeting (from Administrator/Manager contact)
  • ReferenceId - Reference ID
  • OrganisationNameFooter - Company name for footer

ProcessExtraInvoiceBookmarks

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


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("
DateReferenceAmount
{item.Date:dd/MM/yyyy}{item.Reference}{item.Amount:$#,##0.00}

");

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


Tips for HTML Tables:

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

Email Integration

Sending Documents via Email

// 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)