None of the errors in log have anything to do with FA. They are possibly from LiveProjects.

Since you are using Linux Mint 17.1 which is the equivalent of Ubuntu 14.04 (Trusty Tahr) which in turn is based on Debian 8.0 (jessie), you will not be using PHP v5.3.x for which FA 2.3 has been designed. It is also possible that your .htaccess file (which is for Apache 2.2) may be invalid for Apache 2.4 you are using. Remove the .htaccess file temporarily and see if it works. MySQLi driver is being used in these later versions of PHP instead of MySQL - you will need FA v2.4.x for use with MySQLi or backport them.

State your PHP / MySQL / Apache versions and what is in your FA_webroot/tmp/error.log file.

Ubuntu <=> Debian equivalents:

15.04  vivid      jessie
14.10  utopic     jessie
14.04  trusty     jessie
13.10  saucy      wheezy
13.04  raring     wheezy
12.10  quantal    wheezy
12.04  precise    wheezy
11.10  oneiric    wheezy
11.04  natty      squeeze
10.10  maverick   squeeze
10.04  lucid      squeeze

@Petros: In file taxes/tax_calc.inc the functions get_tax_free_price_for_item() and get_tax_for_items() use the formula for calculation you stated.

Please see if these are the places you would like to change.

Rounding is done for consistent storage value in the db to persist across version changes of the db server.

Other places where such code is available are listed below.

In file purchasing/includes/db/supp_payment_db.inc lines 114 to 116 when $rate <> 0:

        $supp_amount = round($amount / $rate, user_price_dec());
        $supp_discount = round($discount / $rate, user_price_dec());
        $supp_charge = round($charge / $rate, user_price_dec());

In file purchasing/includes/db/invoice_db.inc lines 129 to 133:

    foreach ($taxes as $n => $taxitem)
    {
        $taxes[$n]['Value'] =  round2($taxitem['Value'], user_price_dec());
        $tax_total += $taxes[$n]['Value'];
    }

and lines 137 to 143:

    $item_added_tax = 0;
    if (!$supp_trans->tax_included)
    {
        $taxes = $supp_trans->get_taxes($supp_trans->tax_group_id);
        foreach ($taxes as $n => $taxitem)
            $item_added_tax += isset($taxitem['Override']) ? $taxitem['Override'] : round2($taxitem['Value'], user_price_dec());
    }

In file purchasing/includes/supp_trans_class.inc lines 112 to 117:

        foreach ($this->grn_items as $ln_itm) 
        {
            $items[] = $ln_itm->item_code;
            $prices[] =round( ($ln_itm->this_quantity_inv * $ln_itm->chg_price),
                user_price_dec());
        }

lines 160 to 162:

        foreach ($this->grn_items as $ln_itm)
            $total += round(($ln_itm->this_quantity_inv * $ln_itm->taxfree_charge_price($tax_group_id, $tax_group)),
             user_price_dec());

lines 176 to 177:

        foreach ($this->grn_items as $ln_itm)
            $total += round($ln_itm->this_quantity_inv * $ln_itm->chg_price, user_price_dec());

In file purchasing/includes/po_class.inc lines 141 to 144:

        foreach ($this->line_items as $ln_itm) {
            $items[] = $ln_itm->stock_id;
            $prices[] = round($ln_itm->price * ($receival ? $ln_itm->receive_qty : $ln_itm->quantity),  user_price_dec());
        }

lines 164 to 184:

    function get_trans_total() {
        
        $total = 0;
        $dec = user_price_dec();

        foreach ($this->line_items as $ln_itm) {
            $items[] = $ln_itm->stock_id;
            $value = round($ln_itm->quantity * $ln_itm->price, $dec);
            $prices[] =$value;
            $total += $value;
        }

        if (!$this->tax_included ) {
            $taxes = get_tax_for_items($items, $prices, 0, $this->tax_group_id,
            $this->tax_included,  $this->tax_group_array);

            foreach($taxes as $tax)
                $total += round($tax['Value'], $dec);
        }
        return $total;
    }

In file purchasing/po_entry_items.php lines 465 to 470:

            foreach($cart->line_items as $key => $line) {
                $inv->add_grn_to_trans($line->grn_item_id, $line->po_detail_rec, $line->stock_id,
                    $line->item_description, $line->receive_qty, 0, $line->receive_qty,
                    $line->price, $line->price, true, get_standard_cost($line->stock_id), '');
                $inv->ov_amount += round2(($line->receive_qty * $line->price), user_price_dec());
            }

