When submitting a filing to the SEC you want everything about this filing to be checked, double checked, or even triple checked before submitting a live filing. If you are not 100% careful mistakes can happen. Just like elsewhere in life, following a set list of tasks for every filing can end up making life easier and allow you to ensure that mistakes are minimized as much as possible. Luckily there is a task list feature in GoFiler that we can access through Legato. This means that we can take steps to ensure that the same process gets followed every time that a filing gets created.
Friday, October 04. 2019
LDC #155: Automatically Locking Projects
I originally talked about collaboration tasks in a post over two years ago so I can forgive you if you do not remember. Let’s do a quick refresher. GoFiler can store tasks in three different places: enterprise level, user level, or project level. Enterprise tasks are stored based off of the application preferences, and multiple copies of GoFiler which have their preferences pointed to the same location will share enterprise level tasks. User level tasks are stored in a user’s AppData directory, so only the logged in user will be able to see or modify these tasks. Finally, project level tasks are stored in the project data, so they can be seen and modified by anyone who has the project open.
Tasks have a number of properties that can be stored in them. A few examples include: Subject, Status, PercentComplete, Category, Owner, AssignedTo, BlockActions, DateDue, DateStarted, and DateCompleted. For a complete list of task data elements you can check the Legato Script Reference. These allow us to create tasks for any number of purposes, for any number of people, and for several different environments.
The script I want to show off today is a simple script that can be easily modified and extended to fit your personal needs. The script is hooked into the new project/submission ribbon function and it checks to see if the form type that a user selects matches a list that we define. If it does we ask the user where they want to save the project file, save it for them, and create a task that stops the project from being live filed until the task is marked as complete. I’ll point out each section that can be easily extended as we come across them.
In addition this script will show off a simple recursion. We’re going to enter the preprocess, mimic the function that the program would run, and then we call the menu function again with the parameters that we get in the preprocess. While this technically enters the preprocess again, we skip it by using a global boolean to track if we are in the preprocess called by the script or whether we are in the preprocess called by the user. If that global variable is set we skip the preprocess and run the function with the parameters that were passed to it by our other preprocess. We then run the postprocess half of the function which returns back out to the initial preprocess call which then unsets our global boolean, and cancels running the GoFiler function. While this sounds complicated, it is pretty simple to follow along with and will be explained in detail as we run through the script.
Let’s take a look at the full script:
/***************************************************************************************************************** Add Project Tasks On Creation ----------------------------- Revision: 10-04-19 JCK Initial creation Notes: - (c) 2019 Novaworks, LLC. All Rights Reserved. *****************************************************************************************************************/ int bRunByScript; string form; string formlist[]; //Setup is run on startup int setup() { //Set the hooks for new project MenuSetHook("FILE_NEW_SUBMISSION", GetScriptFilename(), "check_formtype"); MenuSetHook("FILE_NEW_PROJECT", GetScriptFilename(), "check_formtype"); //Create a formlist of forms formlist[0] = "8-K"; return ERROR_NONE; } //Run from the hook int check_formtype(int f_id, string mode) { //Declare variables string params[]; string taskid, filename; int rc; string em; //If we are in preprocess if (mode == "preprocess"){ //If we have not been run from the script if (bRunByScript == FALSE) { //Get form selection from the user form = EDGARSelectForm(); //If not blank (cancelled) if (form != "") { //Now run by script bRunByScript = TRUE; //Run our function with the chosen form RunMenuFunction("FILE_NEW_SUBMISSION", FormatString("Form:%s",form)); //No longer run by script bRunByScript = FALSE; } //Cancel our function from finishing return ERROR_EXIT; } //If run by script just return - prevent infinite loop else { return ERROR_NONE; } } //Postprocess else { //Find if our formtype is in the declared list rc = FindInList(formlist, form); //If in the list if (rc >= 0) { //Get a filename from the user filename = BrowseSaveFile("Choose Project Save Location", "Project Files (*.gfp)|*.gfp", "", 0, "gfp"); //Get error rc = GetLastError(); //If error or cancelled if (filename == "" || rc == ERROR_CANCEL) { //Close the file rc = RunMenuFunction("FILE_CLOSE", "ForceClose:true"); if (IsError(rc) == TRUE) { MessageBox("Error Closing Project: 0x%08X", rc); } return ERROR_CANCEL; } //Save the project with the name given from the user RunMenuFunction("FILE_SAVE", FormatString("Filename:%s", filename)); //Populate task parameters params["Location"] = "PT"; params["BlockActions"] = "EDGAR_SUBMIT_LIVE EDGAR_QUEUE_LIVE"; params["Subject"] = "Complete iXBRL Tagging"; //Create task taskid = CreateTask(params); //If failed if (taskid == "") { //Report error rc = GetLastError(); em = GetLastErrorMessage(); MessageBox("Error Creating Task: 0x%08X, %s", rc, em); return ERROR_NONE; } //If successful else { //Save the project with the task in it RunMenuFunction("FILE_SAVE"); } } //Successful return return ERROR_NONE; } } //Setup if run from the IDE int main() { setup(); return ERROR_NONE; }
The first portion of the script is a couple global variables and the setup function:
int bRunByScript; string form; string formlist[]; //Setup is run on startup int setup() { //Set the hooks for new project MenuSetHook("FILE_NEW_SUBMISSION", GetScriptFilename(), "check_formtype"); MenuSetHook("FILE_NEW_PROJECT", GetScriptFilename(), "check_formtype"); //Create a formlist of forms formlist[0] = "8-K"; return ERROR_NONE; }
These three globals are important as our script will end up having multiple copies running simultaneously. Our setup function hooks our function into the two menu functions for creating a new project file. One important thing to note is that this will only work if a user clicks directly on the button for “New Project/Submission” as the “New” dialog does not call the menu function. This may be changed in the future, but it is important to keep in mind for now. The setup function then creates the global formlist array. For my example it only includes the form “8-K”, but you can extend this list to include as many forms as you want. You could also forgo the list entirely and make this script always add tasks.
The only other function in this script is the check_formtype function, which we have now hooked to the menu function. Let’s look at the beginning of this function:
//Run from the hook int check_formtype(int f_id, string mode) { //Declare variables string params[]; string taskid, filename; int rc; string em; //If we are in preprocess if (mode == "preprocess"){ //If we have not been run from the script if (bRunByScript == FALSE) { //Get form selection from the user form = EDGARSelectForm();
The first thing we do, like usual, is declare some variables. We’re creating variables to store task parameters, return values from some functions, and error checking variables. Then we check to see if we are in preprocess. If we are preprocess we check to see if our boolean “bRunByScript” is false. If it is false we get an EDGAR form type from the user.
Let’s dive into what we are actually doing here. Remember that while we are in preprocess the menu function itself has not been run yet. We’re performing actions before the menu function runs and then deciding if the menu function is allowed to function as normal or not. In this case, we’re actually going to be a little recursive, which is why we have our boolean global variable. The first time that the user clicks on the menu function we want to come into these two if statements and continue on to the EDGAR selection form. In other words we are mirroring the functionality of the menu function when the user clicks on the ribbon: the EDGAR form selection dialog will appear.
We will then take the form selected by the user and call our hooked function again:
//If not blank (cancelled) if (form != "") { //Now run by script bRunByScript = TRUE; //Run our function with the chosen form RunMenuFunction("FILE_NEW_SUBMISSION", FormatString("Form:%s",form)); //No longer run by script bRunByScript = FALSE; } //Cancel our function from finishing return ERROR_EXIT; }
If the user selects a form we set our global variable to true and then call RunMenuFunction() to our hooked menu function (FILE_NEW_SUBMISSION) with the form as a variable. This will cause a new project with that form type to be created and opened. It will also run the postprocess half of our function. If you want to follow along chronologically with how the code will run you will want to skip down a couple paragraphs to where I go through the postprocess. Once that has finished, we set our global bRunByScript variable back to false, and then we’re going to return with ERROR_EXIT so that the rest of the function does not run.
Essentially the steps look like this: User clicks on the ribbon button, our preprocess runs and gets the formtype, the preprocess runs the function with the formtype that the user has chosen, our postprocess runs and creates the task in the project, the preprocess returns an exit so that the function does not run again.
The last line of our preprocess is:
//If run by script just return - prevent infinite loop else { return ERROR_NONE; } }
If bRunByScript is true we just want to return ERROR_NONE, as the only time this case is true is if the script has run the function with a form type parameter and we want the internal GoFiler function to run. After the function finishes the postprocess runs and we make to down to this code:
//Postprocess else { //Find if our formtype is in the declared list rc = FindInList(formlist, form); //If in the list if (rc >= 0) { //Get a filename from the user filename = BrowseSaveFile("Choose Project Save Location", "Project Files (*.gfp)|*.gfp", "", 0, "gfp"); //Get error rc = GetLastError(); //If error or cancelled if (filename == "" || rc == ERROR_CANCEL) { //Close the file rc = RunMenuFunction("FILE_CLOSE", "ForceClose:true"); if (IsError(rc) == TRUE) { MessageBox("Error Closing Project: 0x%08X", rc); } return ERROR_CANCEL; } //Save the project with the name given from the user RunMenuFunction("FILE_SAVE", FormatString("Filename:%s", filename));
We use FindInList() to see if the user’s selected form type is in the list that we defined in setup(). If the function returns a zero or higher we know that the value is in the list, so we continue onward. We use BrowseSaveFile() to get a location from the user. We limit the user to only selecting a *.gfp filetype as the project MUST be saved as a GoFiler Project in order for this script to work. We use GetLastError() to get the return code from asking the user for a filename. The most common error code here other than ERROR_NONE is ERROR_CANCEL which means that the user either closed the dialog or clicked on the “Cancel” button. If this is the case, we force the file to close by using RunMenuFunction() with the function “FILE_CLOSE.” This is another place where you could change the behavior to an action that you would prefer, such as leaving the file as untitled and not adding the tasks. Finally we save the currently open project with the filename that the user just selected by again using RunMenuFunction(), this time with the “FILE_SAVE” function and passing it the filename that the user selected. This will save the file without any further interaction from the user.
Now our project is prepped and ready for a task to be added:
//Populate task parameters params["Location"] = "PT"; params["BlockActions"] = "EDGAR_SUBMIT_LIVE EDGAR_QUEUE_LIVE"; params["Subject"] = "Complete iXBRL Tagging"; //Create task taskid = CreateTask(params); //If failed if (taskid == "") { //Report error rc = GetLastError(); em = GetLastErrorMessage(); MessageBox("Error Creating Task: 0x%08X, %s", rc, em); return ERROR_NONE; }
Using some of the task properties I mentioned earlier we populate a string array where the keys are the properties and the values are the values of the properties we want to set. In this case we’re setting three parameters: Location, BlockActions, and Subject. Location is a required property. We are setting ours to “PT” which is a Project Task. The other two options are “UT” for User Tasks and “ET” for Enterprise Tasks. BlockActions is a series of functions that are disabled until the task is marked as done, separated by spaces. So here we are disabling the EDGAR_SUBMIT_LIVE and the EDGAR_QUEUE_LIVE functions. Finally the subject is the name of the task, which for us is “Complete iXBRL Tagging.” This is going to be another section where your individual needs will come in to play. You can set additional parameters as needed, or even add additional tasks if you want more than one.
After all of the tasks parameters are set we use the CreateTask() function to actually add the task to GoFiler. If there is an error the string returned from CreateTask() is blank. We report the error to the user.
Finally there is just a little bit of cleanup that needs to be done:
//If successful else { //Save the project with the task in it RunMenuFunction("FILE_SAVE"); } } //Successful return return ERROR_NONE; } }
If the task was successfully added to the project we tell the program to save the file again using RunMenuFunction() for FILE_SAVE. We don’t have to pass a name to the function as the project has already been saved using a name, so this is just the equivalent of hitting the save button. Finally we return an ERROR_NONE to say that we are done with the postprocess part of the function. This would return back to the preprocess half above.
This script gives us the base for a lot of powerful options. In addition to extending the project types and tasks we can also use the recursive logic to work for other scripts as well. Anytime that we may want to extend a menu function’s initial function and have actions on both the front and back end of the menu function you can think about using this logic to keep variables stored. Using this script as a base you can create new tasklists for your users to follow, each one created automatically every time that a project is created. Locking out filing functions until tasks are finished means that a task will at least have to be looked at before the filing can occur. This should, in the end, lead to less mistakes, less hardships, and more happiness all around.
Joshua Kwiatkowski is a developer at Novaworks, primarily working on Novaworks’ cloud-based solution, GoFiler Online. He is a graduate of the Rochester Institute of Technology with a Bachelor of Science degree in Game Design and Development. He has been with the company since 2013. |
Additional Resources