Document Generation Issues

Discussions related to custom development with Select.
kwesterlage
Posts: 73
Joined: Thu May 21, 2015 2:28 pm

Document Generation Issues

Post by kwesterlage »

We are trying to auto-generate the "Disbursements Summary", "Transaction Forms" and "Single Ledger Balance" reports when certain criteria are met.
I queried the dbo.DocReport table to make sure I passed the correct context (Order.CDFs) for the "Disbursements Summary (Legal) - CDF" report.
The document was generated and successfully written to a PDF; however, it did not contain any data from the order.

Something similar happened when I tried to generate a Transaction Form for one of the receipts.
I checked the table and passed in the corresponding context (Ledger.Transaction) for the "Transaction Form (Pulte - 1 copy)" report and it was generated and written to a PDF.
This time one of the fields was populated (the receipt amount), but everything else was blank.

Lastly, I tried generating a Transaction Form again, instead using the "Transaction Form" template and it worked correctly.
The PDF was saved and all of the receipt data was filled in.

All of the report templates I mentioned are currently set up to be generated from the Reports button in the register, and all of them were successfully generated by SoftPro using the interface.
When trying to generate the docs programmatically, I tried two different orders (one CDF and one HUD) and adjusted the context and report name to match for each. The results were the same.

If required, I can email an export of the report templates to help reproduce the error.
Here is the code I used to test a CDF order:

Code: Select all

class Program
{
    public static string DocPath = @"C:\Users\kwesterlage\Downloads\AutoGeneratedDocs\";

    public const string WorkingTransactionForm = "Transaction Form";
    public const string BrokenTransactionForm = "Transaction Form (Pulte - 1 copy)";
    public const string BrokenDisbursementSummary = "Disbursements Summary (Legal) - CDF";

