MoneyWorks Webhooks

Although not a specific "feature" of MoneyWorks, webhooks (i.e. initiating a scripted action in MoneyWorks via a URL) can be easily implemented using a special custom report. This relies on three other MoneyWorks features:

  1. Reports can be invoked using the MoneyWorks REST APIs;
  2. Reports can have embedded scripts.
  3. You can pass parameters to reports in a REST request.

To embed a script in a report, just add a custom control to the report, set it to be a script, and enter your script (it is better to test the script separately in the script editor then paste the working script in).

In the example below we have a script "myHooks" with two handlers, "add" and "cubicvolume".

Webhook simple report script

In the report itself, we can call a script handler by, for example, =myhooks:add(3,4).

The report, which we have called "~hook", could be as follows:

Webhook simple report

There is a variable script to specify the handler, and variables a and b for handler add, and variables l, w and h for handler cubicvolume. These variables will be instantiated automatically by the parameters that are passed in the /doreport REST call (if we also want to run the report from the user interface, the variables would need to be set up as separate custom controls).

To invoke this through a URL (having uploaded the report to the Datacentre)

http://.../REST/.../doreport/report=~hook&script=cubicvolume&l=10&w=5&h=25

which returns 1250.

In practice your scripts will be more capable, for example extracting all new customers today as JSON, or a list of stock movements through a particular location.

