See, https://frontaccounting.com/punbb/viewtopic.php?id=8672.

There are no db changes.  All the code does is pass the dimension data from the ui to the underlying db functions; prior code always passed zeroes so associated dimensions on payment records was cleared.  I do not understand why, but clearly it was intentional.

In my opinion there is no reason to lose the dimension data in the db.  If the dimension data was not desired (why, I do not know), it should be the responsibility of the user interface code to choose whether to display it.

@rafat

You are right.  I did add underlying support for dimension on supplier payments, but because I only use the direct supplier invoice or bank payments, it looks like I did not update the supplier payments ui.

I'm on a business trip this week; next week I will look into it.

Vanilla FA does not dimension payments and therefore the A/P and A/R accounts do not have dimension.

If you want to make the code changes to dimension payments, I suggest that you examine my fork and look at the commits that have "Add dimension" in the title.

55

(20 replies, posted in Report Bugs here)

Also,

diff --git a/core/purchasing/includes/db/grn_db.inc b/core/purchasing/includes/db/grn_db.inc
index d2fbd7f..0820fb3 100644
--- a/core/purchasing/includes/db/grn_db.inc
+++ b/core/purchasing/includes/db/grn_db.inc
@@ -17,15 +17,14 @@ function update_average_material_cost($supplier, $stock_id, $price, $qty, $date,
        // save a couple of db calls like get_supplier()
        
        $supp = get_supplier($supplier);
-       if ($supplier != null)
+       if ($supplier != null) {
                $currency = $supp['curr_code'];
-       else
+        if ($supp['tax_included'])
+            $price = get_tax_free_price_for_item($stock_id, $price, $supp['tax_group_id'],
+                $supp['tax_included']);
+    } else
                $currency = null;
 
-       if ($supp['tax_included'])
-               $price = get_tax_free_price_for_item($stock_id, $price, $supp['tax_group_id'],
-                       $supp['tax_included']);
-
        if ($currency != null)
        {
                $ex_rate = get_exchange_rate_to_home_currency($currency, $date);

56

(20 replies, posted in Report Bugs here)

Beginning with PHP 7.4.8 (perhaps earlier), php started announcing the following error:

Trying to access array offset on value of type bool ...

Mostly the error can be ignored, but it does indicate improper coding in FA, and possibly bugs.  I haven't done much investigation, but here are a few suggested PHP 7.4.8 fixes to FA (and I am sure there will be many more):

--- a/core/gl/gl_journal.php
+++ b/core/gl/gl_journal.php
@@ -149,7 +149,7 @@ function create_cart($type=0, $trans_no=0)
         {
             $net_sum = 0;
             foreach($cart->gl_items as $gl)
-                if (!is_tax_account($gl->code_id) && !is_subledger_account($gl->code_id, $gl->person_id))
+                if (!is_tax_account($gl->code_id) && !is_subledger_account($gl->code_id))
                     $net_sum += $gl->amount;
 
             $ex_net = abs($net_sum) - array_sum($tax_info['net_amount']);
diff --git a/core/gl/includes/db/gl_db_accounts.inc b/core/gl/includes/db/gl_db_accounts.inc
index 53eb5a9..dc60c13 100644
--- a/core/gl/includes/db/gl_db_accounts.inc
+++ b/core/gl/includes/db/gl_db_accounts.inc
@@ -224,7 +224,7 @@ function is_subledger_account($account)
 
     $result = db_query($sql,"Couldn't test AR/AP account");
     $myrow = db_fetch_row($result);
-    return $myrow[0];
+    return $myrow == false ? 0 : $myrow[0];
 }
 
 function get_subaccount_data($code_id, $person_id)
diff --git a/core/includes/references.inc b/core/includes/references.inc
index a928c7e..027aafd 100644
--- a/core/includes/references.inc
+++ b/core/includes/references.inc
@@ -227,7 +227,7 @@ class references
             return false;
 
         $result = db_fetch_row($result);
-        return $result[0];
+        return $result == false ? false : $result[0];
     }
 
     function is_new_reference($ref, $type, $trans_no=0)
diff --git a/core/includes/ui/items_cart.inc b/core/includes/ui/items_cart.inc
index 2fe92da..3c91f0b 100644
--- a/core/includes/ui/items_cart.inc
+++ b/core/includes/ui/items_cart.inc
@@ -144,8 +144,8 @@ class items_cart
         $this->gl_items[$index]->code_id = $code_id;
         $this->gl_items[$index]->person_id = $person_id;
 
-        $gl_type = is_subledger_account($code_id, $person_id);
-        if ($gl_type)
+        $gl_type = is_subledger_account($code_id);
+        if ($person_id != null && $gl_type)
         {
             $this->gl_items[$index]->person_type_id = $gl_type > 0 ? PT_CUSTOMER : PT_SUPPLIER;
             $data = get_subaccount_data($code_id, $person_id);
@@ -263,7 +263,7 @@ class items_cart
 
         foreach($this->gl_items as $gl)
         {
-            if ($person_type = is_subledger_account($gl->code_id, $gl->person_id))
+            if ($person_type = is_subledger_account($gl->code_id))
             {
                 $tax_info['person_type'] = $person_type < 0 ? PT_SUPPLIER : PT_CUSTOMER;
                 $tax_info['person_id'] = $gl->person_id;
@@ -520,8 +520,8 @@ class gl_item
 
         $this->code_id = $code_id;
         $this->person_id = $person_id;
-        $gl_type = is_subledger_account($code_id, $person_id);
-        if ($gl_type)
+        $gl_type = is_subledger_account($code_id);
+        if ($person_id != null  && $gl_type)
         {
             $this->person_type_id = $gl_type > 0 ? PT_CUSTOMER : PT_SUPPLIER;
             $data = get_subaccount_data($code_id, $person_id);
diff --git a/core/sales/includes/db/sales_order_db.inc b/core/sales/includes/db/sales_order_db.inc
index fcc19fd..17952e3 100644
--- a/core/sales/includes/db/sales_order_db.inc
+++ b/core/sales/includes/db/sales_order_db.inc
@@ -648,5 +648,5 @@ function last_sales_order_detail($order, $field)
 
         $last_query=db_query($sql, "Could not retrieve last order detail");
         $row = db_fetch_row($last_query);
-        return $row[0];
+        return $row == false ? false : $row[0];
 }
diff --git a/core/sales/includes/sales_db.inc b/core/sales/includes/sales_db.inc
index 6ff05f1..02cbaaf 100644
--- a/core/sales/includes/sales_db.inc
+++ b/core/sales/includes/sales_db.inc
@@ -231,6 +231,7 @@ function read_sales_trans($doc_type, $trans_no, &$cart)
     } else {
         // read header data from first document
         $myrow = get_customer_trans($trans_no[0],$doc_type);
+        $debtor_no = $myrow['debtor_no'];
         if (count_array($trans_no)>1)
             $cart->trans_no = get_customer_trans_version($doc_type, $trans_no);
         else
@@ -238,7 +239,7 @@ function read_sales_trans($doc_type, $trans_no, &$cart)
 
         $cart->set_sales_type($myrow["tpe"], $myrow["sales_type"], $myrow["tax_included"],0);
 
-        $cart->set_customer($myrow["debtor_no"], $myrow["DebtorName"],
+        $cart->set_customer($debtor_no, $myrow["DebtorName"],
             $myrow["curr_code"], $myrow["discount"], $myrow["payment_terms"]);
 
         $cart->set_branch($myrow["branch_code"], $myrow["tax_group_id"],
@@ -279,7 +280,7 @@ function read_sales_trans($doc_type, $trans_no, &$cart)
                     @$myrow["src_id"]);
             }
         }
-        $cart->prepayments = get_payments_for($trans_no, $doc_type, $myrow["debtor_no"]);
+        $cart->prepayments = get_payments_for($trans_no, $doc_type, $debtor_no);
 
     } // !newdoc
 
diff --git a/core/sales/includes/ui/sales_order_ui.inc b/core/sales/includes/ui/sales_order_ui.inc
index babf8f2..7172dff 100644
--- a/core/sales/includes/ui/sales_order_ui.inc
+++ b/core/sales/includes/ui/sales_order_ui.inc
@@ -400,7 +400,7 @@ function display_order_header(&$order, $editable, $date_text)
     if (($order->pos['cash_sale'] || $order->pos['credit_sale']) 
         && !$order->is_started()) {
          // editable payment type 
-        if (get_post('payment') !== $order->payment) {
+        if (isset($_POST['payment']) && $_POST['payment'] !== $order->payment) {
             $order->payment = get_post('payment');
             $order->payment_terms = get_payment_terms($order->payment);
             $order->due_date = get_invoice_duedate($order->payment, $order->document_date);

57

(35 replies, posted in Banking and General Ledger)

Extracting persons out of the gl file is tricky because FA nulls these fields out, leading to very obscure SQL.   Try:

--- a/core/gl/includes/db/gl_db_trans.inc
+++ b/core/gl/includes/db/gl_db_trans.inc
@@ -619,13 +619,13 @@ function get_sql_for_journal_inquiry($filter, $from, $to, $ref='', $memo='', $al
         gl.tran_date,
         gl.type as trans_type,
         gl.type_no as trans_no,
-        IFNULL(gl.person_id, IFNULL(st.supplier_id, IFNULL(grn.supplier_id, IFNULL(dt.debtor_no, bt.person_id)))) as person_id,
+        IF(MAX(gl.person_id), MAX(gl.person_id), IFNULL(st.supplier_id, IFNULL(grn.supplier_id, IFNULL(dt.debtor_no, bt.person_id)))) as person_id,
         IF(ISNULL(st.supp_reference), '', st.supp_reference) AS supp_reference,
         refs.reference,
         IF(gl.type=".ST_BANKTRANSFER.",MAX(gl.amount),SUM(IF(gl.amount>0, gl.amount,0))) as amount,
         com.memo_,
         IF(ISNULL(u.user_id),'',u.user_id) as user_id,
-        IF(gl.person_id, gl.person_type_id, IF(!ISNULL(st.supplier_id) OR !ISNULL(grn.supplier_id),".  PT_SUPPLIER . "," .  "IF(dt.debtor_no," . PT_CUSTOMER . "," .
+        IF(MAX(gl.person_id), MAX(gl.person_type_id), IF(!ISNULL(st.supplier_id) OR !ISNULL(grn.supplier_id),".  PT_SUPPLIER . "," .  "IF(dt.debtor_no," . PT_CUSTOMER . "," .
         "IF(bt.person_id != '' AND !ISNULL(bt.person_id), bt.person_type_id, -1)))) as person_type_id
         FROM ".TB_PREF."gl_trans as gl
          LEFT JOIN ".TB_PREF."audit_trail as a ON

58

(10 replies, posted in Accounts Payable)

1.  In vanilla FA, costs are labeled Overhead and Labor.   These labels are misleading, because costs can be any G/L account. (On my site, I have renamed them Cost 1 and Cost 2).

2. I have not tried using Service Item in manufacturing, and it sounds like you uncovered a bug.  I would imagine that it could work like additional costs and could be a useful feature.

Note that you should use Advanced Manufacture to assign additional costs because Basic Manufacture in vanilla FA requires a BOM (On my site, I eliminated that requirement).

I checked the demo site and if you do use a negative cost, FA reports "The amount entered is not a valid number or less then zero".  So you would have to change the code to allow a negative number to reduce the cost of item B from item A.  It may be as simple as changing the check for <=0 to ==0.

I admit that this is a less direct way to achieve what you really want: charge/credit the supplier to change the item cost of a previously manufactured item.

59

(10 replies, posted in Accounts Payable)

1. Purchase item X
2. Pay supplier for purchased item X
3. Manufacture item A based on cost of purchased item X

Now you discover that the supplier needs to be paid more or less based on output of manufactured item A per your supplier agreement.

4. Pay/credit the supplier using a G/L account.  This does not affect inventory.
5. Manufacture item B using all of item A + payment/credit from same G/L account in item 5.

Item B now has the average cost of payments to the supplier / quantity of item B.

FA does not do FIFO.

Vanilla FA might not allow negative cost assignment as a manufacturing issue; in this case, change the code to allow it.

60

(4 replies, posted in Report Bugs here)

diff --git a/core/purchasing/includes/supp_trans_class.inc b/core/purchasing/includes/supp_trans_class.inc
index be9ec00..5a17f86 100644
--- a/core/purchasing/includes/supp_trans_class.inc
+++ b/core/purchasing/includes/supp_trans_class.inc
@@ -66,7 +66,7 @@ class supp_trans
                        read_supp_invoice($trans_no, $trans_type, $this);
                        if ($trans_type == ST_SUPPCREDIT)
                        {
-                               $this->src_docs = find_src_invoices($trans_no);
+                               $this->src_docs = find_src_invoices($this);
                        }
                        read_supplier_details_to_trans($this, $this->supplier_id);
                }
diff --git a/core/purchasing/supplier_credit.php b/core/purchasing/supplier_credit.php
index 461c804..070afda 100644
--- a/core/purchasing/supplier_credit.php
+++ b/core/purchasing/supplier_credit.php
@@ -28,8 +28,12 @@ if (user_use_date_picker())
 
 //----------------------------------------------------------------------------------------
 
-if (isset($_GET['ModifyCredit']))
+if (isset($_GET['ModifyCredit'])) {
        check_is_editable(ST_SUPPCREDIT, $_GET['ModifyCredit']);
+    $_SESSION['page_title'] = sprintf( _("Modifying Supplier Credit # %d"), $_GET['ModifyCredit']);
+    $_SESSION['supp_trans'] = new supp_trans(ST_SUPPCREDIT, $_GET['ModifyCredit']);
+}
+

 
 //---------------------------------------------------------------------------------------------------

I doubt the old code ever worked.  I looked at the change history for related files and nothing has been changed that would have caused this to break.  The proposed change above is incomplete and untested; G/L line items cannot be edited, only added or deleted.

61

(10 replies, posted in Accounts Payable)

Assignment of costs to a given item is done using the manufacturing process.  If for some reason, the costs are not clearly understood when item A manufactured, you can assign more cost by manufacturing item B where item A and the newly discovered costs are input.  Then invoice item B.

All I can say is that your web page processing is messed up in an interesting way.  "Clear all GL field entries" is the title of the button "Process Receive Items" and would not normally display unless the cursor hovered over the button.

It would interesting to "View Page Source" in your browser to see what was actually loaded and that might give you a clue as to what is happening.  Maybe the server is sending some garbage preventing the browser from running the page.

63

(5 replies, posted in Accounts Receivable)

You could take the same approach for item insertion when modifying the code: make a copy of the cart, insert a new line, delete the cart items after the new line, then add the items after the new line from the cart copy.   This would be a nice feature to offer for inclusion into the base code.

Alternatively, you could avoid using the FA editor and instead enter orders as csv files and use the import transactions extension.  When a customer makes a change, void the entire order, and update the csv using a text editor and reimport.  However, I think this only currently supports sales orders and direct invoices and not quotations.

Because FA does not have line numbers on the order, you can also use a pdf editor to move stuff around to match the look of the customer's PO before you print.   For this purpose, I use Libreoffice Draw, which works quite well.

Note that two files had code changes.  You would have to make both changes in order to create and then print a PO with the same item but different descriptions.

If you are unable to make the code changes, then you could use the workaround suggested by paul in the same thread: create the PO, then delete the supplier in the purchasing tab of the item, and then print the PO.

See, https://frontaccounting.com/punbb/viewtopic.php?id=8754.

66

(3 replies, posted in Reporting)

If you are trying to get a report of cash on a day by day basis, you might be better off starting with a banking report because you can select the cash account whereas print receipt reports all receipts regardless of payment type.   For a day by day look at a bank account, you might try this extension.

FA only supports Suppliers and Customers as counterparties.  Indeed these are the only searchable persons in vanilla FA.

So even if you were able to change bank payments to use a G/L account instead of a bank transaction, if you wanted a counterparty, you would have to create a Supplier or a Customer to make the payment to.

While you can write a check to a Miscellaneous or Quickentry person, there is no support to search for those transactions by person.  An employee is not a supported person.

Therefore, if you were hoping on writing the check to either of those types using Bank->Payments (but to a G/L account), there really is no difference to using GJ and just memoing the name of the person who writes the checks.

Could you create a "Petty Cashier" supplier and set its A/P account to "Petty Cash"?

Write a check to the Petty Cashier from the bank account against the Petty Cash g/l account.  Use journal entry to enter the distributions from the petty cash account.  The Petty Cashier should pop up as the counterparty.

Could you use Bank Account Transfer for this purpose?

Transaction 1 :  Bank Account Transfer -> From Bank to Petty Cash

Transaction 2 : Payment ->  From Petty cash  against Employee acct

70

(22 replies, posted in Setup)

I cannot speak to the correctness of your cli script approach, but you may want to instead consider modifying the applicable FA timeouts.   You can extend the maximum php timeout as in this post.   The ajax timeout is less straightforward because there is no option to set the timeout.  You could try changing js/inserts.js:

//  '.ajaxsubmit,.editbutton,.navibutton': // much slower on IE7
    'button.ajaxsubmit,input.ajaxsubmit,input.editbutton,button.editbutton,button.navibutton':
    function(e) {
            e.onclick = function() {
                if (validate(e)) {
                    save_focus(e);
                    var asp = e.getAttribute('aspect')
                    if (asp && (asp.indexOf('process') !== -1))
                        JsHttpRequest.request(this, null, 600000); // ten minutes for backup
                    else
                        JsHttpRequest.request(this);
                }
                return false;
            }
    },

to

//  '.ajaxsubmit,.editbutton,.navibutton': // much slower on IE7
    'button.ajaxsubmit,input.ajaxsubmit,input.editbutton,button.editbutton,button.navibutton':
    function(e) {
            e.onclick = function() {
                if (validate(e)) {
                    save_focus(e);
                    var asp = e.getAttribute('aspect')
                    if (asp && (asp.indexOf('process') !== -1))
                        JsHttpRequest.request(this, null, 600000); // ten minutes for backup
                    else
                        JsHttpRequest.request(this. null, 600000); // ten minutes for all buttons such as delete?
                 }
                return false;
            }
    },

but I am just guessing that the fiscal year delete button executes that code.

Yes, the diff was in error; the mod was for rep209.php. (Stands corrected now).

I don't like the idea of an option to mask what really is an obscure bug.  Someone else unaware of the option will run into the same problem down the line and spend hours searching for the same solution.  It is better if they never run into the problem into the first place.

Yes, you can workaround the problem the way you did, but the workaround is non-intuitive and distracting.  If it were me, I would just code around it so it never happens again.

Thanks for testing this.

Unfortunately my suggested fix was not complete because a lack of code modularity: the print code does not call the underlying base code functions.  This is a bit disconcerting as you found out: viewing or editing a PO does not necessarily produce the same output as printing.   Not only is description is affected, but also units, price, and quantity which could easily lead to surprising results for the purchasing team when changing suppliers.

While I believe the correct fix is to change the print code to call the base function, the bandaid fix is to add the same logic that I suggested for the base code:

diff --git a/core/reporting/rep209.php b/core/reporting/rep209.php
index 2ed8cb8..8b9a15b 100644
--- a/core/reporting/rep209.php
+++ b/core/reporting/rep209.php
@@ -49,7 +49,11 @@ function get_po($order_no)
 
 function get_po_details($order_no)
 {
-       $sql = "SELECT poline.*, units
+       $sql = "SELECT poline.*, units,
+            (SELECT COUNT(*)
+                FROM ".TB_PREF."purch_order_details poline2
+                WHERE poline.item_code = poline2.item_code
+                    AND poline.description != poline2.description) AS dup
                FROM ".TB_PREF."purch_order_details poline
                        LEFT JOIN ".TB_PREF."stock_master item ON poline.item_code=item.stock_id
                WHERE order_no =".db_escape($order_no)." ";
@@ -118,19 +122,21 @@ function print_po()
                $items = $prices = array();
                while ($myrow2=db_fetch($result))
                {
-                       $data = get_purchase_data($myrow['supplier_id'], $myrow2['item_code']);
-                       if ($data !== false)
-                       {
-                               if ($data['supplier_description'] != "")
-                                       $myrow2['description'] = $data['supplier_description'];
-                               if ($data['suppliers_uom'] != "")
-                                       $myrow2['units'] = $data['suppliers_uom'];
-                               if ($data['conversion_factor'] != 1)
-                               {
-                                       $myrow2['unit_price'] = round2($myrow2['unit_price'] * $data['conversion_factor'], user_price_dec());
-                                       $myrow2['quantity_ordered'] = round2($myrow2['quantity_ordered'] / $data['conversion_factor'], user_qty_dec());
-                               }
-                       }
+            if ($myrow2['dup'] == 0) {
+                $data = get_purchase_data($myrow['supplier_id'], $myrow2['item_code']);
+                if ($data !== false)
+                {
+                    if ($data['supplier_description'] != "")
+                        $myrow2['description'] = $data['supplier_description'];
+                    if ($data['suppliers_uom'] != "")
+                        $myrow2['units'] = $data['suppliers_uom'];
+                    if ($data['conversion_factor'] != 1)
+                    {
+                        $myrow2['unit_price'] = round2($myrow2['unit_price'] * $data['conversion_factor'], user_price_dec());
+                        $myrow2['quantity_ordered'] = round2($myrow2['quantity_ordered'] / $data['conversion_factor'], user_qty_dec());
+                    }
+                }

While I understand your suggestion about allowing additional entries in the purchase data table, this would require a database change and substantive code changes which I really don't think really would be worth the coding and testing effort.

Or you could change the code to disable the PO description recall if the same item with a different description is used more than once on the PO:

diff --git a/core/purchasing/includes/db/po_db.inc b/core/purchasing/includes/db/po_db.inc
index b13c100..cb00da7 100644
--- a/core/purchasing/includes/db/po_db.inc
+++ b/core/purchasing/includes/db/po_db.inc
@@ -208,7 +208,11 @@ function read_po_items($order_no, &$order, $open_items_only=false)
 {
        /*now populate the line po array with the purchase order details records */
 
-       $sql = "SELECT poline.*, units
+       $sql = "SELECT poline.*, units,
+            (SELECT COUNT(*) 
+                FROM ".TB_PREF."purch_order_details poline2
+                WHERE poline.item_code = poline2.item_code
+                    AND poline.description != poline2.description) AS dup
                FROM ".TB_PREF."purch_order_details poline
                        LEFT JOIN ".TB_PREF."stock_master item  ON poline.item_code=item.stock_id
                WHERE order_no =".db_escape($order_no);
@@ -224,12 +228,14 @@ function read_po_items($order_no, &$order, $open_items_only=false)
     {
                while ($myrow = db_fetch($result))
         {
-               $data = get_purchase_data($order->supplier_id, $myrow['item_code']);
-               if ($data !== false)
-               {
-                       if ($data['supplier_description'] != "")
-                               $myrow['description'] = $data['supplier_description'];
-               }               
+            if ($myrow['dup'] == 0) {
+                $data = get_purchase_data($order->supplier_id, $myrow['item_code']);
+                if ($data !== false)
+                {
+                    if ($data['supplier_description'] != "")
+                        $myrow['description'] = $data['supplier_description'];
+                }              
+            }
             if (is_null($myrow["units"]))
             {
                        $units = "";

The advice is the same for that message as well.

You can still record a direct supplier invoice with option 1 or 2 with payment method set to delayed or other than petty cash.  You just cannot pay with a non-cash account.  So if you do not pay any suppliers with petty cash, it is OK to change the petty cash account to some other bank account type.