Hi dearmosin,

have a look at my Fiscal Year Replacement module (https://paulshipley.id.au/blog/frontaccounting/fiscal-years-replacement/) which will allow you to change the fiscal year start date for a new company.

@apmuthu: I don't remember but it was probably ECB. I don't think it matters as this code only depends on 'get_rate' being set (line 136).

@joe, as a workaround this is what I did for another website that needed the  money_format() function.

A proper refactoring to a supported library/function would be preferred, but this works as an interim solution.

Hope this helps

money_format.php

<?php

/**
* Implementation of money_format function for platforms that do not
* implictly support it
*
*
* @link       paulshipley.com.au
* @since      1.0.0
* @see        https://www.php.net/manual/en/function.money-format.php
*
* @package    Winesbydesign
* @subpackage Winesbydesign/includes
*/

if (!function_exists('money_format')) {
    /*
    That it is an implementation of the function money_format for the
    platforms that do not it bear.

    The function accepts to same string of format accepts for the
    original function of the PHP.

    (Sorry. my writing in English is very bad)

    The function is tested using PHP 5.1.4 in Windows XP
    and Apache WebServer.
    */
    function money_format($format, $number)
    {
        $regex  = '/%((?:[\^!\-]|\+|\(|\=.)*)([0-9]+)?'.
        '(?:#([0-9]+))?(?:\.([0-9]+))?([in%])/';
        if (setlocale(LC_MONETARY, 0) == 'C') {
            setlocale(LC_MONETARY, '');
        }
        $locale = localeconv();
        preg_match_all($regex, $format, $matches, PREG_SET_ORDER);
        foreach ($matches as $fmatch) {
            $value = floatval($number);
            $flags = array(
            'fillchar'  => preg_match('/\=(.)/', $fmatch[1], $match) ?
            $match[1] : ' ',
            'nogroup'   => preg_match('/\^/', $fmatch[1]) > 0,
            'usesignal' => preg_match('/\+|\(/', $fmatch[1], $match) ?
            $match[0] : '+',
            'nosimbol'  => preg_match('/\!/', $fmatch[1]) > 0,
            'isleft'    => preg_match('/\-/', $fmatch[1]) > 0
            );
            $width      = trim($fmatch[2]) ? (int)$fmatch[2] : 0;
            $left       = trim($fmatch[3]) ? (int)$fmatch[3] : 0;
            $right      = trim($fmatch[4]) ? (int)$fmatch[4] : $locale['int_frac_digits'];
            $conversion = $fmatch[5];

            $positive = true;
            if ($value < 0) {
                $positive = false;
                $value  *= -1;
            }
            $letter = $positive ? 'p' : 'n';

            $prefix = $suffix = $cprefix = $csuffix = $signal = '';

            $signal = $positive ? $locale['positive_sign'] : $locale['negative_sign'];
            switch (true) {
                case $locale["{$letter}_sign_posn"] == 1 && $flags['usesignal'] == '+':
                    $prefix = $signal;
                    break;
                case $locale["{$letter}_sign_posn"] == 2 && $flags['usesignal'] == '+':
                    $suffix = $signal;
                    break;
                case $locale["{$letter}_sign_posn"] == 3 && $flags['usesignal'] == '+':
                    $cprefix = $signal;
                    break;
                case $locale["{$letter}_sign_posn"] == 4 && $flags['usesignal'] == '+':
                    $csuffix = $signal;
                    break;
                case $flags['usesignal'] == '(':
                case $locale["{$letter}_sign_posn"] == 0:
                    $prefix = '(';
                    $suffix = ')';
                    break;
            }
            if (!$flags['nosimbol']) {
                $currency = $cprefix .
                ($conversion == 'i' ? $locale['int_curr_symbol'] : $locale['currency_symbol']) .
                $csuffix;
            } else {
                $currency = '';
            }
            $space  = $locale["{$letter}_sep_by_space"] ? ' ' : '';

            $value = number_format($value, $right, $locale['mon_decimal_point'],
            $flags['nogroup'] ? '' : $locale['mon_thousands_sep']);
            $value = @explode($locale['mon_decimal_point'], $value);

            $n = strlen($prefix) + strlen($currency) + strlen($value[0]);
            if ($left > 0 && $left > $n) {
                $value[0] = str_repeat($flags['fillchar'], $left - $n) . $value[0];
            }
            $value = implode($locale['mon_decimal_point'], $value);
            if ($locale["{$letter}_cs_precedes"]) {
                $value = $prefix . $currency . $space . $value . $suffix;
            } else {
                $value = $prefix . $value . $space . $currency . $suffix;
            }
            if ($width > 0) {
                $value = str_pad($value, $width, $flags['fillchar'], $flags['isleft'] ?
                STR_PAD_RIGHT : STR_PAD_LEFT);
            }

            $format = str_replace($fmatch[0], $value, $format);
        }
        return $format;
    }
}

4

(11 replies, posted in Modules Add-on's)

@kvvaradha, thanks, you're welcome.

It would be possible to wrap a UI around this to configure and activate the cron job, and then package that as a module

I have been doing some work with the Exchange Rates function and noticed that the ECB rates are in XML format yet are being parsed using string functions and regular expressions. While this works, it is not optimal.

I have rewritten this using SimpleXMLElements to parse it.

gl/includes/db/gl_db_rates.inc
@@ -198,21 +205,32 @@ function get_extern_rate($curr_b, $provider = 'ECB', $date)
     $val = '';
     if ($provider == 'ECB')
     {
-        $contents = str_replace ("<Cube currency='USD'", " <Cube currency='EUR' rate='1'/> <Cube currency='USD'", $contents);
-        $from_mask = "|<Cube\s*currency=\'" . $curr_a . "\'\s*rate=\'([\d.,]*)\'\s*/>|i";
-        preg_match ( $from_mask, $contents, $out );
-        $val_a = isset($out[1]) ? $out[1] : 0;
-        $val_a = str_replace ( ',', '', $val_a );
-        $to_mask = "|<Cube\s*currency=\'" . $curr_b . "\'\s*rate=\'([\d.,]*)\'\s*/>|i";
-        preg_match ( $to_mask, $contents, $out );
-        $val_b = isset($out[1]) ? $out[1] : 0;
-        $val_b = str_replace ( ',', '', $val_b );
-        if ($val_b) 
-        {
+        $data = new SimpleXMLElement($contents);
+
+        $val_a = 0;
+        $val_b = 0;
+
+        foreach ($data->Cube->Cube->Cube as $rate) {
+            if ($curr_a == 'EUR') {
+                $val_a = 1;
+            }
+            elseif ($curr_a == $rate["currency"] )
+            {
+                $val_a = (float) $rate["rate"];
+            }
+
+            if ($curr_b == 'EUR') {
+                $val_b = 1;
+            }
+            elseif ($curr_b == $rate["currency"] )
+            {
+                $val_b = (float) $rate["rate"];
+            }
+        }
+
+        if ($val_b) {
             $val = $val_a / $val_b;
-        } 
-        else 
-        {
+        } else {
             $val = 0;
         }
     }

The Reserve Bank of Australia (RBA) is Australia's central bank, and they supply an official exchange rate at https://www.rba.gov.au/rss/rss-cb-exchange-rates.xml.

I have made changes to the Exchange Rates function to include the RBA as a provider.

To use this, change the config file (config.php) so that:

$xr_providers = array("ECB", "YAHOO", "GOOGLE", "BLOOMBERG","RBA");
$dflt_xr_provider = 4;

Hope this helps

gl/includes/db/gl_db_rates.inc

@@ -170,6 +170,13 @@ function get_extern_rate($curr_b, $provider = 'ECB', $date)
         $proto = 'https://';
         $contents=file_get_contents($proto.$site.$filename);
     }
+    elseif ($provider == 'RBA')
+    {
+        $filename = "/rss/rss-cb-exchange-rates.xml";
+        $site = "www.rba.gov.au";
+        $proto = 'https://';
+        $contents=file_get_contents($proto.$site.$filename);
+    }
     if (empty($contents)) {
         if (function_exists('curl_init'))
         {    // first check with curl as we can set short timeout;

@@ -240,6 +258,40 @@ function get_extern_rate($curr_b, $provider = 'ECB', $date)
         $val = getInnerStr($contents, '<span id="ctl00_M_lblToAmount">', '<');
         $val = str_replace (',', '', $val);
     }
+    elseif ($provider == 'RBA')
+    {
+        $data = new SimpleXMLElement($contents);
+        $data->registerXPathNamespace('ns', 'http://purl.org/rss/1.0/');
+        $data->registerXPathNamespace('cb', 'http://www.cbwiki.net/wiki/index.php/Specification_1.2/');
+
+        $val_a = 0;
+        $val_b = 0;
+        
+        for ($i = 0; $i < count($data->xpath('ns:item')); $i++) {
+            if ($curr_a == 'AUD')
+            {
+                $val_a = 1;
+            }
+            elseif ($curr_a == $data->item[$i]->children('cb',TRUE)->statistics[0]->exchangeRate[0]->targetCurrency )
+            {
+                $val_a = (float) $data->item[$i]->children('cb',TRUE)->statistics[0]->exchangeRate[0]->observation[0]->value;
+            }
+
+            if ($curr_b == 'AUD') {
+                $val_b = 1;
+            }
+            elseif ($curr_b == $data->item[$i]->children('cb',TRUE)->statistics[0]->exchangeRate[0]->targetCurrency )
+            {
+                $val_b = (float) $data->item[$i]->children('cb',TRUE)->statistics[0]->exchangeRate[0]->observation[0]->value;
+            }
+        }
+
+        if ($val_b) {
+            $val = $val_a / $val_b;
+        } else {
+            $val = 0;
+        }
+    }
     return $val;
 }  /* end function get_extern_rate */

7

(11 replies, posted in Modules Add-on's)

Based on this post and the wiki (https://frontaccounting.com/fawiki/index.php?n=Devel.CronJobSample), I have written a complete script for updating the exchange rates via cron. Be sure to change the 'user' & 'password' at the end of the script to a valid account that has FA security to access the Exchange Rates function. Creating a cron entry should be trivial.

The setup in the first part of the script would be a good template for other batch tasks, though it is not possible to call functions that are expecting a display to be present, which does limit what processes can be performed via cron.

Hope this helps

xr_load.php (assuming that this is in FA root directory)

<?php
/**********************************************************************
*
* Load current day Exchange Rates from external provider using cron
*
* run using:
* cd <fa root dir>
* php xr_load.php
*
***********************************************************************/

// set environment
$path_to_root=".";
$page_security = 'SA_EXCHANGERATE';


// configure fake server environment for cron
$_SERVER = array();
$_SERVER['REMOTE_ADDR']='batch';
$_SERVER['HTTP_USER_AGENT']='cron';
$_SERVER['REQUEST_URI']='';
$_SERVER['HTTPS']='';
define('FA_LOGOUT_PHP_FILE', 'cronjob');
include_once($path_to_root . "/includes/session.inc");
install_hooks();


// load rates function
include_once($path_to_root . "/includes/date_functions.inc");
include_once($path_to_root . "/includes/banking.inc");

/**
*
* Load Exchange Rates from external provider
*
* @param {object} $company The FA Company to by loaded
* @param {object} $usr     FA User
* @param {object} $pwd     FA Password
*
* @return
*
*/
function xr_load($company, $usr, $pwd)
{
    global $_SESSION;

    $_SESSION['wa_current_user']->login($company,$usr,$pwd);

    $date_ = Today();

    foreach (get_currencies() as $curr) {
        $curr_abrev = $curr['curr_abrev'];

        if (!is_company_currency($curr_abrev)) {
            $BuyRate = maxprec_format(retrieve_exrate($curr_abrev, $date_));

            if (get_date_exchange_rate($curr_abrev, $date_) == 0)
            {
                add_exchange_rate($curr_abrev, $date_, $BuyRate, $BuyRate);
            } else {
                update_exchange_rate($curr_abrev, $date_, $BuyRate, $BuyRate);
            }
        }
    }
}

// load rates
xr_load(0,'user',password');

On the Exchange Rates screen (Banking and General Ledger) there is a message towards the bottom of the screen which states "Exchange rates are entered against the company currency.". This is not very informative. A slight addition to the code will show what the current company currency is and the exchange rate provider. Like so:

   Exchange rates are entered against the company currency = USD (Provider = ECB)

gl/manage/exchange_rates.php

 @@ -146,7 +146,7 @@ function display_rate_edit()
 
     submit_add_or_update_center($selected_id == '', '', 'both');
 
-    display_note(_("Exchange rates are entered against the company currency."), 1);
+    display_note(_("Exchange rates are entered against the company currency = ".get_company_pref('curr_default')." (Provider = ".$xchg_rate_provider.")"), 1);
 }
 
 //---------------------------------------------------------------------------------------------

The date on the screen is not being refreshed when the exchange rates are updated. The data is correct in the database.

In gl/manage/exchange_rates.php, on line 132 the 'date' is updated to 'Today()', yet only the 'BuyRate' is refreshed on the screen (line 140).

I believe that this patch fixes the issue.

@@ -137,6 +137,8 @@ function display_rate_edit()
     {
         $_POST['BuyRate'] = 
             maxprec_format(retrieve_exrate($_POST['curr_abrev'], $_POST['date_']));
+
+        $Ajax->activate('date_');
         $Ajax->activate('BuyRate');
     }
     amount_row(_("Exchange Rate:"), 'BuyRate', null, '',

Hope this helps

Hi Apmuthu, I did consider this, and while technically easy to do, I'm not so sure about the accounting wisdom. The existing code only allows fiscal years of a full year in length and I didn't want to change more than necessary. I could see this happening if the existing company changed it's fiscal year start/end dates, then the transition year would be truncated. I would suggest that in that case it would be better to start a new FA company with the new dates and transition from the old FA company to the new one. Of course if someone with more accounting expertise than I would like to advise, that would be great.

Hi,

I have been using FrontAccounting for several years and one of the things that has annoyed me is that the Fiscal Years admin function is difficult to use and limited in functionality. At first glance it appears to be able to do anything you could want it to, but you will quickly find (and an inspection of the code will confirm) that the validation will not allow you to change the year start, and the year end must be the year start plus one year less a day, so you can enter anything you like but there is only one valid combination.

My Fiscal Years Replacement extension addresses both of these issues. I have replaced the start and end date fields with a single button to add a new year, which is a much simpler UI, with the identical functionality. If there is only one fiscal year (and no transactions) the start date can be changed to whatever is required (the end date will be set to the start date plus a year less one day). Once transactions have been entered (or there is more than one fiscal year) the start date is fixed and can not be changed.

The module can be downloaded from:

https://github.com/PaulShipley/admin_fiscalyear

For a full description see

https://paulshipley.id.au/blog/frontaccounting/fiscal-years-replacement/

Thanks for the quick fix Joe.

FYI. This was with PHP 7.2.15 on Windows 10 with XAMPP 7.2.15 (2019/03/01)

@apmuthu

Mmm formats (like format 5 - YYYYMmmDD) are even clear to our American friends, whereas 03/04 could be 4th of March or 3rd of April. :-)

Hi,

I am getting numerous non-numeric value errors in date_functions.inc line 399 (like below).

A non-numeric value encountered in file: D:\Users\shipl\htdocs\fa24\includes\date_functions.inc at line 399
D:\Users\shipl\htdocs\fa24\includes\date_functions.inc:531:     date2sql('2019/Mar/5')
D:\Users\shipl\htdocs\fa24\includes\references.inc:125:     explode_date_to_dmy('2019/Mar/5')
D:\Users\shipl\htdocs\fa24\includes\references.inc:252:     (references Object)->_parse_next('1','',(Array[3]))
D:\Users\shipl\htdocs\fa24\gl\gl_bank.php:203:     (references Object)->get_next('1','','')
D:\Users\shipl\htdocs\fa24\gl\gl_bank.php:35:     create_cart('1','0')

This issue seems to be due to the handling of non-numeric months (like in date formats 3,4,5). I am using date format 5 (YYYYMmmDD - 2019/Mar/5) in \includes\date_functions.inc


    if ($year+$day+$month) {
        if ($how > 2)
        {
            global $tmonths;
            $month = array_search($month, $tmonths);
        }

I believe that this should be

    if ($how > 2)
    {
        global $tmonths;
        $month = array_search($month, $tmonths);
    }

    if ($year+$day+$month) {

The patch file is

399,404d398
<     if ($how > 2)
<     {
<         global $tmonths;
<         $month = array_search($month, $tmonths);
<     }
<
406c400,405
<          //to modify assumption in 2030
---
>         if ($how > 2)
>         {
>             global $tmonths;
>             $month = array_search($month, $tmonths);
>         }
>         //to modify assumption in 2030

Hope this helps

14

(1 replies, posted in Installation)

Hi,

I have been working on migrating my private production environment (Ubuntu 16.04, Apache2) to HTTPS and found that there was an issue with FrontAccounting. The problem was that I could not edit or add items using Items and Inventory / Items as the JsHttpRequest would fail (leaving an explanation mark triangle). The issue was that the X-Frame-Options needed to be set in the Apache2 configuration.

The issue is described here:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options

The details are described here:
http://paulshipley.id.au/blog/load-denied-by-x-frame-options

Hope this helps.

Hi Nikunj, Poncho and others,

My extension is primarily to format the layout of the invoice to meet Australian Taxation Office (ATO) requirements, so I don't believe it will do what you are looking for. It might be possible to use a similar approach to develop an Indian GST Invoice but I am not an accountant or familiar with Indian tax law.

Hope this helps.

Paul

16

(9 replies, posted in Modules Add-on's)

@apmuthu: Great. I will refactor my code accordingly. Thanks.

Hi,

Using FA 2.4.4 and PHP 7.2.3 (XAMPP 2018-03-14) with debugging on, I am getting the following error message when loading Customer Allocations:

count(): Parameter must be an array or an object that implements Countable in file: D:\Users\shipl\htdocs\fa24\includes\db_pager.inc at line 293
D:\Users\shipl\htdocs\fa24\includes\db_pager.inc:347:     (db_pager Object)->_sql_gen('1')
D:\Users\shipl\htdocs\fa24\includes\db_pager.inc:180:     (db_pager Object)->_init()
D:\Users\shipl\htdocs\fa24\includes\db_pager.inc:392:     (db_pager Object)->query()
D:\Users\shipl\htdocs\fa24\includes\ui\db_pager_view.inc:45:     (db_pager Object)->select_records()
d:\Users\shipl\htdocs\fa24\sales\allocations\customer_allocation_main.php:113:     display_db_pager((db_pager Object))

Checking the code for db_pager.inc, while $extra_where is defined on line 52, there are several ways it can be used without being initialized.

I propose changing line 52 from

    var    $extra_where;

to

    var    $extra_where = array();

Hope this helps.

Thanks.

18

(9 replies, posted in Modules Add-on's)

The ATO guidelines show two examples: one for invoices less than $1000 and the other for amounts of $1000 or more. In practice most businesses choose one or the other depending on if the bulk of their invoices are above or below the threshold. I have modeled my changes on example 2 (above $1000).

This layout requires separating price (ex tax), GST and total for each line item, which required some changes to the calculations. The line and column totals are unchanged.

Hope this clarifies these changes.

19

(9 replies, posted in Modules Add-on's)

Thanks. Let me know if there are any issues. I have tried to make as few changes as possible that meets the ATO requirements.

I'm not sure if this could be made generic as each jurisdiction has it's own requirements. For example the ATO insists that all references are to 'GST', whereas the UK uses 'VAT'. I suspect that there would have to be several versions (eg: Australia, UK, India, USA, etc.) Happy to discuss further ideas.

Great. Thanks Joe. Glad to help.

I have created an extension module to provide an Australian Tax Invoice.

The ATO (Australian Tax Office) is rather flexible about the layout of invoices but there are several mandatory elements that must be complied with to be legal. Their guidelines can be found here https://www.ato.gov.au/Business/GST/Iss … axinvoices.

I have used the Module facility to override the standard invoice (rep109) to make FA generate compliant invoices. A number of the other reports have also been modified to be consistent (but not a legal requirement). As this is using Modules, the changes can be switched on or off simply by enabling or disabling the extension.

Module can be downloaded from https://github.com/PaulShipley/rep_aus_invoice

@apmuthu Can this be added to the official extensions?

Thanks.

I'm getting the following message in Reports and Analysis with code version 2.4.4 (de943e150d36f815a02d4996e532a5e4e7510bc8) and PHP 7.2.3 (XAMPP 2018-03-14) with debugging on.

count(): Parameter must be an array or an object that implements Countable in file: D:\Users\shipl\htdocs\fa24\reporting\includes\reports_classes.inc at line 37
d:\Users\shipl\htdocs\fa24\reporting\reports_main.php:34:     (BoxReports Object)->addReportClass('Customer','0')

It seems that the issue is an uncorrected old style class constructor (probably missed as it has been renamed).

I propose the following change to fix the issue.

------------------------------------------------
reporting/includes/reports_classes.inc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/reporting/includes/reports_classes.inc b/reporting/includes/reports_classes.inc
index c872cef0..96134f95 100644
--- a/reporting/includes/reports_classes.inc
+++ b/reporting/includes/reports_classes.inc
@@ -26,7 +26,7 @@ class BoxReports
    var $ar_reports;
    var $ctrl_handlers = array();

-    function ReportClasses()
+    function __construct()
    {
        $this->ar_classes = array();
    }
------------------------------------------------

Thanks.

23

(5 replies, posted in Report Bugs here)

I'm getting the same message (warning?) with code version 2.4.4 (de943e150d36f815a02d4996e532a5e4e7510bc8) and PHP 7.2.3 (XAMPP 2018-03-14) with debugging on.

This message seems to be related to trying to change the ini setting after the session has started, which was being silently ignored in earlier versions of PHP but is now being reported. Refer https://stackoverflow.com/questions/48209844/ini-set-fails-to-set-session-variables-php-7-2-0-and-higher

I propose that the ini_set be moved before the new SessionManager() [as follows]

-------------------------------------------------------
includes/session.inc | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/includes/session.inc b/includes/session.inc
index 3f8c525d..779dc4e6 100644
--- a/includes/session.inc
+++ b/includes/session.inc
@@ -394,6 +394,9 @@ function login_timeout()
    if (file_exists($path_to_root.'/'.$ext['path'].'/hooks.php'))
        include_once($path_to_root.'/'.$ext['path'].'/hooks.php');
}
+
+ini_set('session.gc_maxlifetime', 36000); // 10hrs
+
$Session_manager = new SessionManager();
$Session_manager->sessionStart('FA'.md5(dirname(__FILE__)));

@@ -429,7 +432,7 @@ function login_timeout()
*/
// ini_set('session.save_path', VARLIB_PATH.'/');

-ini_set('session.gc_maxlifetime', 36000); // 10hrs
+// ini_set('session.gc_maxlifetime', 36000); // 10hrs

hook_session_start(@$_POST["company_login_name"]);
-------------------------------------------------------

Thanks.

24

(6 replies, posted in Report Bugs here)

Hi @kvvaradha,

Yes, it is with PHP 7.1.

I have confirmed that is_numeric is a solution.

function check_value($name)
{
    if (!isset($_POST[$name]) || !is_numeric($_POST[$name]))
        return 0;
    return 1;
}

I need to leave the debugging on while I work on my own code.

Thanks.

25

(6 replies, posted in Report Bugs here)

Hi,

With debugging on I notice a lot of these messages occuring through out the application, for example:

A non-numeric value encountered in file: C:\Users\Paul\htdocs\fa24\includes\ui\ui_input.inc at line 350
C:\Users\Paul\htdocs\fa24\sales\manage\customers.php:330:     check_value('show_inactive')

Problem is in function check_vaule

function check_value($name)
{
    if (!isset($_POST[$name]) || ($_POST[$name]+0) === 0)
        return 0;
    return 1;
}

And is related to GIT dc449868579dadd4fb918b6a9a7b10ba175d388b

-    if (!isset($_POST[$name]) || $_POST[$name]=='')
+    if (!isset($_POST[$name]) || ($_POST[$name]+0) === 0)

There seems to be three values passed to this function: NULL, '', '1'. The error is occurring when the check box is not ticked (ie: '')

I propose this as a possible solution:

function check_value($name)
{
    if (!isset($_POST[$name]))
        return 0;
    if ($_POST[$name] === 0)
        return 0;
    return 1;
}

Hope this helps.