Notes:

  1. The report name starts with a tilde (~) so it will not appear in the MoneyWorks Reports menu (and hence can't be invoked through the user interface).
  2. The handlers in script that are to be called from the report must be public.
  3. The scripts use a subset of the mwScripting. In particular the following are not available:
    • Alert, Ask, Say or anything that might create or interact with a window or selection
    • Any file operations, such as syslog and file_open
    • CURL calls
  4. You will need to have the proper authorisation headers for your REST requests.

Keep in mind also that the report writer itself can do a lot of what you can do in a script in terms of number crunching and data manipulation (the scripts were in fact redundant in our trivial example). Also, to update records in MoneyWorks there is /import and /evaluate/expr=replacefield(...), and of course /export for extracting selected data.

Posted in MWScript, REST | Comments Off on MoneyWorks Webhooks

SendSMTPMail

SendSMTPMail is a script handler you can implement that takes over the job of sending SMTP mail.
The handler signature is:

SendSMTPMail(recipients, attachmentPath, subject, message, attachmentFileName)

When MoneyWorks 9 has an email + attachment to send and the preferences are set to send via SMTP, the job is usually performed by Built_In:__SendSMTPMail.

If there is a script handler in your document named SendSMTPMail, then that handler will be called instead (note: if there are multiple such handlers, they will all be called, potentially resulting in the message being sent multiple times)

  • recipients may be a single recipient or a comma-delimited list
  • attachmentPath is the posix or windows path to the file to be attached
  • subject is the email subject
  • message is the plain text message; may be blank
  • attachmentFileName is a suggested attachment name which may differ from the actual filename of the attachmentPath (e.g. "Invoice 1234.pdf")

In MoneyWorks 9, your SendSMTPMail handler can call internal handlers Built_In:__SendSMTPMail(to, path, subject, message_text, attachmentName) or Built_In:__SendSMTPMail_WithCredentials(to, path, subject, msg, attachmentName,
server, replyTo, authUser, authPass)
to do the heavy lifting for you. The source code for SendSMTPMail is reproduced below.

Note that prior to v9, if there was no user defined handler, an external executable (sendEmail) was used. SendEmail(.exe) is no longer installed with MoneyWorks 9, so if you have scripts that rely on it, you may need to update them.

Automatically called: When a message needs to be sent via SMTP

Use for: Customising SMTP sending behaviour

Availability: MoneyWorks Gold 8.1.7 and later.

Below is the source of the built in handler (in MoneyWorks 9.0.2). You can copy/paste this into your document as a starting point, but to have it override the built-in handlers, you should remove the leading underscores from __SendSMTPMail and __SendSMTPMail_WithCredentials.

constant meta = "MoneyWorks Built-in MWScript handler library"

/********************************************************************************

    When MoneyWorks has an email + attachment to send and the preferences are
    set to send via SMTP...
    
    IF there is a script handler named SendSMTPMail, then that handler 
    will be called to send the email.
    
    OTHERWISE, Built_In:__SendSMTPMail will be called
    
    Your SendSMTPMail handler MAY call Built_In:__SendSMTPMail or
    Built_In:__SendSMTPMail_WithCredentials
    
    handler signature:
    ------------------------------------------------------------------------------
    SendSMTPMail(recipients, attachmentPath, subject, message, attachmentFileName)
    ------------------------------------------------------------------------------

    - recipients may be a single recipient or a comma-delimited list
    - attachmentPath is the posix or windows path to the file to be attached
    - subject is the email subject
    - message is the plain text message; may be blank
    - attachmentFileName is a suggested attachment name which may differ from the
        actual filename of the attachmentPath (e.g. "Invoice 1234.pdf")


**********************************************************************************/

property TLS_Enabled = true

// Build the email message in this proprty

property payload

// Use Preload_Built_In:SetVerbose(1) to get CURL logging output in the log file to debug connections

property verbose = 0

// Callback to deliver payload to libcurl; don't return more than the requested number of bytes

property pl_offset

// Callback to deliver payload to libcurl; don't return more than the requested number of bytes

on Read_Payload_Callback(bytes, data)
    let thispayload = Mid(payload, pl_offset + 1, bytes, IN_BYTES)
    let pl_offset = pl_offset + bytes
    return thispayload
end

on Curl_WriteHandler(bytes, data)
    
end

// Break lines between words

on GetLineFromMessage(m)
    // Try to break at <= 78 characters (although RFC 821 allows up to 1000, it recommends 78 + \r\n)
    let line = Left(m, 78)
    let nl = Position(line, "\r")
    if nl == 0
        let nl = Position(line, "\n")
    endif
    if nl <> 0
        return Left(line, nl - 1) + " " // include space to indicate that the linebreak is a soft one
    endif
    if Length(line) == 78
        while Right(line, 1) <> " " and Position(line, " ") > 0
            let line = Left(line, Length(line) - 1)
        endwhile
    endif
    return line
end

on AppendPayload(line)
    let payload = payload + line + "\r\n"
end

on CleanEmail(ad)
    let lt = PositionInText(ad, "<")
    let gt = PositionInText(ad, ">")
    if lt and gt and gt > lt
        return Trim(Mid(ad, lt + 1, gt - lt - 1), true)
    endif
    return trim(ad, true)
end

on AttachFileWithPath(path, attachmentName, MIME_bound)
    let content_type = "application/octet-stream"
    if Right(path, 4) = ".pdf"
        let content_type = "application/pdf"
    elseif Right(path, 4) = ".jpg"
        let content_type = "image/jpeg"
    elseif Right(path, 4) = ".png"
        let content_type = "image/png"
    endif
    AppendPayload("--" + MIME_bound)
    if attachmentName != ""
        AppendPayload("Content-Type: " + content_type + "; name=\"" + attachmentName + "\"")
    else
        AppendPayload("Content-Type: " + content_type)
    endif
    AppendPayload("Content-Transfer-Encoding: base64")
    AppendPayload("")
    let fd = File_Open(path)
    let b64 = Base64Encode(File_Read(fd), true)
    File_Close(fd)

    AppendPayload(b64)
end

on __SendSMTPMail_WithCredentials(to, path, subject, message_text, attachmentName, server, replyTo, authUser, authPass, bearer) public

    let res = ""
        
    /*
     *    Mail headers

     */
     
//    let to = CleanEmail(to) to may contain multiple comma-delimited addresses
     
    let payload = ""
    AppendPayload("Date: " + DateToText(Time(), DateFormRFC2822))
    // send To: all recipients. Could sort them into To:, CC:, BCC: if you want, using some criteria
    // (maybe if the address is prefixed with cc: or bcc:, parse that)
    AppendPayload("To: " + to)
    AppendPayload("From: " + replyTo)
    AppendPayload("Reply-To: " + CleanEmail(replyTo))
    AppendPayload("Message-ID: <" + MakeGUID() + "@" + server + ">")
    AppendPayload("Subject: " + subject)
    AppendPayload("User-Agent: MoneyWorks/"+version+"_SendSMTPMail_libcurl")
    
    /*
     *    MIME header

     */

    AppendPayload("MIME-Version: 1.0")
    let MIME_bound = "MW-"+MakeGUID()
    AppendPayload("Content-Type: multipart/mixed; boundary=" + MIME_bound)


    AppendPayload("")
    AppendPayload("This is a message with multiple parts in MIME format.")
    AppendPayload("--" + MIME_bound)
    
    /*
     *    Add the text/plain MIME section with the message

     */
     
    AppendPayload("Content-Transfer-Encoding: quoted-printable")
    AppendPayload("Content-Type: text/plain; charset=utf-8; format=fixed")
    AppendPayload("")
    
    // linebreak the message (chunk_split() fn would be nice)
    
    let mtext = message_text
    while length(mtext)
        let a_line = GetLineFromMessage(mtext)

        // convert the line to quoted-printable
        // passing = as delim to URLEncode will only encode non-printable-ASCII and =
        AppendPayload(URLEncode(a_line, "="))
        
        let mtext = Right(mtext, Length(mtext) - Length(a_line))
    endwhile
    AppendPayload(" ")
    
    //syslog(payload)

    /*
     *    Add the attachment, with the suggested attachment name

     */

    if TypeOf(path) == TypeText and path != "" and TypeOf(attachmentName) == TypeText
        AttachFileWithPath(path, attachmentName, MIME_bound)
    elseif TypeOf(path) == TypeArray    // array of ["Attachment Name"] = "filepath"
        foreach k in array path
            let att_name = ""
            if TypeOf(attachmentName) = TypeArray and ElementExists(attachmentName, k)
                let att_name = attachmentName[k]
            endif
            AttachFileWithPath(path[k], att_name, MIME_bound)
        endfor
    endif

    /*
     *    MIME end marker

     */

    AppendPayload("--" + MIME_bound + "--")
    
    /*
     *    Now use CURL to send...

     */
    
    
    let curl = curl_init()
    if PositionInText(server, ":465")
        curl_setopt(curl, CURLOPT_URL, "smtps://" + server)
    else
        curl_setopt(curl, CURLOPT_URL, "smtp://" + server)
    endif
    if TLS_Enabled
        curl_setopt(curl, CURLOPT_USE_SSL, 1)
    else
        curl_setopt(curl, CURLOPT_USE_SSL, 0)
    endif
    curl_setopt(curl, CURLOPT_VERBOSE, verbose)
     
    if authUser != ""
        curl_setopt(curl, CURLOPT_USERNAME, authUser)
        if TypeOf(bearer) = TypeText and bearer != ""
            curl_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BEARER);
            curl_setopt(curl, CURLOPT_XOAUTH2_BEARER, bearer);
        else 
            curl_setopt(curl, CURLOPT_PASSWORD, authPass)
        endif
    endif


    /* Note that this option isn't strictly required, omitting it will result
     * in libcurl sending the MAIL FROM command with empty sender data. All
     * autoresponses should have an empty reverse-path, and should be directed
     * to the address in the reverse-path which triggered them. Otherwise,
     * they could cause an endless loop. See RFC 5321 Section 4.5.5 for more
     * details.

     */
     
    curl_setopt(curl, CURLOPT_MAIL_FROM, CleanEmail(replyTo))
 
    /* Iterate over comma-separated  recipient list (an explode() fn would be nice)
     * It is irrelevant which are To, CC, BCC — that cosmetic information is provided in headers.

     */ 
     
    let recipients = CreateArray()
    let i = 0
    if verbose
        syslog("recipients text: " + to)
    endif
    
    foreach rcpt in text to        // 
        // The recipient must be a pure email address; extract that if we have "Name <addr>" format
        // (the foreach in text will already do an implicit trim of whitespace)
        let r = CleanEmail(rcpt)
        if r <> ""
            let recipients[i] = r
            let i = i + 1
            if verbose
                syslog("Adding recipient: " + r)
            endif
        endif
    endfor
    
    if CountElements(recipients) = 0 
        syslog("[ERROR] no recipients for message")
        return
    endif

    if GetAppPreference("bccToSelf")
        let recipients[i] = CleanEmail(GetAppPreference("replyTo"))
        syslog("added bcc to " + recipients[i])
    endif

    curl_setopt(curl, CURLOPT_MAIL_RCPT, recipients)
    
 
    /* We're using a callback function to specify the payload (the headers and
     * body of the message). You could just use the CURLOPT_READDATA option to

     * specify a FILE pointer to read from. */ 
    curl_setopt(curl, CURLOPT_READFUNCTION, "Read_Payload_Callback")
    curl_setopt(curl, CURLOPT_UPLOAD, 1)
    curl_setopt(curl, CURLOPT_WRITEFUNCTION, "Curl_WriteHandler")    // Having this gets us a numeric curlcode as a result from curl_exec
     
     if TLS_Enabled
        curl_setopt(curl, CURLOPT_SSL_OPTIONS, 2)    // = CURLSSLOPT_NO_REVOKE Windows needs this for gmail
     endif
     
     let pl_offset = 0

    /* Send the message */ 
    let res = curl_exec(curl);
    curl_close(curl);
    
     // the text "[INFO] Email was sent successfully" MUST be logged for MW to consider the operation succeeded
    if(res == 0)
        syslog("[INFO] Email was sent successfully to " + to)
    else
        syslog("[ERROR] " + curl_strerror(res))
    endif

