Function: Example - Calculate Invoice Lines using SQL

In FileMaker, scripting applies to the current layout in the relationship graph. For an Invoice, the layout position is for the Invoice table, and the InvoiceLines are in a related table shown in a portal on the invoice layout. Calculations on the invoice line can be done as calculated fields or as calculations done when lines change. In this solution (a real example), three sets of sum fields were used on the invoice line. One set was calculated fields, one set was indexed fields, and the last set was calculations that either picked values from the calculated field or the indexed field depending on the invoice approval state. The indexed fields had to be updated in the approval routine. As FileMaker's function for this had some issues, this ACF function was created to perform the calculations on the fields instead.

The ACF Functions:

package InvoiceCalculations "Functions to do invoice calculations"; 

// Allow us to check the version of the routines and reload if too old or not installed. 
function InvCalc_Version ()
    return 12.0;
end

// Decide what fields need to be updated and create SQL assignments for the update part. 
// Output on inconsistency: <field>=<new value>
function checkConsistency (float dbval, float calcval, string FieldName)
    if (dbVal == calcval) then
        return ""; 
    else
        return format ("%s=%f", FieldName, calcval);
    end if
end 

// Do the calculation for all invoice lines and update those that need an update. 

function ApproveInvoiceLinesCalc (string invoiceNumber)
    // Get a resultset of all the invoice lines for the invoice. 
    int lq = SQL_query ("SELECT Auto_nr, Antall, Kostpris, Salgspris_u_MVA, MVA_kode, MVA_prosent, Rabatt_belop, Rabatt_prosent, in_Linjesum_avgiftspliktig, in_Linjesum_avgiftsfritt, in_Linjesum_u_MVA, in_Linjesum_kostpris, in_Linjesum_fritt_og_pliktig, in_Linjesum_rabatt, in_Linjesum_m_MVA, in_Linjesum_MVA, in_Linjesum_DG, in_Linjesum_DB FROM db_InvoiceDescriptions WHERE Faktura_nr=" + invoiceNumber); 

    // Declarations
    float Linjesum_avgiftspliktig, Linjesum_avgiftsfritt, Linjesum_u_MVA, Linjesum_kostpris, Linjesum_fritt_og_pliktig, Linjesum_rabatt, Linjesum_m_MVA, Linjesum_MVA, Linjesum_DG, Linjesum_DB;
    float line_net, linemva, mvapro, rabpro, rabbel, line_kostpris, line_db, line_dg; 

    int num_rows = SQL_getRowCount (lq); 

    print format ("Number of Invoice Lines: %d\n", num_rows); 

    int i, ll; 
    string res, upds; 
    array string row, upd, empty; 

    // Loop through the invoice lines
    for (i = 1, num_rows, 1)
        // The SQL_getRow returns an array, allowing us to access each column in the recordset individually. 
        // The first parameter is the handle, and the second is the row number, starting with "0". 
        
        row = SQL_getRow (lq, i-1); 
        // print ("Columns: " + string (sizeof (row)) + " : " + row[1]);
        // As all columns are text from the SQL, we need to use the float function to convert to float (actually double)
        Linjesum_avgiftspliktig = float (row[9]);
        Linjesum_avgiftsfritt = float (row[10]);
        Linjesum_u_MVA = float (row[11]);
        Linjesum_kostpris = float (row[12]);
        Linjesum_fritt_og_pliktig = float (row[13]);
        Linjesum_rabatt = float (row[14]);
        Linjesum_m_MVA = float (row[15]);
        Linjesum_MVA = float (row[16]);
        Linjesum_DG = float (row[17]);
        Linjesum_DB = float (row[18]);
        mvapro = float (row[6]);
        
        line_net = float (row[2]) * float (row[4]);
        if (row[8] == "") then
            rabbel = float (row[7]); 
        else
            rabbel = line_net * float(row[8]) / 100.0;
        end if
        line_net = line_net - rabbel;
        
        upd = empty; // Empty array
        line_kostpris = float (row[3]) * float (row[2]);
        line_db = line_net - line_kostpris; 
        
        // Calculate linjesum-DG
        if (line_net == 0 && line_db == 0) then 
            line_dg = 0; 
        elseif (line_db < 0 && line_net == 0) then 
            line_dg = -100; 
        elseif (line_db < 0) then
            line_dg = abs(line_db) * 100 / line_net * -1;
        elseif (line_net == 0) then
            line_dg = 100; 
        else     
            line_dg = line_db * 100 / line_net; 
        end if

        // Check consistency between db-values and our calculations. 
        // The checkConsistency functions is defined near the top. 
        if (mvapro == 0) then
            upd[] = checkConsistency (Linjesum_avgiftspliktig, 0, "in_Linjesum_avgiftspliktig"); 
            upd[] = checkConsistency (Linjesum_avgiftsfritt, line_net, "in_Linjesum_avgiftsfritt"); 
            upd[] = checkConsistency (Linjesum_u_MVA, line_net, "in_Linjesum_u_MVA"); 
            upd[] = checkConsistency (Linjesum_fritt_og_pliktig, line_net, "in_Linjesum_fritt_og_pliktig"); 
            upd[] = checkConsistency (Linjesum_MVA, 0.0, "in_Linjesum_MVA");
            upd[] = checkConsistency (Linjesum_m_MVA, line_net, "in_Linjesum_m_MVA");
        else
            linemva = line_net * mvapro / 100; 
            upd[] = checkConsistency (Linjesum_avgiftspliktig, line_net, "in_Linjesum_avgiftspliktig"); 
            upd[] = checkConsistency (Linjesum_avgiftsfritt, 0, "in_Linjesum_avgiftsfritt"); 
            upd[] = checkConsistency (Linjesum_u_MVA, line_net, "in_Linjesum_u_MVA"); 
            upd[] = checkConsistency (Linjesum_fritt_og_pliktig, line_net, "in_Linjesum_fritt_og_pliktig"); 
            upd[] = checkConsistency (Linjesum_MVA, linemva, "in_Linjesum_MVA");
            upd[] = checkConsistency (Linjesum_m_MVA, line_net + linemva, "in_Linjesum_m_MVA");
        end if  
        upd[] = checkConsistency (Linjesum_kostpris, line_kostpris, "in_Linjesum_kostpris");
        upd[] = checkConsistency (Linjesum_DB, line_db, "in_Linjesum_DB");
        upd[] = checkConsistency (Linjesum_DG, line_dg, "in_Linjesum_DG");
         
        // Now we have the "upd" array, containing assignments for update, or empty if the field does not need an update. 
        // Implode creates a comma-separated string with potential to have excessive commas in it because of the empty array rows.
        // Get rid of the excessive commas. 
        upds = implode (",", upd); 
        upds = substitute (upds, ",,", ","); // We need to do this Several times, since
        upds = substitute (upds, ",,", ","); // ,,,, => ,,
        upds = substitute (upds, ",,", ","); // ,, => , and so on. 
        upds = substitute (upds, ",,", ","); 
        upds = substitute (upds, ",,", ","); 
        
        // There might be a comma at the end; we need to get rid of this. 
        // If the last empty rows end up with a single comma. 
        ll = length(upds);
        if (ll != 0) then
            if (right (upds, 1) == ",") then
                upds = left (upds, ll-1);
            end if
        end if

        // Anything to do? Run the Update SQL. "upds" now contains a comma-separated list of assignments. 
        if (upds != "") then    
            // Craft the SQL
            upds = format ("UPDATE db_InvoiceDescriptions SET %s WHERE Auto_nr=%s", upds, row[1]); 
            // Run it. 
            res = ExecuteSQL (upds); // Execute the UPDATE;  
            print (upds + "\n" + res + "\n"); // For debugging, see the console. 
            if (res != "") then // Non-blank result indicates an SQL error. 
                SQL_close (lq); 
                return res; 
            end if
        end if
    end for
    SQL_close (lq); 
    return "OK";
end

In the approval script, we now just call this, instead of the old function that was buggy.

    // In the FileMaker Script to approve the invoice. 
    Set Variable [ $res; ACF_Run("ApproveInvoiceLinesCalc"; InvoiceHeader::InvoiceNo)]
    if ( $res = "OK")
        // Everything went OK
    else
        // Handle the error. 
    end if

This implementation handled the unstability issue 100%.

References: