ACF Library

GenerateExcelBudget

Back to List

Description: Main function to produce the Excel budget shown in the demo app for cash flow budgets.

FileMaker Prototype:

Set Variable [$res; ACF_Run("GenerateExcelBudget"; string_BudgetKey)]

Category: EXCEL

Dependencies

NOTE: This function serves as a reference template. To use it effectively, you may need to adjust certain parts of the code, such as field names, database structures, and specific data lists to fit the requirements of your system. This customization allows the function to work seamlessly within your environment.

Function source:

/*

   Main routine - Called from a button in the Budget layout. 
   Produce an Excel Spreadsheet for the Cash-Flow budget. 
   A summary tab first, and then month tabs for the needed amount of tabs to cover all months 
   between start and end dates in the budget record. 
   
*/


function GenerateExcelBudget(string BudgetKey)
    // Declare arrays to hold data from the SQL query
   
    ARRAY STRING budTitle;
    ARRAY STRING budSubtitle;
    ARRAY DATE budFromDate;
    ARRAY DATE budToDate;
    ARRAY STRING budExcelPath;

    ARRAY STRING traPrimaryKey;
    ARRAY STRING traBudget_fk;
    ARRAY STRING traName;
    ARRAY DATE traDateStart;
    ARRAY STRING traIncomeExpense;
    ARRAY STRING traPeriodType;
    ARRAY INT traPeriodCount;
    ARRAY FLOAT traAmount;
    ARRAY STRING traCategory;

    // SQL Query to fetch budget and transaction data
    string sql = "SELECT bud.Title, bud.Subtitle, bud.FromDate, bud.ToDate, bud.ExcelPath,
    tra.PrimaryKey, tra.Budget_fk, tra.Name, tra.DateStart, tra.IncomeExpense, tra.PeriodType,
    tra.PeriodCount, tra.Amount, tra.Category
    FROM Budget AS bud
    INNER JOIN Transactions AS tra ON bud.PrimaryKey = tra.Budget_fk
    WHERE bud.PrimaryKey = :BudgetKey
   ORDER BY tra.IncomeExpense DESC, tra.Category ASC, tra.Name ASC
    INTO :budTitle, :budSubtitle, :budFromDate, :budToDate, :budExcelPath, 
    :traPrimaryKey, :traBudget_fk, :traName, :traDateStart, :traIncomeExpense, :traPeriodType, 
    :traPeriodCount, :traAmount, :traCategory";

    // Execute the SQL query
    string res = ExecuteSQL(sql);
    int ctrans = sizeof(budExcelPath);
    if (res != "OK" || ctrans == 0) then
        return "No transactions: " + res; // Return if no transactions found
    end if
   
   // Have JSON copy of the data for future updates to the sheet. 
   // Used to compare new data, old data and data from the sheet to see what is new, what is
   // user changes to the sheet, and what is the same. 
   JSON js; 
   js["budTitle"] = budTitle[1]; 
   js["budSubtitle"] = budSubtitle[1];
   js["budFromDate"] = budFromDate[1];
   js["budToDate"] = budToDate[1];
   js["budExcelPath"] = budExcelPath[1];
   js["BudgetKey"] = BudgetKey;
   
   js["traPrimaryKey"] = traPrimaryKey;
   js["traName"] = traName;
   js["traDateStart"] = traDateStart;
   js["traIncomeExpense"] = traIncomeExpense;
   js["traPeriodType"] = traPeriodType;
   js["traPeriodCount"] = traPeriodCount;
   js["traAmount"] = traAmount;
   js["traCategory"] = traCategory;
   
   
   // return "Debug"; 

    // Extract date range
    DATE fromDate = budFromDate[1];
    DATE toDate = budToDate[1];
    int mndFrom = int(string(fromDate, "%m"));
    int mndTo = int(string(toDate, "%m"));
    int YearFrom = int(string(fromDate, "%Y"));
    int YearTo = int(string(toDate, "%Y"));

    ARRAY STRING mndNames = {"January", "February", "March", "April", "May", "June", 
    "July", "August", "September", "October", "November", "December"};

    // Create the Excel workbook
    if (budExcelPath[1] == "") then
        return "No Path";
    end if
   $$WaituSec = 0.0; // Calculate the microseconds spent on the confirm dialogue (User answer wait)
   long uSecStart, uSecEnd; 
    if (file_exists(budExcelPath[1])) then
      uSecStart = uSec(); 
      if (confirm ("Update the file: " + budExcelPath[1])) then
         uSecEnd = uSec(); 
         $$WaituSec = uSecEnd-uSecStart; 
              return UpdateExcelBudget(BudgetKey);
      else
         uSecEnd = uSec(); 
         $$WaituSec = uSecEnd-uSecStart; 
         return "Update canceled"; 
      end if
    end if
   
   // Update jsCopy so we have some values to compare next time we update the Workbook
   string jsCopy = string (js); 
   res = ExecuteSQL ( "UPDATE Budget SET jsCopy = :jsCopy WHERE PrimaryKey = :BudgetKey"); 
   
    int wb = Excel_create(budExcelPath[1], "Summary");
    int s, sumary = Excel_getSheetID(wb, 1);
    res = DefineStyles(wb);
    int noTabs = 0;
    int i, yy, mm, immf, immt, dim, dow, crow, position, sec1row, sec2row;
    DATE fim, lim, cdate;
    string chStyle, formel, PrevSheetRef, sheetName;

    // Initialize the summary sheet
    excel_setColumnWidth(sumary, 1, 20.0);
    Excel_SetCell(sumary, 1, 1, "Summary sheet: " + budTitle[1], "H1");
    Excel_SetCell(sumary, 2, 1, budSubtitle[1] + " " + string(fromDate) + " - " + string(toDate), "H2");

    // Add starting balance
    Excel_SetCell(sumary, 3, 1, "Balance-in:", "CH");
    Excel_SetCell(sumary, 3, 2, 0.0, "SumLine");

    // Add month headings to summary sheet
    int scol = 2;
    for (yy = YearFrom, YearTo)
        immf = ((yy == YearFrom) ? mndFrom : 1);
        immt = ((yy == YearTo) ? mndTo : 12);
        for (mm = immf, immt)
            cdate = date(format("%04d-%02d-%02d", yy, mm, 1), "%Y-%m-%d");
            Excel_setCell(sumary, 5, scol, cdate, "SCH");
            scol++;
        end for
    end for

    // Add summary sheet titles for balance, income, and expenses
    Excel_SetCell(sumary, 6, 1, "MAX balance:", "CH");
    Excel_SetCell(sumary, 7, 1, "MIN balance:", "CH");
    Excel_SetCell(sumary, 8, 1, "Estimated Income:", "CH");
    Excel_SetCell(sumary, 9, 1, "Estimated Expense:", "CH");

    scol = 2;
    for (yy = YearFrom, YearTo)
        immf = ((yy == YearFrom) ? mndFrom : 1);
        immt = ((yy == YearTo) ? mndTo : 12);
        for (mm = immf, immt)
            // Create a new sheet for the month
            sheetName = mndNames[mm] + "-" + yy;
            s = Excel_Add_WorkSheet(wb, sheetName);
            excel_setColumnWidth(s, 1, 20.0);
            Excel_SetCell(s, 1, 1, budTitle[1], "H1");
            Excel_SetCell(s, 2, 1, budSubtitle[1] + " " + mndNames[mm] + "-" + yy, "H2");

            // Handle first page balance reference
            if (noTabs == 0) then
                PrevSheetRef = Excel_SheetAddress("Summary", 3, 2);
            end if
            noTabs++;

            // Determine first and last day of the month
            fim = date(format("%04d-%02d-%02d", yy, mm, 1), "%Y-%m-%d");
         if ( mm == 12 ) then
            // special for december, as month 13 does not exists. 
            lim = date(format("%04d-%02d-%02d", yy, mm , 31), "%Y-%m-%d");
         else
                  lim = date(format("%04d-%02d-%02d", yy, mm + 1, 1), "%Y-%m-%d") - 1;
         end if
            dim = lim - fim + 1;

            // Create date headings for the sheet
            for (i = 1, dim)
                cdate = fim + (i - 1);
                dow = string(cdate, "%w");
                dow = ((dow == 0) ? 7 : dow);
                case
                    :(dow == 6) chStyle = "CHSAT";
                    :(dow == 7) chStyle = "CHSUN";
                default chStyle = "CH";
                end case
                excel_SetCell(s, 3, i + 1, cdate, chStyle);
            end for
            crow = 5;

            // Process transactions: Income
            sec1row = crow;
            for (i = 1, ctrans)
                if (left(traIncomeExpense[i], 1) == "I") then
                    cdate = traDateStart[i];
                    Excel_SetCell(s, crow, 1, traName[i], "num");
               res = styleRow ( s, crow, dim+1); 
                    repeat
                        if (cdate <= lim && cdate >= fim) then
                            position = cdate - fim;
                            Excel_SetCell(s, crow, position + 2, traAmount[i], "num");
                        end if
                        cdate = NextTransRepeat(cdate, fim, traDateStart[i], traPeriodType[i], traPeriodCount[i]);
                    until (cdate > lim);
                    crow++;
                end if
            end for
            crow++;

            // Add formulas for income totals
            excel_setCell(s, crow, 1, "Sum Income", "SumInc");
            for (i = 1, dim)
                formel = format("SUM(%s)", excel_sheetaddress(sec1row, i + 1, crow - 1, i + 1));
                Excel_setCellFormula(s, crow, i + 1, formel, "SumInc");
            end for
            sec1row = crow;
            crow++;

            // Process transactions: Expenses
            sec2row = crow;
            for (i = 1, ctrans)
                if (left(traIncomeExpense[i], 1) == "E") then
                    cdate = traDateStart[i];
                    Excel_SetCell(s, crow, 1, traName[i], "num");
               res = styleRow ( s, crow, dim); 
                    repeat
                        if (cdate <= lim && cdate >= fim) then
                            position = cdate - fim;
                            Excel_SetCell(s, crow, position + 2, traAmount[i], "num");
                        end if
                        cdate = NextTransRepeat(cdate, fim, traDateStart[i], traPeriodType[i], traPeriodCount[i]);
                    until (cdate > lim);
                    crow++;
                end if
            end for
            crow++;

            // Add formulas for expense totals
            excel_setCell(s, crow, 1, "Sum Expenses", "SumExp");
            for (i = 1, dim)
                formel = format("SUM(%s)", excel_sheetaddress(sec2row, i + 1, crow - 1, i + 1));
                Excel_setCellFormula(s, crow, i + 1, formel, "SumExp");
            end for
            sec2row = crow;
            crow++; crow++;

            // Add formulas for balance calculation
         // first day is special
            excel_setCell(s, crow, 1, "Balance", "SumBal");
            formel = format("%s-%s+%s", excel_sheetaddress(sec1row, 2), excel_sheetaddress(sec2row, 2), PrevSheetRef);
            Excel_setCellFormula(s, crow, 2, formel, "SumBal");
         
         // The remaining days
            for (i = 2, dim)
                formel = format("%s-%s+%s", excel_sheetaddress(sec1row, i + 1), excel_sheetaddress(sec2row, i + 1), excel_sheetaddress(crow, i));
                Excel_setCellFormula(s, crow, i + 1, formel, "SumBal");
            end for
         
         // Add a notes row, and style it with textwrap and alignment top. 
         Excel_SetCell ( s, crow+3, 1, "Notes:", "H2");
         for (i=2, dim)
            Excel_SetCell ( s, crow+3, i, "", "Note"); 
         end for
            // Update summary sheet formulas
            formel = format("MAX(%s)", excel_sheetaddress(sheetName, crow, 2, crow, dim + 1));
            Excel_SetCellFormula(sumary, 6, scol, formel, "SumBal");

            formel = format("MIN(%s)", excel_sheetaddress(sheetName, crow, 2, crow, dim + 1));
            Excel_SetCellFormula(sumary, 7, scol, formel, "SumBal");

            formel = format("SUM(%s)", excel_sheetaddress(sheetName, sec1row, 2, sec1row, dim + 1));
            Excel_SetCellFormula(sumary, 8, scol, formel, "SumInc");

         formel = format ("SUM(%s)", excel_sheetaddress (sheetName, sec2row,2, sec2row, dim+1)); 
         Excel_SetCellFormula ( sumary, 9, scol, formel, "SumExp"); 
         scol++; 
         
         // The reference to include in the next month sheet to carry over the balance. 
         PrevSheetRef = Excel_SheetAddress (sheetName, crow, dim+1); 
      
      end for // Months
   end for  // Years
   
   Excel_Close(wb); // Close and save the Workbook
   return "OK"; 
end

Back to List