end

constant fNoOverrideReplyTo = #0004

on OverrideReplyTo(reply)
    if Initials <> ""
        let usersettings = Find("Login.Email+`\t`+flags", "initials=`"+Initials+"`", 1)
        if usersettings <> ""
            let f = TextToNum(Slice(usersettings, 2, "\t"))
            if TestFlags(f, fNoOverrideReplyTo) = 0
                let r = Slice(usersettings, 1, "\t")
                if r <> ""
                    let reply = r
                endif
            endif
        endif
    endif
    return reply
end

uses MW_SMTP_OAuth:GetBearerToken

on __SendSMTPMail(to, path, subject, message_text, attachmentName) public
        
    /*
     *    Get the server settings from preferences;
     *
     *    These could be overridden (in particular the From: might want to be overridden with the logged-in user,
     *    NOTE that the SMTP server must accept email from that user; most servers won't accept
     *    email from addresses in other domains. There is no magical way to change that.)

     */

    let bearerToken = ""
    let authPass = ""
    let server = GetAppPreference("smtp")
    let replyTo = GetAppPreference("replyTo")
    if GetAppPreference("useAuth") = 1
        let authUser = GetAppPreference("smtpUser")
        let authPass = GetAppPreference("smtpPass")
    elseif GetAppPreference("useAuth") = 2
        let authUser = GetAppPreference("smtpUser")
        let bearerToken = MW_SMTP_OAuth:GetBearerToken()
    else
        let authUser = ""
        let authPass = ""
    endif

    let replyTo = OverrideReplyTo(replyTo)

    __SendSMTPMail_WithCredentials(to, path, subject, message_text, attachmentName, server, replyTo, authUser, authPass, bearerToken)
    