    static void Main(string[] args)
    {
        try
        {
            string selectServerPath = ConfigurationManager.AppSettings["SelectServerPath"];
            SelectServer select = new SelectServer(new Uri(selectServerPath));

            string reason;
            if (!select.TryAuthenticate(out reason))
            {
                // Write login failure reason to console and exit.
                Console.WriteLine(reason);
                return;
            }

            // Open Order
            IOrderStore os = select.GetService<IOrderStore>();
            IOrderInfo search = os.Orders.Where(x => x.Number == "TX-080421").FirstOrDefault();
            if (search == null) return;
            dynamic order = os.OpenOrder(search, true);

            // Open Ledger for Transaction Form context
            ILedgersManager lm = select.GetService<ILedgersManager>();
            ILedgerInfo ledgerInfo = lm.GetLedgerForOrder(((IOrder)order).ToOrderInfo().Identifier.Guid);
            IOrderLedger ledger = (IOrderLedger)lm.GetLedger(ledgerInfo);

            // Setup for doc generation
            IDocumentManager dm = select.GetService<IDocumentManager>();
            IRendererFactory rendererFactory = select.GetService<IRendererFactory>();
            IRenderer renderer = rendererFactory.Create();

            var toGenerate = new List<string> { BrokenDisbursementSummary, BrokenTransactionForm, WorkingTransactionForm };

            foreach (var doc in toGenerate)
            {
                var docInfos = dm.Documents.Where(x => x.Title == doc).FirstOrDefault();
                if (docInfos != null)
                {
                    switch (doc)
                    {
                        case WorkingTransactionForm:
                        case BrokenTransactionForm:
                            int i = 0;
                            string docPrefix = (doc == WorkingTransactionForm) ? "Working" : "Broken";
                            foreach (var transaction in ledger.Transactions.Where(t => t.Kind == TransactionKind.Receipt))
                            {
                                IRendering transactionDoc = renderer.Render(docInfos, transaction, null);
                                SaveRenderedDoc(transactionDoc, $"{DocPath}{docPrefix}TransactionForm_{i}.pdf");
                                i++;
                            }
                            break;
                        case BrokenDisbursementSummary:
                            IRendering disbursementDoc = renderer.Render(docInfos, order.CDFs, null);
                            SaveRenderedDoc(disbursementDoc, $"{DocPath}DisbursementSummary.pdf");
                            break;
                        default:
                            break;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }

    public static void SaveRenderedDoc(IRendering renderedDoc, string path)
    {
        using (Stream tmpFile = File.Create(path))
        {
            System.IO.Directory.CreateDirectory(DocPath);
            Stream docStream = renderedDoc.Export(DocumentFormat.PortableDocumentFormat);
            docStream.Seek(0, SeekOrigin.Begin);
            docStream.CopyTo(tmpFile);
        }
    }
}
kkirkfield
Posts: 27
Joined: Tue Jun 28, 2016 2:26 pm

Re: Document Generation Issues

Post by kkirkfield »

I do not have your custom transaction form document, but it should be similar to the default "Transaction Form" document. I have put together an example that demonstrates how to render the documents you need. The main thing to remember is that you need to pass the heavy weight object that is stored in the blob, and not the light weight info object. The documents that have an Order.CDFs context seem to need an instance of a CDF, and not the entire collection.

Code: Select all

using SoftPro.Accounting.Client.Ledgers;
using SoftPro.Accounting.Client.Transactions;
using SoftPro.Documents.Client;
using SoftPro.Documents.Client.Rendering;
using SoftPro.OrderTracking.Client.Orders;
using SoftPro.Select.Client;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace DocRenderingExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // Initialize the SelectServer instance from a using block so that it is disposed at the end.
            // If you do not do this, the license will remain locked by the server.
            // If there are no licenses remaining, the authentication will still pass,
            // but trying to access a service on the server will throw an exception.
            using (SelectServer server = new SelectServer(new Uri("http://localhost:8080")))
            {
                string reason;

                if (!server.TryAuthenticate(out reason))
                {
                    Console.WriteLine(reason);
                    return;
                }

                // Get the IDocumentManager service. This service provides access to documents that can be rendered.
                IDocumentManager documentManager = server.GetService<IDocumentManager>();

                // Define the names of the documents that will be rendered.
                string[] documentNames = new string[]
                {
                    "DOC_SPUNR_CDF2015_DisbursementsSummary_ProTrust",
                    "DOC_SPUNR_SingleLedgerBalanceByID_Tran",
                    "DOC_SPUNR_TransactionForm"
                };

                // Get all the needed light weight document infos in one query to the server.
                // Try to always filter the query using the IDocumentInfo.Identifier.Name.
                // This property is readonly, and will be consistent across all installs of Select.
                List<IDocumentInfo> documentInfos = documentManager.Documents
                                                                   .Where(d => d.Identifier.Name == documentNames[0] ||
                                                                          d.Identifier.Name == documentNames[1] ||
                                                                          d.Identifier.Name == documentNames[2])
                                                                   .ToList();

                // The query can return the document infos in any order,
                // so assign them to variables here for rendering later.
                IDocumentInfo disbursementSummaryDocument = documentInfos.First(d => d.Identifier.Name == documentNames[0]);
                IDocumentInfo singleLedgerBalanceDocument = documentInfos.First(d => d.Identifier.Name == documentNames[1]);
                IDocumentInfo transactionFormDocument = documentInfos.First(d => d.Identifier.Name == documentNames[2]);

                // Get the renderer that will be shared by all the document rendering.
                IRendererFactory rendererFactory = server.GetService<IRendererFactory>();
                IRenderer renderer = rendererFactory.Create();

                // Get the order store service. This service provides access to orders.
                IOrderStore orderStore = server.GetService<IOrderStore>();

                // Get the light weight test order info.
                IOrderInfo orderInfo = orderStore.Orders.Where(o => o.Number == "TX-080421").FirstOrDefault();

                if (orderInfo != null)
                {
                    // Get the heavy weight test order.
                    dynamic order = orderStore.OpenOrder(orderInfo, true);

                    // The context of the DOC_SPUNR_CDF2015_DisbursementsSummary_ProTrust document is a CDF in the Order.CDFs.
                    IRendering rendering = renderer.Render(disbursementSummaryDocument, order.CDFs[0], null);
                    ExportRenderedDocumentAsPdf(rendering, "C:\\Disbursement Summary.pdf");
                }

                // Get the ledgers manager service. This service provides access to ledgers.
                ILedgersManager ledgersManager = server.GetService<ILedgersManager>();

                // Get the light weight ledger info for the test order.
                ILedgerInfo ledgerInfo = ledgersManager.GetLedgerForOrder(orderInfo.Identifier.Guid);

                if (ledgerInfo != null)
                {
                    // Get the heavy weight ledger for the test order.
                    ILedger ledger = ledgersManager.GetLedger(ledgerInfo);

                    // The context of the DOC_SPUNR_SingleLedgerBalanceByID_Tran document is the ledger.
                    // Note that you must pass the ledger and not the ledger info,
                    // or it will not have access to the data.
                    IRendering rendering = renderer.Render(singleLedgerBalanceDocument, ledger, null);
                    ExportRenderedDocumentAsPdf(rendering, "C:\\Single Ledger Balance.pdf");

                    // Get the transactions manager service. This service provides access to transactions.
                    ITransactionsManager transactionsManager = server.GetService<ITransactionsManager>();

                    // Get the light weight transaction infos for all receipts in the test order ledger.
                    List<ITransactionInfo> transactionInfos = ledger.Transactions.Where(t => t.Kind == TransactionKind.Receipt).ToList();

                    foreach (ITransactionInfo tInfo in transactionInfos)
                    {
                        // Get the heavy weight transaction for the current transaction info.
                        ITransaction t = transactionsManager.GetTransaction(tInfo);

                        // The context of the DOC_SPUNR_TransactionForm document is the transaction.
                        // Note that you must pass the transaction and not the transaction info,
                        // or it will not have access to the data.
                        rendering = renderer.Render(transactionFormDocument, t, null);
                        ExportRenderedDocumentAsPdf(rendering, "C:\\Transaction Form.pdf");
                    }
                }
            }
        }

        static void ExportRenderedDocumentAsPdf(IRendering rendering, string path)
        {
            // You must dispose of streams when they are no longer needed.
            // If you don't, the memory and resource handles for those streams
            // will not be released until the garbage collector decides to run for those objects.
            using (Stream documentStream = rendering.Export(DocumentFormat.PortableDocumentFormat))
            using (FileStream fileStream = File.Create(path))
            {
                // Copy the document stream into the new file stream.
                documentStream.Seek(0, SeekOrigin.Begin);
                documentStream.CopyTo(fileStream);
            }
        }
    }
}

kwesterlage
Posts: 73
Joined: Thu May 21, 2015 2:28 pm

Re: Document Generation Issues

Post by kwesterlage »

Unfortunately this hasn’t helped us solve why no data is generated on the custom report, even when providing the correct heavyweight context (ITransaction).

So the question to SoftPro is: how are you determining what to send to the .Render() method to generate the doc? Other than the information in the DocReport table in the database, I’m not sure what else we could pass to the method to get this custom doc to generate.
kkirkfield
Posts: 27
Joined: Tue Jun 28, 2016 2:26 pm

Re: Document Generation Issues

Post by kkirkfield »

Verify that your custom report has a context of {{Ledger.Transaction}} in the dbo.DocReport view like you expect.

The renderer should be detecting the context that is required, and getting it from the object you passed to Render(). If it is still not working, try the following code:

Code: Select all

rendering = renderer.Render(transactionFormDocument, null, new TransactionPrompt(t));
ExportRenderedDocumentAsPdf(rendering, "C:\\Transaction Form.pdf");

Code: Select all

class TransactionPrompt : Prompt<PromptEventArgs>
    {
        private ITransaction _transaction;

        public TransactionPrompt(ITransaction transaction)
        {
            _transaction = transaction;
        }

        protected override void OnRequest(PromptEventArgs value)
        {
            IValueRequest<string> contextIdRequest = (IValueRequest<string>)value.Requests.First();
            contextIdRequest.Value = _transaction.ID.Guid.ToString();
        }
    }
This code is manually passing the context id to the prompt, because null is specified in the context for Render(). Put a breakpoint in the OnRequest() method and verify that your report requires the context id. If it is not requesting the context id, then something may be wrong in the report, or it may be set up incorrectly. Let me know the results.
kwesterlage
Posts: 73
Joined: Thu May 21, 2015 2:28 pm

Re: Document Generation Issues

Post by kwesterlage »

I followed your instructions to look at the OnRequest PromptEventArgs and yes, it does contain a request for CONTEXTID.
If the actual report was to blame for the problem, I would expect the UI-generated one to be incorrect as well (which is not the case).
BobRichards
Posts: 1377
Joined: Wed Jan 15, 2014 3:50 pm
Location: Raleigh, NC
Contact:

Re: Document Generation Issues

Post by BobRichards »

I want you to know that we are working on an example for you and will post it soon.
Bob Richards, Senior Software Developer, SoftPro
kwesterlage
Posts: 73
Joined: Thu May 21, 2015 2:28 pm

Re: Document Generation Issues

Post by kwesterlage »

Great, thank you.
BobRichards
Posts: 1377
Joined: Wed Jan 15, 2014 3:50 pm
Location: Raleigh, NC
Contact:

Re: Document Generation Issues

Post by BobRichards »

The Crystal Report documents require passing information that references row IDs in the Select SQL database. Here is an example of a CDF Disbursement Summary report where the SQL information is obtained for the order and the first CDF. Other reports will use a similar method to resolve any required information.

Code: Select all

// Get the order.
IOrderStore os = ss.GetService<IOrderStore>();
IOrderInfo search = os.Orders.Where(x => x.Number == "2016080002").FirstOrDefault();
IOrder order = os.OpenOrder(search, true);

// Get document.
IDocumentManager dm = ss.GetService<IDocumentManager>();
IRendererFactory rendererFactory = ss.GetService<IRendererFactory>();
IRenderer renderer = rendererFactory.Create();
IDocumentInfo docInfo = dm.Documents
	.Where(t => t.Identifier.Name == "DOC_SPUNR_CDF2015_DisbursementsSummary_ProTrust")
	.FirstOrDefault();

// Get prompt responses from tag collection.
int rootID = Int32.Parse(((IOrderItem)order).GetTag("ROOT_ID"));
IList CDFs = (IList)order.GetProperty("CDFs");
IOrderItem cdf = (IOrderItem)CDFs[0];
int cdfContext = Int32.Parse(cdf.GetTag("PERSIST_ID"));

// Pass prompts to prompt response object and render doc.
//   (In this case, renderer.Render context not required since passed as a prompt.)
RenderPrompts prompts = new RenderPrompts(rootID, cdfContext);
IRendering rendering = renderer.Render(docInfo, null, prompts);

// Save rendered document to file...
Here is the response class that provides the order and context information when prompted.

Code: Select all

// Response handler.
class RenderPrompts : IPrompt<PromptEventArgs>
{
	private int _rootID;
	private int _contextID;

	// Pass in desired responses for requested prompt IDs.
	public RenderPrompts(int rootID, int contextID)
	{
		_rootID = rootID;
		_contextID = contextID;
	}

	public void Request(PromptEventArgs value)
	{
		foreach (var prompt in value.Requests)
		{
			if (prompt.ID == "ROOTID#")
			{
				IValueRequest response = (IValueRequest)prompt;
				response.Handled = true;
				response.Value = _rootID;
			}
			else if (prompt.ID == "CONTEXTID#")
			{
				IValueRequest response = (IValueRequest)prompt;
				response.Handled = true;
				response.Value = _contextID;
			}
		}
	}
}
Currently, some order contacts (and possibly other items) may be displayed as hot spots on the rendered document. This will be corrected in a future release.
Bob Richards, Senior Software Developer, SoftPro
BobRichards
Posts: 1377
Joined: Wed Jan 15, 2014 3:50 pm
Location: Raleigh, NC
Contact:

Re: Document Generation Issues

Post by BobRichards »

This is the Single Ledger Balance report. It is also Crystal Reports based. In this case, the report requires that the desired ledger guid is passed via prompts. I also passed a value for the required "Sort option" prompt. Select supplied a default value, but as a best practice, you should always supply required parameters yourself.

Code: Select all

// Open Order
IOrderStore os = ss.GetService<IOrderStore>();
IOrderInfo search = os.Orders.Where(x => x.Number == "2016080002").FirstOrDefault();

// Open Ledger for context.
ILedgersManager lm = ss.GetService<ILedgersManager>();
ILedgerInfo ledgerInfo = lm.GetLedgerForOrder(search.Identifier.Guid);
IOrderLedger ledger = (IOrderLedger)lm.GetLedger(ledgerInfo);

// Setup for doc generation
IDocumentManager dm = ss.GetService<IDocumentManager>();
IRendererFactory rendererFactory = ss.GetService<IRendererFactory>();
IRenderer renderer = rendererFactory.Create();
IDocumentInfo docInfo = dm.Documents
	.Where(t => t.Identifier.Name == "DOC_SPUNR_SingleLedgerBalanceByID_Trust_Adj")
	.FirstOrDefault();

// Pass prompts to prompt response object and render doc.
RenderPrompts prompts = new RenderPrompts(ledger.ID);
IRendering rendering = renderer.Render(docInfo, null, prompts);

// Save rendered document to file...
Prompts response class.

Code: Select all

class RenderPrompts : IPrompt<PromptEventArgs>
{
	private string _contextID;

	public RenderPrompts(Guid ledger)
	{
		_contextID = ledger.ToString();
	}

	public void Request(PromptEventArgs value)
	{
		foreach (var prompt in value.Requests)
		{
			if (prompt.ID == "CONTEXTID")
			{
				// The ledger guid as a string.
				IValueRequest response = (IValueRequest)prompt;
				response.Handled = true;
				response.Value = _contextID;
			}
			else if (prompt.ID == "SORTORDER_REFERENCETRANDATEAMOUNT")
			{
				// This is a required prompt.  Even though it has a default value,
				//   you should probably set it to prevent errors in the future.
				IValueRequest response = (IValueRequest)prompt;
				response.Handled = true;
				response.Value = "Reference Number";
			}
		}
	}
}
Bob Richards, Senior Software Developer, SoftPro
kwesterlage
Posts: 73
Joined: Thu May 21, 2015 2:28 pm

Re: Document Generation Issues

Post by kwesterlage »

We are still having issues with our SoftPro-customized version of the Transaction Form report.

I found no tags matching "PERSIST_ID" in the: ITransactionInfo, ITransaction, ILedger and IOrderLedger objects.
I then tried passing ITransaction.ID.Guid.ToString() as the CONTEXTID parameter and that resulted in same error as before (report is being generated but without most of the data).
Post Reply