3,778

(2 replies, posted in Installation)

If a user uploads a module into the modules folder, there will be no corresponding _init/config file in the modules/_cache folder. Installing the module goes without any issue (since it is already in place, but will benefit from such a check as well) but uninstalling it causes the error: "Cannot download repo index file.".

This is because the function uninstall() in includes/packages.inc attempts:

        $ctrl = get_control_file("$cachepath/_init/config");

without checking if the file exists!

Preceding it with the following and encompassing the next few statements within it should alleviate it:

if (file_exists("$cachepath/_init/config")) {
        $ctrl = get_control_file("$cachepath/_init/config");

        $targetdir = $path_to_root.'/'.$ctrl['InstallPath'];

        $dpackage = new package("$cachepath/_data", $targetdir);

        $flist = $dpackage->extract_files(true);

        $success &= copy_files($flist, "$cachepath/_back", $targetdir, true);
}

This will of course not purge the module from the modules folder ($ctrl['InstallPath'] is undefined) but will remove the module's entry from the installed_extensions.php files.


@joe: please verify and incorporate in the stable repo.

What platform are you on - Linux / Windows?
Have you enabled the various file functions in php.ini?
What does your Apache Error log say and are there any clues in your tmp/error.log file?

Make sure you use the latest v2.3.24+ from the Repo. Please read the install instructions in the wiki and related forum posts as to what files and folders should be writeable by the webserver process and enable them accordingly.

When you wish to install extensions/languages/themes/charts from the official repo, you will need to make the modules folder and it's sub folders writeable by the webserver process (755).

Please try to replace line 106 in purchasing/includes/purchasing_db.inc:

    $price = round($price * $data['conversion_factor'], user_price_dec());

with

    $price = $price * $data['conversion_factor'];

along with the default setting in preferences for the decimals and revert. Also state if there was any other place where the said function user_price_dec() should not be used.

The user_price_dec(), it appears, should be used only for display purposes and the highest precision should be used while storing data. This, of course, is limited to the number of decimal places available in the table's field where it is stored.

If this were to work, then all places where INSERT / UPDATE / REPLACE are used with this function, should be re-examined.

There is a constant defined as FLOAT_COMP_DELTA - see if it affects your values. Check it's appearance in purchasing/includes/db/suppliers_db.inc under function get_supplier_details() for the sql statement's WHERE clause:

"AND ABS(trans.ov_amount + trans.ov_gst + trans.ov_discount) - trans.alloc > ".FLOAT_COMP_DELTA

A similar code fragment is in purchasing/includes/db/supp_trans_db.inc in function get_sql_for_supplier_inquiry() apart from other files.

3,783

(5 replies, posted in Accounts Receivable)

Void one of the invoices.

My repo has been committed with this fix.

There could be several items in a delivery note coming in from different locations. (@joe: clarification)

If your business process confines items ion a delivery note to be from just one location, you might want to edit the header function of the report - See the Wiki.

Yes, that is the solution!

When $qoh is 0, only the new qty should dictate the material cost and not add to it.

@joe: please correct it in the stable version:

manufacturing/includes/db/work_orders_db.inc line 48.

The patch which includes proper initial sorting of Work Orders as well is:

--- old/manufacturing/includes/db/work_orders_db.inc    Sun May 10 02:37:36 2015
+++ new/manufacturing/includes/db/work_orders_db.inc    Sat Oct 10 00:40:29 2015
@@ -45,7 +45,7 @@
     if ($qoh + $qty != 0)
     {
         if ($qoh == 0) // 27.10.2014 apmuthu and dz.
-            $material_cost += $m_cost;
+            $material_cost = $m_cost;
         else    
             $material_cost = ($qoh * $material_cost + $qty * $m_cost) /    ($qoh + $qty);
     }
@@ -555,6 +555,7 @@
 
         $sql .= " AND workorder.required_by < '$Today' ";
     }
+    $sql .= " ORDER BY workorder.id DESC";
     return $sql;
 }

You're right. Further investigation reveals something to do with NULL not being zero as the php code computes the material cost and adds to it the existing value.... just before inserting the Work Order cost entries.

Attached is the sql trail started for the Add Work Order button click alone - 82 queries!

In manufacturing/includes/db/work_orders_quick_db.inc, the function add_work_order_quick() is the one being executed in our case.

In manufacturing/includes/db/work_orders_db.inc, line 48 may be changed from:

            $material_cost += $m_cost;

to

            $material_cost = $m_cost;