end

on SetVerbose(v) public
    let verbose = v
end

on SetSMTPEnableTLS(b) public
    let TLS_Enabled = b
end
Posted in MWScript, Sample Code | Comments Off on SendSMTPMail

MoneyWorks Now login gateway API

If you are implementing a service that will access a MoneyWorks database via its REST interface hosted on a MoneyWorks Now server, you can retrieve the necessary login credentials by querying the MoneyWorks Now login gateway.

A MoneyWorks Datacentre REST query requires Authorization headers for the realms "Datacentre" and "Document", as outlined in http://cognito.co.nz/developer/moneyworks-datacentre-rest-api/ and http://cognito.co.nz/developer/accessing-restserver-from-php/.

That is you need a folder username and password (this is the Datacentre realm), a document name, and a document username and password. You also need to know the domain name of the host on which the document resides. It is possible that the host for a database may change (if for example it is relocated to a server that is nearer its users). Such a relocation is transparent to normal MoneyWorks Now users, because MoneyWorks looks up the login credentials from the MoneyWorks Now gateway every time a user logs in. If you are implementing a service that interfaces to a MoneyWorks database, then you will need to do this lookup and cache the results.

Setting up access

The owner of the document should set up a username in the document and grant it access via MoneyWorks Now. If you are implementing a service, ideally the MoneyWorks Now account used for this should be one that is specific to your service. For example, let us imagine that you are providing a bank statement feed service into the document owned by Acme Widgets Ltd. You should create a MoneyWorks Now account called say "feedservice_acme@yourcompany.com". The document owner (Acme Widgets) would set up a user in their document called "Bank Feed", and assign login privileges to your MoneyWorks Now account. The user in the document will be assigned a password (usually randomly generated). The document will also be located within Acme's hosting account which will have a folder name and associated password. A mobile app, on the other hand, will probably log in using the user's own regular MoneyWorks Now login.

All you need to know in order to log in to their document is the MoneyWorks Now account name and its password.

The login gateway is located at

https://api.moneyworks.net.nz/mwnow/get_docs.php

To query the gateway, make a POST request with parameters "user" and "pass".

e.g.

curl --data "user=feedservice_acme@yourcompany.com&pass=secret" "https://api.moneyworks.net.nz/mwnow/get_docs.php"

The return from the server will be UTF8 text in much the same format as an HTML header. i.e lines consisting of a data field name followed by a colon followed by one space followed by a value and terminated by a CRLF.

status: ok
count: 1
host_1: mwnow2.moneyworks.net.nz
port_1: 6710
folderuser_1: MWNow/Test3
folderpass_1: F7%6P%B5XAVW8X+ZXBUU
docname_1: Acme3.mwd7
company_1: Acme Widget Ltd
docuser_1: Bank Feed
docpass_1: 98427ndupefhulkdaIG
etc...

The status will be "ok" on success, otherwise "fail".

Make no assumptions about the ordering of the rows. There may also be additional data field rows not documented here which should be ignored.

If there are multiple document logins associated with the account, the response may contain multiple entries with a count greater than 1 (the next entry would be host_2, folderuser_2, etc. The number of entries is denoted by the count field. If there are multiple entries, you will need to select the entry for the docname that you intend to log in to.

Alternatively, you may add &format=json to the post params to get JSON formatted results

"status" : "ok",
"docs" : [
    { 
       "host" : "mwnow2.moneyworks.net.nz",
       "port" : 6710,
       "folderuser" : "MWNow/Test3"
       "folderpass" : "F7%6P%B5XAVW8X+ZXBUU"
       "docname" : "Acme3.mwd7"
       "company" : "Acme Widget Ltd"
       "docuser" : "Bank Feed"
       "docpass" : "98427ndupefhulkdaIG"
    }, 
    {  
       "host" : "mwnow3.moneyworks.net.nz",
       "port" : 6710
       "folderuser" : "ABC"
        etc
    }
    ...
    ]

The folderuser and folderpass are the Datacentre realm credentials. The docuser and docpass are the Document realm credentials. The docname is the document name to use in the request url. And of course the host is the server to which you should address your REST requests.

It will be rare for the address and credentials to change, so it is not necessary to look them up every time you need to make a request of the REST API. You should cache the credentials. The login server may apply strict rate limiting to requests, so do not depend on it to respond to multiple requests for the same account in quick succession.

In the event of status = fail, there will be a reason.

e.g.

status: fail
reason: Incorrect username or password

or

status: fail
reason: Retrying too soon after previous login attempt
Posted in Uncategorized | Comments Off on MoneyWorks Now login gateway API

FileMaker Plug-in and -1703 errors on Mojave

If you are using the MoneyWorks-FileMaker plug-in in Mojave you may be getting unexpected -1703 errors when using the MWKS_ImportFile script step. The following description explains the problem and provides the solution:

My FileMaker script exports the transaction data to a text file that the MoneyWorks plugin then immediatly imports into Moneyworks. It worked just fine when I manually imported the text file into MW. It also worked when I manually stepped through the FileMaker script.
This led me to believe the text file wasn't yet available to the plugin in time to import it into MW. I added a small delay between the data export and the MW plugin call and that solved the problem.

Adding a Pause of 1 second after the export and before attempting to import the file resolves the issue.

Posted in FileMaker | Comments Off on FileMaker Plug-in and -1703 errors on Mojave

Custom edit field special properties

MoneyWorks Gold 8 allows you to create new windows that can be used with scripts. The UI form editor allows you to set the type (text/number/date) and numeric format of an edit field, but there are some other useful modes you can obtain by using metacharacter sequences in the initial text for the field. A metacharacter sequence is enclosed in angle brackets and will be removed from the initial text that the field is instantiated with. It must appear at the start of the initial text.

<LEN=99999> Set the length limit for the field (the default length limit is 255 bytes).
<PASS> Password mode (typed characters are hidden)
<ARTN> Allow returns (newlines) in the field (with Option/Ctrl) held down
<AR!O> Allow returns (newlines) in the field without a modifier key having to be used
<SBAR> A scrollable edit field. Implies ARTN
<SBA2> A scrollable edit field that uses the Scintilla text editing component and allows internal tab characters to be typed. Also implies AR!O and allows unlimited text length. Does not send ItemHit messages during typing.

Where it makes sense, you can combine the metacharacter sequences, e.g. <TIMELEN=5>

In v8.1 you can also use

<TIME> Defer ItemHit messages until the user has paused typing. Use when your ItemHit handler might take more than a few milliseconds to run.

Posted in MWScript | Comments Off on Custom edit field special properties

Python script for MoneyWorks

A third-party Python script is available to facilitate development of services that use the MoneyWorks REST APIs to push data to MoneyWorks, or to automate MoneyWorks processes from outside of MoneyWorks.

Examples:

from moneyworks import Moneyworks
mw = Moneyworks()

# Print the version of Moneyworks we are talking to
print mw.version()

# Get the email address of the first person in a company, selected by code
print mw.get_email("ACME")

# Get a PDF document (which you can save or email) of a transaction
pdf = mw.print_transaction('sequencenumber=`9999`', 'my_invoice')

# Export some data for all companies with code starting with 'A' and print their names
my_data = mw.export("name", "left(code, 1)=`A`")
for contact in my_data:
  print contact['name']

Note: In general, if you want to initiate something from within MoneyWorks, use mwScript. The REST APIs are for when actions are initiated by some other systems (such as a sale on a web store).

For more details and to download the script resources, go to https://github.com/ari/moneyworks-cli

Posted in REST | Comments Off on Python script for MoneyWorks

Interfacing with a mail client on Windows

By default. MoneyWorks uses SimpleMAPI to create a new email message in the system mail client (assuming the default system mail client supports SimpleMAPI).

The drawback with SimpleMAPI is that it is modal (you must send or discard each message before you can continue), and there is no signature support.

From v7.3.7, the Windows version of MoneyWorks has supported using an external script for mail client interfacing. A script for interfacing to Microsoft Outlook is built-in and used automatically, but you can also write your own script if you need to interface to another mail client that supports COM/Scripting.

MoneyWorks will look for a script named mail_client_helper.vbs in the Standard Plugins Scripts folder. If this script is found, it will be run with the following parameters: mail_client_helper.vbs "attachmentpath" "recipients" "subject" "message"

The following script will create a new email retaining your default signature. It does this by grabbing the content of the new empty email (which contains the signature) then re-adding that content after attaching the enclosure (which obliterates the signature as a side effect).

' Usage mail_client_helper "attachmentpath"  "recip1" "subject" "message"
Set outlook = createobject("Outlook.Application")
Set message = outlook.createitem(olMailItem)
With message
	.Subject = WScript.Arguments.Item(2)
	.Display
	signature = .htmlbody
	.Recipients.Add WScript.Arguments.Item(1)  ' assume only one recipient
	.Attachments.Add WScript.Arguments.Item(0)
  .htmlbody = WScript.Arguments.Item(3) & vbNewLine & signature
End With
Posted in Uncategorized | Comments Off on Interfacing with a mail client on Windows

Optimising a report

Some time ago I had the need to find a pair of transactions that added or subtracted to a particular number. In fact when you are chasing down a problem in a set of accounts this is not an uncommon thing to want to do.

So I wrote a report to do it. The report would load every transaction amount into a table, and then test every possible combination of two transactions to see if they added or subtracted to the number in question, then print the matches.

Since the report was quite compute-intensive, it was a candidate for optimising by rewriting the loading and searching of the amounts in MWScript. The algorithm is exactly the same, but since MWScript is compiled, it can run the algorithm faster than the report interpreter. It turned out to be 7x faster.

Here's the original report:
Screen Shot 2015-08-17 at 3.58.41 pm

The logic for this can be rewritten using an MWScript function in the report's custom controls:

on Exhaustive(sel, Look_For) public
	let seqs = CreateTable()
	let results = ""
	syslog("loading...")
	foreach t in transaction sel
		TableAccumulate(seqs, t.sequencenumber, t.gross)
	endfor
	syslog("searching...")
	foreach i in (1, TableGet(seqs))
		foreach J in (1, TableGet(seqs))
			if TableGet(seqs, i, 1)  + TableGet(seqs, j, 1)  = Look_For or TableGet(seqs, i, 1)  - TableGet(seqs, j, 1)  = Look_For
				let results = results + TableGet(seqs, i, 0) + "\t" + TableGet(seqs, j, 0) + "\n"
			endif
		endfor
	endfor
	return results
end

Here's the report containing both implementations (it's set to always use the faster implementation, but if you change the Faster control to a checkbox you can choose to run the old implementation).