and let us see if it makes a difference. Otherwise there could be some DELTA in subtraction causing the "==" to fail.

Did a sql_trail for the Direct purchase Invoice (turned on in the config.php file just before the "Process Invoice" button was clicked) and there were 144 SQL queries!
SELECT = 100
INSERT = 21
REPLACE = 3
UPDATE =  18
One BEGIN and One COMMIT.

It appears that each line item (stock_id) rate in the purchase Direct Invoice form populates the stock_master.material_cost and purch_data.price (for the specific supplier_id) fields even if it already exists and has not changed. It must certainly be here that something is going wrong.

The following SQL run on the FA db would yield the necessary sequence of SQLs that were executed:

SELECT id , `result`, msg, REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(`sql`, '&#039;', "'"),'&lt;','<'),'&gt;','>'),"\n",""),"\t",""),"\r","") AS SQL_used FROM `0_sql_trail`

The file purchasing/includes/db/grn_db.inc has the function update_average_material_cost() that provides the material cost for computations in other files. Must see if there is any anomaly here - missing scope/vars, etc.

The value of the $_POST variable just before Work Order Add just before executing line 264 in manufacturing/work_order_entry.php is:

Array
(
    [wo_ref] => WO11
    [type] => 0
    [_stock_id_edit] => I3
    [stock_id] => I3
    [StockLocation] => DEF
    [quantity] => 1
    [date_] => 09/10/2015
    [Labour] => 3.00
    [cr_lab_acc] => 1060
    [Costs] => 2.00
    [cr_acc] => 1060
    [memo_] => 
    [RequDate] => 
    [ADD_ITEM] => Add Workorder
    [_focus] => wo_ref
    [_modified] => 0
    [_token] => a8c1a6693552df346a1fd03d90b1287e8c32cd276dfc60712b91f2154ef04926
    [_random] => 700059.4337549664
)

Only the last 2 elements change on WO Addition.

3,789

(4 replies, posted in FA Modifications)

Purchase price history - has anyone has implemented it in FA?

If so, please share your code and / or list the issues you faced in it's design and what compromises were needed to make it to work.

3,790

(3 replies, posted in Accounts Receivable)

Check if it is a browser issue and / or date ranges / cache issue.

Server and client platform and versions,
php and mysql versions,
fa version ans Chart used.

... will be needed to assist you.

It looks like your PHP 5.4+ may be the problem. FA 2.3 was designed for PHP 5.3.

Now getting to the troubleshooting, place inspection points as in the wiki and enable the various debug flags in the config.php and see what fails where.

Provide a test backup (upload the sql somewhere and provide link here) of the fa install after all your above transactions and a list of menu navigations to replicate your error. It will save time to go thru the data entry screens.

Which FA v2.3.24 are you using? Take the Post Release, the v2.3.24+ which is on the GitHub Repo Master.

Please let us know if the problem persists.

Please state what version of PHP/MySQL and what Chart of Accounts you are using and on what platform/version.

Thanks Janusz.

Very elegantly done avoiding another function (should it now be toggle_hooks? wink ).

It certainly was worth the wait.

Synched and committed in my repo as well.

Attached herewith is a PDF of your article. A couple of mail-> lines above the code snippet referred to will also need to be changed suitably.

Using the Autoload may not be recommendable as it might cause multiple classes out of context to overload one another. Including just the files used like lass.phpmailer.php  (and others if used) may be better off. Better still would be to include it in another replacement file class.mail.inc where the latter's methods and properties can be aliased.

My tests have been on a plain vanilla FA 2.3.24+ install and the problem exists in it. It is imperative to fix it in the stable version as well based on the commit in my repo. The current activate_hooks is in itself a workaround on which the deactivate_hooks is based and the comment above the former would attest to this in includes/hooks.inc after the class definition:

/*
    Non active hooks are not included in $Hooks array, so we can use special function to 
    activate.
*/

Only, in this case, at this point of entry into the hooks_invoke even the active hooks are not in it! There are a host of functions that are outside the hooks class definition that create a new instance of the hooks class and then invoke the hooks methods!

Install the extension in the default company and activate it for a non default company alone and then try to de-activate it and see if the tables get dropped.

Only the official dashboard extension has the drop sql in it.

Try to only install the dashboard extension (and not the theme) and then activate it and then de-activate it and you will see that the tables remain. At first I thought that it was to save the tables for a re-installation so that user changes are persistent but then most extensions drop the table if it exists before re-creating them during the activation phase and hence it gives us an illusion of having been deleted during deactivation unless the tables are checked just after deactivation.