If there is a highlighted selection of transactions, it will only search those.

Find Pairs.crep

Posted in Uncategorized | Comments Off on Optimising a report

GetLastErrorMessage

GetLastErrorMessage()

Retrieves the last logged error message.
When a script gets an error from the Import() function, you can use GetLastErrorMessage() to get more information about the last error that occurred.

Important: The last error message will only be updated when an error is logged to the log file. Do not rely on GetLastErrorMessage() to tell if the last thing you did resulted in an error. It reports on the last error that was logged, not the success/failure of the last operation that was performed.

Availability
MoneyWorks v7.3 and later

Posted in Uncategorized | Comments Off on GetLastErrorMessage

Importing over high latency network

Ideally, importing to a cloud server would always be done via cloud APIs (i.e. the REST API provided by MoneyWorks Datacentre). However, one of the advantages of MoneyWorks over browser-based cloud solutions is that you can integrate seamlessly with other desktop apps, and this integration typically involves importing via the desktop client, which has been subject to latency issues.

Solution

As of MoneyWorks Datacentre 7.1.8, importing data via the MoneyWorks Gold client by almost any means (Import command, Paste from clipboard, drag and drop, Applescript, COM, Filemaker plugin; tabular or xml formatted data) will proceed on the server if the network connection is high latency (generally more than a couple of milliseconds, so you will also see this behaviour on most wifi networks).

What happens

  • The entire data or datafile to import is first uploaded to the server (it's compressed first), along with the import map supplied (if any).
  • The import is then done by a worker process on the server (using the CLI import command).
  • Any error messages or results are returned by the server.

The result

Importing to a server over a high latency network should generally be much faster, and also use less network bandwidth.

The only other noticeable change is that the first "checking pass" in the manual import/pasting case is not seen by the user. If the data is good to import, it will be imported. The progress bar will also be indeterminate, with any error or success messages not shown until completion.

Controlling serverside importing

Normally the serverside importing mode is selected automatically depending on the detected latency of the network connection, but you may wish to control the mode manually. For this purpose, there is a hidden preference option named remoteImportMode. You can use defaults write or RegEdit to set this.

remoteImportMode = 0 — (default) automatic based on latency
remoteImportMode = 1 — force server side importing even on low latency network
remoteImportMode = 2 — force client side importing (will run as for 7.1.7 and earlier).

If you experience problems with the new importing, please try

defaults write nz.co.cognito.MoneyWorks.Gold remoteImportMode 2

To test your import scripts with server side importing

defaults write nz.co.cognito.MoneyWorks.Gold remoteImportMode 1

To reset to default behaviour

defaults write nz.co.cognito.MoneyWorks.Gold remoteImportMode 0

On Windows, use the Registry Editor to set the valu in the key:

HKEY_CURRENT_USER\Software\Cognito\MoneyWorks Gold\Preferences\remoteImportMode

NOTE: If you're executing the CLI on Mac using the symlink at /usr/bin/moneyworks instead of the full path to the actual executable, substitiute moneyworks for nz.co.cognito.MoneyWorks.Gold in the above commands.

Posted in Uncategorized | Comments Off on Importing over high latency network