The usage of XOR is may be standard even if not very widely used, but the usage of the hook_invoke by callback isn't where a simple if...else could have sufficed.

The real solution however, would be to make sure that the global $Hooks array gets pupulated earlier than this invocation and does not get overwritten thereafter. In fact, we have @kvvaradha to thank as this was investigated during the vetting of his hrm extension based on the dashboard skeleton.

It is pertinent to note that solution may lie in the fact that the extension install and uninstall works perfectly in includes/packages.inc wherein the $Hooks array gets populated with the active extensions in:

            if (($ext['active'] == true) && file_exists($path_to_root.'/'.$ext['path'].'/hooks.php'))
            {
                // we need to include the new hooks file to activate extension
                include_once($path_to_root.'/'.$ext['path'].'/hooks.php');
                foreach($db_connections as $comp => $db)
                    activate_hooks($ext['package'], $comp);
            }

Perhaps something like this should precede the hooks_invoke in admin/inst_module.php.

Yes, that was fast - even on a Sunday - wonder why it happened on a Sunday in the first place.

GitHub has restored the public viewing of my repo.

Notification of the restore:

Hi there,

I'm sorry about our naughty robots - they mistakenly flagged your account as spammy. They do a good job but can be a bit overzealous at times.

I've just unflagged your account and you are all set!

Don't hesitate to ask if you have any questions.

Thank you!

Looks like others too have faced this predicament including HackedTeam and others.

One way people have got their account profile hidden thus is because others have deliberately forked their repos repeatedly in a short while. This is one of the methods that corporates and the unscrupulous use to scuttle Open Source and for some devs to prevent others from reaping the benefit of development, participation and / or community splits.

My FA GitHub repo has been hidden from the public by their robot for reasons in the attachment.

Incidentally this happened today and I did not receive any email notification of it.

This is important for the FA repo on GitHub as well and for every GitHub user to rethink on alternatives.

Google Code closes shop to the free public on 2016-01-16 and they currently have a button to migrate to GitHub!

You can post your PHPMailer integration code herein.

First off, the current callback method of deactivating the extensions is simply not working as far as the dropping of the extension's tables are concerned - wonder when was the last time it worked, if ever.

Lines 213 to 225 of the current admin/inst_module.php file are:

    foreach($exts as $i => $ext) {
        if ($ext['package'] && ($ext['active'] ^ check_value('Active'.$i))) {
            if (!$ext['active'])
                $activated = activate_hooks($ext['package'], $comp);
            else
                $activated = hook_invoke($ext['package'], check_value('Active'.$i) ?
                 'activate_extension':'deactivate_extension', $comp, false);
            if ($activated !== null)
                $result &= $activated;
            if ($activated || ($activated === null))
                $exts[$i]['active'] = check_value('Active'.$i);
        }
    }

The second line (first if) above has an XOR (^) which means that the code fragment is executed only if either

  1. the extension was already active or

  2. the activate extension checkbox has just been ticked and submitted

but not both.

The second "if" activates the extension using the direct function activate _hooks() if it was inactive which because of the XOR condition above would mean that the activate extension checkbox had just been ticked. This works correctly and the module gets installed. However, this does not happen through the callback portion of the hook_invoke() function through which it is never called. The use of the direct function activate _hooks() was necessary since the value of the $Hooks global array variable is empty when it enters the activate_extension() method by callback if the hook_invoke() was used.

The "else" of the second "if" triggers the callback only for the deactivate_extension() method when the activate extension checkbox has just been unticked whilst the extension was active. Here, the value of the $Hooks global array variable is empty when it enters the said deactivate_extension() method which exits without executing the deactivation sql if any. It is for this reason that the new direct function deactivate_hooks() was used to do the job.

The esoteric callback method in hook_invoke() seems to make the code quite a bit of a challenge for the average user to comprehend and I was initially blinded to conclude that the said deactivate_extension() method was never called. We should try to use simple and easy to understand constructs for ease of debugging / troubleshooting by the average user for mass trusted adoption.

Steps to reproduce:
1.  Install and activate for one company any extension (currently, only dashboard extension has a drop sql separately) that creates tables and has a means to drop them as well. Alternatively create an extension that only creates one table and then on deactivation drops that table and then install and activate it for one company.
2. Now choose to deactivate it for the company for which it is currently active.
3. Check and find that the newly created table(s) have not been dropped despite deactivation.
4. Use my new function method and then repeat the steps above and you will find successful deactivation with the newly created table(s) dropped.